文章目录
day1
数据库环境搭建
导入表结构,命令行中: (注意目录中不要有中文字符)
可以直接拖动文件进去
mysql> source D:\db\xxx
配置WebMvcConfig
如果不放到static文件夹下,是无法访问到我们的静态资源的。
此时可以通过这个配置类进行静态资源映射。
注意点:
@Configuration 声明是配置类 且要放在config文件夹下
config文件夹与启动类同级
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport{
/**
* 配置静态资源访问
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("配置静态资源访问");
registry.addResourceHandler("/backend/**").
addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").
addResourceLocations("classpath:/front/");
}
}
弹幕:
Dao是ssm基于jdbc的,需要在impl中实现具体函数。Mapper是Mybatis的,只需要接口映射xml就可以
//controller
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
}
//service
public interface EmployeeService extends IService<Employee> {
}
//serviceImpl
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}
/*
1. 实现了 EmployeeService 接口。这意味着该类需要提供接口中定义的所有方法的具体实现
2. 通过继承 ServiceImpl ,以使用 MyBatis-Plus 提供的通用服务方法
3. ServiceImpl 是 MyBatis-Plus 提供的一个基础实现类,包含了对数据库的基本 CRUD 操作
*/
//mapper
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
/*
1. Mapper注解 允许 MyBatis 自动生成实现类来执行 SQL 操作
2. BaseMapper 是 MyBatis-Plus 提供的一个通用 Mapper 接口,获得了一系列的 CRUD 方法(如 insert, delete, update, selectById 等),这些方法与 Employee 实体类相对应。
*/
返回结果类
@Data
public class R<T> {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
private Map map = new HashMap(); //动态数据
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
public static <T> R<T> error(String msg) {
R r = new R();
r.msg = msg;
r.code = 0;
return r;
}
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
登录/退出登录
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername, employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
if (emp == null) {
return R.error("用户名不存在");
}
if (!emp.getPassword().equals(password)) {
return R.error("密码错误");
}
if(emp.getStatus()==0){
return R.error("账号已被禁用");
}
request.getSession().setAttribute("employee", emp);
return R.success(emp);
}
@PostMapping("/logout")
public R<String>logout(HttpServletRequest request){
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
}
day2
完善登录功能
实现步骤:
1.自定义过滤器
2.在启动类上加入注解@ServletComponentScan
3.完善过滤器
package com.itheima.reggie.filter;
import java.io.IOException;
import com.alibaba.fastjson.JSON;
import com.itheima.reggie.common.R;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import jakarta.servlet.ServletException;
import org.springframework.util.AntPathMatcher;
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
//路径匹配器,支持通配符
public static final AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//向下转型
//浏览器传进来的参数是HttpServletRequest类型,而ServletRequest是HttpServletRequest的父类
//我们用这个父类接收,所以要向下转型,才能使用子类的方法
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response= (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
log.info("requestURI:{}",requestURI);
//2.定义不需要处理的路径
String[] urls = {
"/employee/login",
"/employee/logout",
"/backend/**",
"/frontend/**",
};
//3.如果不需要处理,则直接放行
if(checkUrl(requestURI, urls)){
filterChain.doFilter(request, response);
return;
}
//4.如果需要处理,则判断是否登录
Object employee = request.getSession().getAttribute("employee");
if(employee!=null){
log.info("已登录,用户信息:{}",employee);
filterChain.doFilter(request, response);
return;
}
//5.如果没有登录,则通过输出流向客户端页面响应数据
log.info("未登录,拦截请求:{}",requestURI);
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
public boolean checkUrl(String requestURI, String[] urls) {
for (String url : urls) {
if (pathMatcher.match(url, requestURI)) {
return true;
}
}
return false;
}
}
新增员工
//GlobalExceptionHandler.java
//全局异常处理器
@ControllerAdvice(annotations = {RestController.class, Controller.class})//无论是Controller还是RestController都会被拦截
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String>exceptionHandler(SQLIntegrityConstraintViolationException e){
log.error(e.getMessage());
if(e.getMessage().contains("Duplicate entry")){
String[] split = e.getMessage().split(" ");
String msg =split[2]+"已存在";
return R.error(msg);
}
return R.error("未知错误");
}
}
//EmployeeController.java
@PostMapping
public R<String> save(HttpServletRequest request, @RequestBody Employee employee) {
log.info("employee:{}", employee.toString());
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获得当前登录用户id
Object emp = request.getSession().getAttribute("employee");
Long empId = ((Employee) emp).getId();
employee.setCreateUser(empId);
employee.setUpdateUser(empId);
employeeService.save(employee);
return R.success("保存成功");
}
分页查询员工信息
//EmployeeController.java
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
log.info("page:{},pageSize:{},name:{}",page,pageSize,name);
//构建分页构造器
Page pageInfo = new Page(page,pageSize);
//条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
//过滤条件
queryWrapper.like(name!=null,Employee::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
employeeService.page(pageInfo,queryWrapper);
//执行查询
return R.success(pageInfo);
}
//MybatisPlusConfig
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
启用/禁用员工信息
问题:前端js处理数字最多存16位,导致前段返回的Id与数据库不一致
解决方法:服务端用消息转换器
具体实现步骤:
1)提供对象转换器JacksonObjectMapper,基于jackson进行Java对象到json数据的转换
//一个工具类
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
2)在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行java对象到json数据的转换
//JaksonObjectMapper.java
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
@Component
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
//WebMvcConfig.java
/**
* 扩展mvc框架的消息转换器
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展mvc框架的消息转换器");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转换为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器添加到mvc框架的消息转换器集合中
converters.add(0,messageConverter);//index=0说明是优先使用
}
弹幕里面的其他方法:
1.在实体类中的id上添加注解:@JsonSerialize(using=ToStringSerializer.class)
2.在实体类中的id上添加注解:
@JsonFormat(shape=JsonFormat.Shape.STRING)
编辑员工信息
编辑和新增共用一个页面 只要新增一个返回员工信息的接口就可以了
//EmploeeController.java
@GetMapping("/{id}")
public R<Employee> findById(@PathVariable Long id){
log.info("根据Id查询员工信息,id:{}",id);
Employee employee = employeeService.getById(id);
if(employee==null){
return R.error("员工不存在");
}
return R.success(employee);
}
day3
公共字段自动填充
创建人、创建时间、修改时间、修改人是公共字段
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。
实现步骤:
1、在实体类的属性上加入@TableField注解,指定自动填充的策略
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口
问题:怎么在这个元数据对象处理器接口获取当前用户id
ThreadLocal类
在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
1、LoginCheckFilter的doFilter方法
2、Employeecontroller的update方法
3、MyMetaObjectHandler的updaeFill方法
可以在上面的三个方法中分别加入下面代码(获取当前线程id):
long id = Thread.currentThread().getId():
log.info("线程id:{}”.id);
执行编辑员工功能进行验证,通过观察控制台输出可以发现,一次请求对应的线程id是相同的:
-
什么是ThreadLocal?
-
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
-
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
-
ThreadLocal常用方法:
- public void set(T value)
- 设置当前线程的线程局部变量的值
- public T get()
- 返回当前线程所对应的线程局部变量的值
- public void set(T value)
-
解决步骤:
- 我们可以在LoginCheckfilter的doFilter方法中获取当前登录用户id
- 并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id)
- 然后在MyMeta0bjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
实现步骤:
实现步骤:
1、编写BaseContext工具类,基于ThreadLocal封装的工具类
package com.itheima.reggie.common;
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
}
2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
Employee employee = (Employee) request.getSession().getAttribute("employee");
if(employee!=null){
BaseContext.setCurrentId(employee.getId());
log.info("已登录,用户信息:{}",employee);
filterChain.doFilter(request, response);
return;
}
3、在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insertFill]");
metaObject.setValue("createTime", LocalDateTime.now()) ;
metaObject.setValue("updateTime", LocalDateTime.now()) ;
metaObject.setValue("createUser", BaseContext.getCurrentId()) ;
metaObject.setValue("updateUser", BaseContext.getCurrentId()) ;
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[updateFill]");
metaObject.setValue("updateTime", LocalDateTime.now()) ;
metaObject.setValue("updateUser", BaseContext.getCurrentId()) ;
}
}
新增分类
@PostMapping
public R<String> save(@RequestBody Category category) {
log.info("保存分类信息:{}", category);
categoryService.save(category);
return R.success("保存成功");
}
分类信息分页查询
@GetMapping("/page")
public R page(int page,int pageSize) {
Page<Category> pageInfo = new Page<>(page, pageSize);
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByAsc(Category::getSort);
categoryService.page(pageInfo, queryWrapper);
return R.success(pageInfo);
}
删除分类
//Contoller
@DeleteMapping
public R<String> delete(Long ids) {
log.info("删除分类信息:{}", ids);
categoryService.removeById(ids);
return R.success("删除成功");
}
//Service
public interface CategoryService extends IService<Category> {
public void remove(Long id);
}
//ServiceImpl
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService{
@Autowired
private DishService dishService;
@Autowired
private SetmealService setmealService;
@Override
public void remove(Long id) {
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
dishLambdaQueryWrapper.eq(Dish::getCategoryId, id);
int count1 = (int) dishService.count(dishLambdaQueryWrapper);
if (count1 > 0) {
throw new RuntimeException("该分类下有菜品,不能删除");
}
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId, id);
int count2 = (int) setmealService.count(setmealLambdaQueryWrapper);
if(count2 > 0){
throw new RuntimeException("该分类下有套餐,不能删除");
}
}
}
修改分类
@PutMapping
public R<String> update(@RequestBody Category category) {
log.info("更新分类信息:{}", category);
categoryService.updateById(category);
return R.success("更新成功");
}
day4
文件上传下载
文件上传
- 服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
- commons-fileupload
- commons-io
- Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明-个MultipartFile类型的参数即可接收上传的文件,例如:
文件下载
-
文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程
-
通过浏览器进行文件下载,通常有两种表现形式:
- 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
- 直接在浏览器中打开
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。
import org.springframework.beans.factory.annotation.Value;
@Value("${reggie.path}")
//注意导的是这个包
//CommonController
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {
//file是一个临时文件,需要转存到指定位置,否则本次请求完成后,文件会被删除
@Value("${reggie.path}")
private String basePath;
//上传文件
@PostMapping("/upload")
public R<String> upload(MultipartFile file) {
log.info("上传文件:{}", file.getOriginalFilename());
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//使用UUID生成文件名
String fileName = UUID.randomUUID().toString() + suffix;
File dir = new File(basePath);
if(!dir.exists()) {
dir.mkdirs();
}
try {
//将文件保存到指定位置
file.transferTo(new File(basePath + fileName));
}catch (Exception e) {
log.error("上传文件失败", e.getMessage());
}
return R.success(fileName);
}
//下载文件
@GetMapping("/download")
public void download(String name, HttpServletResponse response) {
//输入流 读取文件
try{
FileInputStream fis = new FileInputStream(basePath + name);
ServletOutputStream os = response.getOutputStream();
response.setContentType("image/jpeg");
byte[] bytes = new byte[1024];
int len = 0;
while((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
os.flush();
}
//关闭资源
fis.close();
os.close();
}catch (Exception e) {
log.error("下载文件失败", e.getMessage());
}
}
}
新增菜品
DTO,全称为Data Transfer Object,即数据传输对象,一般用于展示层与服务层之间的数据传输。
//DishDto
@Data
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName;
private Integer copies;
}
//DishController
@RequestMapping("/dish")
@RestController
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
log.info("dishDto:{}",dishDto.toString());
dishService.saveWishFlavour(dishDto);
return R.success("新增菜品成功");
}
}
//DishService
public interface DishService extends IService<Dish> {
//新增菜品 同时插入菜品对应的口味数据 要操作两张表:dish、dish_flavor
public void saveWishFlavour(DishDto dishDto);
}
//DishServiceImpl
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
@Override
@Transactional
public void saveWishFlavour(DishDto dishDto) {
//新增菜品 同时插入菜品对应的口味数据 要操作两张表:dish、dish_flavor
this.save(dishDto);//调用 ServiceImpl 类中的 save 方法
Long dishId = dishDto.getId();
List<DishFlavor> flavors = dishDto.getFlavors();
flavors.stream().map((item)->{
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
}
菜品信息分页查询
因为在分页查询的Dish的records(菜品记录中),只有这个菜品所属的categoryId,但是我们需要分页的时候展示的是菜品名字。
又因为DishDto里面有分类名称,所以改成返回的是DishDto
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
Page<Dish> pageInfo = new Page<>(page,pageSize);//存储原始菜品Dish的分页信息
Page<DishDto> dishDtoPage = new Page<>(page,pageSize);//存储转换后的菜品分页信息
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(name!=null,Dish::getName,name);//如,则查询果 name 不为空,则查询
queryWrapper.orderByDesc(Dish::getCreateTime);
dishService.page(pageInfo,queryWrapper);
//对象拷贝
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");//将pageInfo中的属性 除了records复制到dishDtoPage中
List<Dish> records = pageInfo.getRecords();//获取菜品记录列表
//将每个Dish转换为DishDto
/*
1.创建一个新的DishDto示例
2.复制Dish的属性到DishDto
3.根据categoryId来获取相应的Category对象,并设置名称到DishDto中
*/
List<DishDto> list = records.stream().map((item)->{
DishDto dishDto = new DishDto();// 1
BeanUtils.copyProperties(item,dishDto);// 2
Long categoryId = item.getCategoryId(); //3
Category category = categoryService.getById(categoryId);
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(list);//将转换后的列表设置到dishDtoPage的记录中
return R.success(dishDtoPage);
}
修改菜品
- 单个菜品信息回显
- 查询dish
- 把dish转换为dishDto
- 通过菜品id 查询dishFlavor
- 把口味加到dishDto上
//controller
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id){
DishDto dishDto = dishService.getByIdWithFlavor(id);
return R.success(dishDto);
}
//service
public DishDto getByIdWithFlavor(Long id);
//serviceImpl
public DishDto getByIdWithFlavor(Long id) {
Dish dish = this.getById(id);
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish,dishDto);
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dish.getId());
List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(flavors);
return dishDto;
}
- 修改
- 首先直接更新dish表的基本信息
- 然后把当前口味表直接全部删除 再添加回去
//controller
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
log.info("dishDto:{}",dishDto.toString());
dishService.updateWithFalvor(dishDto);
return R.success("修改菜品成功");
}
//Service
public void updateWithFalvor(DishDto dishDto);
//ServiceImpl
@Override
public void updateWithFalvor(DishDto dishDto) {
//更新dish表基本信息
this.updateById(dishDto);
//清理当前菜品对应口味数据 dish_flavor表的delete操作
dishFlavorService.remove(new LambdaQueryWrapper<DishFlavor>().eq(DishFlavor::getDishId,dishDto.getId()));
//添加当前提交的口味数据 dish_flavor表的insert操作
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item)->{
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
day5
新增套餐
将新增页面录入的套餐信息插入到setmeal表,还要向setmeal_dish表插入套餐和菜品关联数据,所以新增的时候涉及到两个表。
1.展示菜品分类
@GetMapping("/list")
public R<List<Dish>> list(Dish dish){
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Dish::getStatus,1);
queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
List<Dish> list = dishService.list(queryWrapper);
return R.success(list);
}
@Override
public void saveWithDish(SetmealDto setmealDto) {
//保存套餐信息 操作setmeal表 执行insert操作
this.save(setmealDto);
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
setmealDishes.forEach(item -> item.setSetmealId(setmealDto.getId()));
//保存套餐和菜品的关系 操作setmeal_dish表 执行insert操作
setmealDishService.saveBatch(setmealDishes);
}
分页查询
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//分页构造器对象
Page<Setmeal> pageInfo = new Page<>(page,pageSize);
Page<SetmealDto> dtoPage = new Page<>();
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据name进行like模糊查询
queryWrapper.like(name != null,Setmeal::getName,name);
//添加排序条件,根据更新时间降序排列
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
setmealService.page(pageInfo,queryWrapper);
//对象拷贝
BeanUtils.copyProperties(pageInfo,dtoPage,"records");
List<Setmeal> records = pageInfo.getRecords();
List<SetmealDto> list = records.stream().map((item) -> {
SetmealDto setmealDto = new SetmealDto();
//对象拷贝
BeanUtils.copyProperties(item,setmealDto);
//分类id
Long categoryId = item.getCategoryId();
//根据分类id查询分类对象
Category category = categoryService.getById(categoryId);
if(category != null){
//分类名称
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
dtoPage.setRecords(list);
return R.success(dtoPage);
}
删除套餐
@Override
@Transactional
public void removeWithDish(List<Long> ids) {
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Setmeal::getId, ids);
queryWrapper.eq(Setmeal::getStatus, 1);
long count = this.count(queryWrapper);
if(count>0) {
throw new RuntimeException("删除的套餐中存在已上架的套餐,不能删除");
}
//删除套餐和菜品的关系 操作setmeal_dish表 执行delete操作
this.removeByIds(ids);
setmealDishService.remove(new LambdaQueryWrapper<SetmealDish>().in(SetmealDish::getSetmealId, ids));
}