Bootstrap

[自用,更新自day5]瑞吉外卖代码及笔记

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()
      • 返回当前线程所对应的线程局部变量的值
  • 解决步骤:

    • 我们可以在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));
    }
;