• 瑞吉外卖笔记


    文章目录

    😹 作者: gh-xiaohe
    😻 gh-xiaohe的博客
    😽 觉得博主文章写的不错的话,希望大家三连(✌关注,✌点赞,✌评论),多多支持一下!!

    项目视频链接:黑马程序员,瑞吉外卖项目

    💒 后台系统业务功能开发

    数据表

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rpQhByCi-1661780532526)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220829213105662.png)]

    🚏 1、登录、退出功能

    🚀 基类R

    /**
     * 通用返回结果,服务端响应的数据最终都会封装成此对象
     */
    @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;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    🚄 登录 @PostMapping(“/login”)

    • DigestUtils md5加密工具类
    • 登录页面展示(页面位置:项目/resources/backend/page/login/login.html)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sdGkmOXd-1661780532528)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220829214109879.png)]

    @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());
    
            // 通过username进行对比
            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.getId());
    
            return R.success(emp);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    http://localhost:8080/backend/page/login/login.html

    🚒 静态资源映射 WebMvcConfig(重点)

    • 创建配置类WebMvcConfig,设置静态资源映射,否则无法访问页面
    /**
     * @author gh  Email:@2495140780qq.com
     * @Description
     * @date 2022-07-05-下午 9:53
     */
    @Slf4j
    @Configuration
    public class WebMvcConfig extends WebMvcConfigurationSupport {
        /**
         * 设置静态资源映射   resources 下 不写static 和 template 无法访问页面  处理映射
         * @param registry
         */
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            log.info("开始进行静态资源映射");
            // 使用 returnValueHandlers 来设置要映射哪些访问路径 访问路径映射到哪些资源文件
            registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
            registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    🚤 退出功能

    @Slf4j
    @RestController
    @RequestMapping("/employee")
    public class EmployeeController {
    
        @Autowired
        private EmployeeService employeeService;
        
        // 退出功能
        @RequestMapping("logout")
        public R<String> logout(HttpServletRequest request) {
            request.getSession().removeAttribute("employee");
            return R.success("退出成功");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    🚏 2、完善登录功能、新增员工、员工信息分页查询、启用/禁用员工账号、编辑员工信息

    🚀 完善登录功能

    🚬 自定义过滤器LoginCheckFilter(重点)
    • 实现Filter

    • AntPathMatcher 路径匹配器,支持通配符

    • 启动类上加入注解 @ServletComponentScan

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxkpbyKn-1661822071997)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220830085504935.png)]

    /**
     * 检查用户是否已经完成登录过滤器
     */
    @WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*") // 顾虑器的名称 和 拦截的路径
    @Slf4j
    public class LoginCheckFilter implements Filter{
    
        // 路径匹配器,支持通配符
        public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            // 向下转型
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
    
            // 1、获取本次请求的URI
            String requestURI = request.getRequestURI();//
    
            log.info("拦截到请求:{}",requestURI);
    
            // 定义不需要处理的请求路径
            String[] urls = new String[]{
                    "/employee/login",
                    "/employee/logout",
                    // 静态资源     /backend/index.html 需要使用 通配符的方式进行匹配上 需要使用AntPathMatcher对象
                    "/backend/**",
                    // 移动端页面 都是静态的 (页面也可以查看,数据不可以展示)
                    "/front/**"
            };
    
    
            // 2、判断本次请求是否需要处理
            boolean check = check(urls, requestURI);
    
            // 3、如果不需要处理,则直接放行
            if(check){
                log.info("本次请求{}不需要处理",requestURI);
                // 放行
                filterChain.doFilter(request,response);
                return;
            }
    
            // 4、判断登录状态,如果已登录,则直接放行
            if(request.getSession().getAttribute("employee") != null){
                log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
                // 放行
                filterChain.doFilter(request,response);
                return;
            }
    
            log.info("用户未登录");
            // 5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
            // backend/js/request.js 文件中响应拦截器 45行
            response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
            return;
    
        }
    
        /**
         * 路径匹配,检查本次请求是否需要放行
         * @param urls
         * @param requestURI
         * @return
         */
        public boolean check(String[] urls,String requestURI){
            for (String url : urls) {
                // match 匹配
                boolean match = PATH_MATCHER.match(url, requestURI);
                if(match){
                    return true;// 匹配成功
                }
            }
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    @Slf4j
    @SpringBootApplication()
    @ServletComponentScan
    public class ReggieApplication {
        public static void main(String[] args) {
            SpringApplication.run(ReggieApplication.class, args);
            log.info("项目启动成功...");
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    🚄 新增员工

    • 初始化密码 DigestUtils 工具类

    • 账号已经存在,由于employee表中对该字段加入了唯一约束,此时程序会抛出异常:

      • 异常信息 java.sql.SQLIntegrityConstraintViolationException: Duplicate entry ‘zhangsan’ for key ‘employee.idx_username’
    • 使用异常处理器进行全局异常捕获 GlobalExceptionHandler

    执行过程:

    1、页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端

    2、服务端Controller接收页面提交的数据并调用Service将数据进行保存

    3、Service调用Mapper操作数据库,保存数据

    在这里插入图片描述

    🚬 新增员工
        /**
         * 新增员工
         *
         * @param employee
         * @return
         */
        @PostMapping
        public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
            log.info("新增员工,员工信息:{}",employee.toString());
    
            // 设置初始密码123456,需要进行md5加密处理
            employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
    
            employee.setCreateTime(LocalDateTime.now());
            employee.setUpdateTime(LocalDateTime.now());
    
            // 获得当前登录用户的id
            Long empId = (Long) request.getSession().getAttribute("employee");
    
            // 创建人
            employee.setCreateUser(empId);
            // 更新人
            employee.setUpdateUser(empId);
    
            employeeService.save(employee);
    
            return R.success("新增员工成功");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    🚬 异常处理器 GlobalExceptionHandler(重点)
    /**
     * 全局异常处理
     */
    // ControllerAdvice 通知 拦截哪些Controller 拦截类上面加了这个两个注解的Controller
    @ControllerAdvice(annotations = {RestController.class, Controller.class})
    @ResponseBody
    @Slf4j
    public class GlobalExceptionHandler {
    
        /**
         * 异常处理方法
         * @return
         */
        @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
        public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {
    
            log.info(ex.getMessage()); // 输出异常信息
    
            // 异常信息 java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'zhangsan' for key 'employee.idx_username'
            if (ex.getMessage().contains("Duplicate entry")) { // 异常信息是否包含 Duplicate entry (重复录入)
                // 动态截取 重复信息
                String[] split = ex.getMessage().split(" ");
                String msg = split[2] + "已存在";
                return R.error(msg);
            }
            return R.error("未知错误");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    🚒 员工信息分页查询

    • 使用Mybatis Plus提供的分页插件来简化开发

    执行过程:

    1、页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端

    2、服务端Controller接收页面提交的数据并调用Service查询数据

    3、Service调用Mapper操作数据库,查询分页数据

    4、Controller将查询到的分页数据响应给页面

    5、页面接收到分页数据并通过ElementUI的Table组件展示到页面上

        页面中使用的是ElementUI提供的分页组件进行分页条的展示

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pbo4TYg6-1661823647629)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220830093815641.png)]

        页面中创建VUE对象完成后会调用init方法,在init方法中发送ajax请求并提交分页参数(page、pageSize、name),请求服务端Controller进行分页查询

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VrZGceNI-1661823647629)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220830093957643.png)]

    🚬 Mybatis Plus提供的分页插件
    /**
     * 配置MP的分页插件
     */
    @Configuration
    public class MybatisPlusConfig {
    
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); // MybatisPlus拦截器
            mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // addInnerInterceptor 添加内部拦截器 PaginationInnerInterceptor 页码内部拦截器
            return mybatisPlusInterceptor;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    🚬 分页功能
        /**
         * 员工信息分页查询
         *
         * @param page
         * @param pageSize
         * @param name
         * @return
         */
        @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);
    
            // 构造条件构造器 name 可不能传递
            LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
            // 添加过滤条件
            // like 自己封装了一个 条件判断 如果不传递: 就不进行添加 name 值
            // ==>  Preparing: SELECT COUNT(*) FROM employee WHERE (name LIKE ?)
    		// ==>  Parameters: %李四%(String)
            queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name); // 当name 不等于空时才会添加
            // 添加排序条件 使用更新时间进行排序
            queryWrapper.orderByDesc(Employee::getUpdateTime);
    
            // 执行查询
            employeeService.page(pageInfo,queryWrapper);
    
            return R.success(pageInfo);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    🚤 启用/禁用员工账号

    • 只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,
    • 普通用户登录系统后启用、禁用按钮不显示。
    • 提供对象转换器 JacksonObjectMapper 继承 ObjectMapper

        页面中是怎么做到只有管理员admin能够看到启用、禁用按钮的?

    在这里插入图片描述

    执行过程:

    1、页面发送ajax请求,将参数(id、status)提交到服务端

    2、服务端Controller接收页面提交的数据并调用Service更新数据

    3、Service调用Mapper操作数据库

        页面中的ajax请求是如果发送的呢?

    在这里插入图片描述

    🚬 代码实现
        /**
         * 修改员工信息
         *
         * @param employee
         * @return
         */
        @PutMapping
        public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
            log.info(employee.toString());
    
            Long empId = (Long)request.getSession().getAttribute("employee");
            // 更新时间
            employee.setUpdateTime(LocalDateTime.now());
            // 更新人 --> 登录用户
            employee.setUpdateUser(empId);
            employeeService.updateById(employee);
    
            return R.success("员工信息修改成功");
            // 分页 发送时 id 正确 但是 点击禁用时 id 传递错误 页面js的问题
            // long的数据 19位 js对数字处理的时候 丢失的精度 js(只能保证前16位)
            // 如何解决这个问题?我们可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    🚬 功能测试

    在这里插入图片描述

    🚬 代码修复 JacksonObjectMapper 工具类(重点)

    在这里插入图片描述

    具体实现步骤:

        1)提供对象转换器JacksonObjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)

        2)在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行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 添加序列化器
                    .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)
                    // Long型数据处理时 使用 此 ToStringSerializer.instance 序列化器
                    // 最终结果 Long 转换成 String
                    .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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    @Slf4j
    @Configuration
    public class WebMvcConfig extends WebMvcConfigurationSupport {
        /**
         * 扩展mvc框架的消息转换器 项目启动时就会调用
         *
         * @param converters
         */
        @Override
        protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
            log.info("扩展消息转换器...");
    
            // 创建消息转换器对象  作用: 将controller 的返回结果 转成 相应的 JSON 在通过输出流的方式 响应给页面
            MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
            // 设置对象转换器,底层使用Jackson将Java对象转为json
            messageConverter.setObjectMapper(new JacksonObjectMapper());
            // 将上面的消息转换器对象追加到mvc框架的转换器集合中
            // 索引 0 让自己转换器放在最前面 会优先使用自己的转换器
            converters.add(0,messageConverter);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    🚗 编辑员工信息

    在这里插入图片描述

    在这里插入图片描述

    执行过程:

    1、点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]

    2、在add.html页面获取url中的参数[员工id]

    3、发送ajax请求,请求服务端,同时提交员工id参数

    4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面

    5、页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显

    6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端

    7、服务端接收员工信息,并进行处理,完成后给页面响应

    8、页面接收到服务端响应信息后进行相应处理

    注意:add.html页面为公共页面,新增员工和编辑员工都是在此页面操作

        /**
         * 根据id查询员工信息
         *
         * @param id
         * @return
         */
        @GetMapping("/{id}")
        public R<Employee> getById(@PathVariable Long id){
            log.info("根据id查询员工信息...");
            Employee employee = employeeService.getById(id);
            if(employee != null){
                return R.success(employee);
            }
            return R.error("没有查询到对应员工信息");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    🚏 3、公共字段自动填充、新增分类、分类信息分页查询、删除分类、修改分类

    🚀 公共字段自动填充 (重点)

    使用Mybatis Plus提供的公共字段自动填充功能

    Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。

    🚬 实现步骤:
    • 1、在实体类的属性上加入@TableField注解,指定自动填充的策略

    • 2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口

    /**
     * 员工信息
     * @TableName employee
     */
    @TableName(value ="employee")
    @Data
    public class Employee implements Serializable {
    
        /**
         * 创建时间
         */
        @TableField(value = "create_time",fill = FieldFill.INSERT)
        private LocalDateTime createTime;
    
        /**
         * 更新时间
         */
        @TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
        private LocalDateTime updateTime;
    
        /**
         * 创建人
         */
        @TableField(value = "create_user",fill = FieldFill.INSERT)
        private Long createUser;
    
        /**
         * 修改人
         */
        @TableField(value = "update_user",fill = FieldFill.INSERT_UPDATE)
        private Long updateUser;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    注意:当前我们设置createUser和updateUser为固定值,后面我们需要进行改造,改为动态获得当前登录用户的id

    /**
     * 自定义 元数据对象处理器
     *
     *  注意,我们在MyMetaObjectHandler类中是不能获得HttpSession对象的,所以我们需要通过其他方式来获取登录用户id。
     *
     *      可以使用ThreadLocal来解决此问题,它是JDK中提供的一个类。
     */
    @Component
    @Slf4j
    public class MyMetaObjecthandler implements MetaObjectHandler {
        /**
         * 插入操作,自动填充
         * @param metaObject
         */
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("公共字段自动填充[insert]...");
            log.info(metaObject.toString());
            metaObject.setValue("createTime", LocalDateTime.now()); // 属性名 设置时间
            metaObject.setValue("updateTime",LocalDateTime.now());
            metaObject.setValue("createUser", new Long(1)); // 当前登录用户的id
            metaObject.setValue("updateUser",new Long(1));
    
        }
    
        /**
         * 更新操作,自动填充
         * @param metaObject
         */
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("公共字段自动填充[update]...");
            log.info(metaObject.toString());
    
            metaObject.setValue("updateTime",LocalDateTime.now());
            metaObject.setValue("updateUser",new Long(1));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    🚬 动态获得当前登录用户的id

    前面我们已经完成了公共字段自动填充功能的代码开发,但是还有一个问题没有解决,就是我们在自动填充createUser和updateUser时设置的用户id是固定值,现在我们需要改造成动态获取当前登录用户的id。

    将用户登录成功后我们将用户id存入了HttpSession中,现在我从HttpSession中获取不就行了?

    注意,我们在MyMetaObjectHandler类中是不能获得HttpSession对象的,所以我们需要通过其他方式来获取登录用户id。

    可以使用ThreadLocal来解决此问题,它是JDK中提供的一个类。

    在使用ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:

    • LoginCheckFilter的doFilter方法
    • EmployeeController的update方法
    • MyMetaObjectHandler的updateFill方法

    可以在上面的三个方法中分别加入下面代码(获取当前线程id):

    Long id = Thread.currentThread().getId();
    log.info("线程id:{}",id)
    
    • 1
    • 2

    通过观察控制台输出可以发现,一次请求对应的线程id是相同的:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YVVXIZTG-1662086864361)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220902103614020.png)]

    🚭 什么是ThreadLocal?

    ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为使用该变量线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本

    ThreadLocal为每个线程提供单独一份存储空间具有线程隔离的效果,只有在线程内才能获取到对应的值线程外不能访问

    🚭 ThreadLocal常用方法:
    • public void set(T value) 设置当前线程的线程局部变量的值
    • public T get() 返回当前线程所对应的线程局部变量的值

        我们可以在LoginCheckFilterdoFilter方法中获取当前登录用户id,并调用ThreadLocalset方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandlerupdateFill方法中调用ThreadLocalget方法来获得当前线程所对应的线程局部变量的值(用户id)

    🚭 实现步骤:(重点)
    • 1、编写BaseContext工具类,基于ThreadLocal封装的工具类
    • 2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
    • 3、在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id

    编写BaseContext工具类

    /**
     * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
     */
    public class BaseContext {
    
        // id 是Long类型
        private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    
        /**
         * 设置值
         * @param id
         */
        public static void setCurrentId(Long id){
            threadLocal.set(id);
        }
    
        /**
         * 获取值
         * @return
         */
        public static Long getCurrentId(){
            return threadLocal.get();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    调用BaseContext来设置当前登录用户的id

    /**
     * 检查用户是否已经完成登录过滤器
     */
    @WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*") // 顾虑器的名称 和 拦截的路径
    @Slf4j
    public class LoginCheckFilter implements Filter{
    
        // 路径匹配器,支持通配符
        public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
           
    
            // 4、判断登录状态,如果已登录,则直接放行
            if(request.getSession().getAttribute("employee") != null){
                log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
                BaseContext.setCurrentId(empId);
                filterChain.doFilter(request, response);
                // 放行
                filterChain.doFilter(request,response);
                return;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    调用BaseContext获取登录用户的id

    /**
     * 自定义 元数据对象处理器
     *
     *  注意,我们在MyMetaObjectHandler类中是不能获得HttpSession对象的,所以我们需要通过其他方式来获取登录用户id。
     *
     *      可以使用ThreadLocal来解决此问题,它是JDK中提供的一个类。
     */
    @Component
    @Slf4j
    public class MyMetaObjecthandler implements MetaObjectHandler {
        /**
         * 插入操作,自动填充
         * @param metaObject
         */
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("公共字段自动填充[insert]...");
            log.info(metaObject.toString());
            metaObject.setValue("createTime", LocalDateTime.now()); // 属性名 设置时间
            metaObject.setValue("updateTime",LocalDateTime.now());
            metaObject.setValue("createUser", BaseContext.getCurrentId()); // 当前登录用户的id
            metaObject.setValue("updateUser",BaseContext.getCurrentId());
    
        }
    
        /**
         * 更新操作,自动填充
         * @param metaObject
         */
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("公共字段自动填充[update]...");
            log.info(metaObject.toString());
    
            long id = Thread.currentThread().getId();
            log.info("线程id为:{}",id);
    
            metaObject.setValue("updateTime",LocalDateTime.now());
            metaObject.setValue("updateUser",BaseContext.getCurrentId());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    🚄 新增分类

    category表

    表结构如下:

    在这里插入图片描述

    需要注意,category表中对name字段加入了唯一约束,保证分类的名称是唯一的:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dXYWPwla-1662096022449)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220902131551683.png)]

    🚬 执行过程:
    • 1、页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
    • 2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
    • 3、Service调用Mapper操作数据库,保存数据
      在这里插入图片描述
    🚬 新增分类方法
        /**
         * 新增分类
         *
         * @param category
         * @return
         */
        @PostMapping
        public R<String> save(@RequestBody Category category){ // 传入的数据是JSON格式需要加入 @RequestBody注解
            log.info("category:{}",category);
            categoryService.save(category);
            return R.success("新增分类成功");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    🚒 分类信息分页查询

    🚬 执行过程:
    • 1、页面发送ajax请求,将分页查询参数(page、pageSize)提交到服务端
    • 2、服务端Controller接收页面提交的数据并调用Service查询数据
    • 3、Service调用Mapper操作数据库,查询分页数据
    • 4、Controller将查询到的分页数据响应给页面
    • 5、页面接收到分页数据并通过ElementUI的Table组件展示到页面上

    在这里插入图片描述

    🚬 分页方法
        /**
         * 分页查询
         * @param page
         * @param pageSize
         * @return
         */
        @GetMapping("/page")
        public R<Page> page(int page,int pageSize){
            // 分页构造器
            Page<Category> pageInfo = new Page<>(page,pageSize);
            // 条件构造器
            LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
            // 添加排序条件,根据sort进行排序
            queryWrapper.orderByAsc(Category::getSort);
    
            //分页查询
            categoryService.page(pageInfo,queryWrapper);
            return R.success(pageInfo);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    🚤 删除分类 (联表重点)

    🚬 执行过程:
    • 1、页面发送ajax请求,将参数(id)提交到服务端
    • 2、服务端Controller接收页面提交的数据并调用Service删除数据
    • 3、Service调用Mapper操作数据库
    🚬 删除方法 - 不检测关联
    /**
     * 根据id删除分类
     * @param ids
     * @return
     */
    @DeleteMapping
    public R<String> delete(Long ids){
        log.info("删除分类,id为:{}",ids);
    
        categoryService.removeById(id);
        return R.success("分类信息删除成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    🚬 删除方法 - 检测关联

        实现了根据id删除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善。


    Dish表 菜品

    Setmeal表 套餐

    🚭 具体实现步骤:
    • 1)创建自定义业务异常类
    • 2)在GlobalExceptionHandler中处理自定义异常
    • 3)在CategoryService中扩展remove方法
    • 4)在CategoryServiceImpl中实现remove方法
    • 5)CategoryController 中实现删除操作
    🚭 自定义业务异常类 CustomException
    /**
     * 自定义业务异常类
     */
    public class CustomException extends RuntimeException {
    
        // 传递提示信息
        public CustomException(String message){
            super(message);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    🚭 处理自定义异常
    /**
     * 全局异常处理
     */
    // ControllerAdvice 通知 拦截哪些Controller 拦截类上面加了这个两个注解的Controller
    @ControllerAdvice(annotations = {RestController.class, Controller.class})
    @ResponseBody
    @Slf4j
    public class GlobalExceptionHandler {
    
        /**
         * 异常自己定义的异常
         * @return
         */
        @ExceptionHandler(CustomException.class)
        public R<String> exceptionHandler(CustomException ex){
            log.error(ex.getMessage());
    
            return R.error(ex.getMessage());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    🚭 CategoryServiceImpl中实现remove方法
    @Service
    public class CategoryServiceImpl extends ServiceImpl<CategoryMapper,Category> implements CategoryService{
    
        @Autowired
        private DishService dishService;
    
        @Autowired
        private SetmealService setmealService;
    
        /**
         * 根据id删除分类,删除之前需要进行判断
         *
         * @param id
         */
        @Override
        public void remove(Long id) {
            LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
            //添加查询条件,根据分类id进行查询
            dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
            int count1 = dishService.count(dishLambdaQueryWrapper);
    
            //查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
            if(count1 > 0){
                //已经关联菜品,抛出一个业务异常
                throw new CustomException("当前分类下关联了菜品,不能删除");
            }
    
            //查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
            LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
            //添加查询条件,根据分类id进行查询
            setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
            int count2 = setmealService.count(setmealLambdaQueryWrapper);
            if(count2 > 0){
                //已经关联套餐,抛出一个业务异常
                throw new CustomException("当前分类下关联了套餐,不能删除");
            }
    
            //正常删除分类
            super.removeById(id);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    🚭 实现删除操作
    /**
     * 根据id删除分类
     * @param ids
     * @return
     */
    @DeleteMapping
    public R<String> delete(Long ids){
        log.info("删除分类,id为:{}",ids);
    
        //categoryService.removeById(id);
        categoryService.remove(ids);
    
        return R.success("分类信息删除成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    🚗 修改分类

    🚬 执行过程:
    • 1、点击修改按钮时,弹出修改窗口并回显数据
    • 2、点击确定按钮,发送ajax请求,将修改窗口的分类信息以json方式提交给服务端
    • 3、服务端接收分类信息,并进行处理,完成后给页面响应
    • 4、页面接收到服务端响应信息后进行相应处理
    🚬 修改分类方法
    /**
     * 根据id修改分类信息
     * @param category
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody Category category){
        log.info("修改分类信息:{}",category);
    
        categoryService.updateById(category);
    
        return R.success("修改分类信息成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    🚏 4、文件上传下载、新增菜品、菜品信息分页查询、修改菜品

    🚀 文件上传下载

    🚬 文件上传

        文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。
        
        文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。

    🚭 文件上传时,对页面的form表单有如下要求:
    • method=“post” 采用post方式提交数据

    • enctype=“multipart/form-data” 采用multipart格式上传文件

    • type=“file” 使用input的file控件上传

    举例:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTQmOAVY-1662257079402)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220904092329199.png)]

    🚭 服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
    • commons-fileupload

    • commons-io

    🚭 Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件

        Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如:

    在这里插入图片描述

    🚭 文件上传功能实现
    • 使用UUID生成文件名
    • 获取配置文件application.yml 中的 reggie.path信息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iVsjZ5f8-1662257079404)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220904095211185.png)]

    /**
     * 文件上传和下载
     */
    @RestController
    @RequestMapping("/common")
    @Slf4j
    public class CommonController {
    
        @Value("${reggie.path}")
        private String basePath; // 获取配置文件application.yml 中的 reggie.path信息
    
        /**
         * 文件上传
         * @param file
         * @return
         */
        @PostMapping("/upload")
        public R<String> upload(MultipartFile file){ // 必须声明这个类 MultipartFile  file 要和页面中的name="" 保持一致 Spring提供的
            //  file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
            log.info(file.toString());
    
            //  原始文件名
            String originalFilename = file.getOriginalFilename();//abc.jpg
            // 把原始文件名称后面的 后缀截取过来
            String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
    
            // 使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
            String fileName = UUID.randomUUID().toString() + suffix;//dfsdfdfd.jpg
    
            // 创建一个目录对象
            File dir = new File(basePath);
            //  判断当前目录是否存在 (传递路径可能是 D:/E/ed/abc) 目录不存在
            if(!dir.exists()){
                //目录不存在,需要创建
                dir.mkdirs();
            }
    
            try {
                // 将临时文件转存到指定位置
                file.transferTo(new File(basePath + fileName));
            } catch (IOException e) {
                e.printStackTrace();
            }
            return R.success(fileName);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    🚬 文件下载

        文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。

    🚭 通过浏览器进行文件下载,通常有两种表现形式:
    • 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
    • 直接在浏览器中打开

         通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。

    🚭 文件下载功能实现
    /**
     * 文件上传和下载
     */
    @RestController
    @RequestMapping("/common")
    @Slf4j
    public class CommonController {
    
        @Value("${reggie.path}")
        private String basePath; // 获取配置文件application.yml 中的 reggie.path信息
    
        /**
         * 文件下载
         * @param name
         * @param response
         */
        @GetMapping("/download")
        public void download(String name, HttpServletResponse response){
            // 输出流需要通过 HttpServletResponse 来响应
    
            try {
                // 输入流,通过输入流读取文件内容
                FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
    
                // 输出流,通过输出流将文件写回浏览器
                ServletOutputStream outputStream = response.getOutputStream();
    
                // 设置响应回去的是什么类型的文件
                response.setContentType("image/jpeg"); // 图片文件
    
                int len = 0;
                byte[] bytes = new byte[1024];
    
                // 输入流来读 放到数组中去 =-1 表示读取完毕
                while ((len = fileInputStream.read(bytes)) != -1){
                    // 通过输出流 向浏览器 写
                    outputStream.write(bytes,0,len);
                    // flush 刷新
                    outputStream.flush();
                }
    
                // 关闭资源
                outputStream.close();
                fileInputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    🚄 新增菜品

    dish 菜品表

    dish_flavor 菜品口味表

    🚬 前端页面和服务端的交互过程:
    • 1、页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中
    • 2、页面发送请求进行图片上传,请求服务端将图片保存到服务器
    • 3、页面发送请求进行图片下载,将上传的图片进行回显
    • 4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端

        开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

    🚬 查询菜品分类数据方法实现:
    /**
     * 根据条件查询分类数据
     * @param category
     * @return
     */
    @GetMapping("/list")
    public R<List<Category>> list(Category category){
        // 条件构造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        // 添加条件
        queryWrapper.eq(category.getType() != null,Category::getType,category.getType());
        // 添加排序条件 优先使用 getSort 其次使用getUpdateTime
        queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
    
        List<Category> list = categoryService.list(queryWrapper);
        return R.success(list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

        页面发送请求进行图片上传,请求服务端将图片保存到服务器,使用前面开发的CommonController upload 方法来处理即可。

        页面发送请求进行图片下载,将上传的图片进行回显,使用前面开发的CommonController download 方法来处理即可。

    🚬 新增菜品方法实现
    🚭 将菜品相关数据以json形式提交到服务端,请求信息如下

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4dhaRbeL-1662293177075)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220904152213792.png)]

    🚭 导入DTO

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xqPRZUKa-1662293177075)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220904151834474.png)]

    🚭 启用事务支持

    多表操作,为了保证数据一致性,需要启用事务管理

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UxG2HJmH-1662293177076)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220904151905276.png)]

    🚭 1、代码实现 - DishServiceImpl

    @Transactional 事务控制

    @Service
    @Slf4j
    public class DishServiceImpl extends ServiceImpl<DishMapper,Dish> implements DishService {
    
        @Autowired
        private DishFlavorService dishFlavorService;
    
        /**
         * 新增菜品,同时保存对应的口味数据
         *
         * @param dishDto
         */
        @Transactional // 多张表操作 加入事务控制  主启动类 开始是事务支持
        public void saveWithFlavor(DishDto dishDto) {
            // 保存菜品的基本信息到菜品表dish
            this.save(dishDto);
    
            Long dishId = dishDto.getId();//菜品id
    
            // 菜品口味
            List<DishFlavor> flavors = dishDto.getFlavors(); // dishDto.getFlavors() 没有装转好 dishId 需要单独处理
            // stream 流的方式处理 集合
            flavors = flavors.stream().map((item) -> { // 遍历出来的 item 就是 每个DishFlavor实体
                item.setDishId(dishId);
                return item;
            }).collect(Collectors.toList()); // collect(Collectors.toList()); 又把数据重新转换成集合
    
            // 保存菜品口味数据到菜品口味表dish_flavor
            // dishFlavorService.saveBatch(dishDto.getFlavors()); 只封装 name 和 value 没有封装上 dishId(对应菜品的id),dishId需要单独处理
            dishFlavorService.saveBatch(flavors);// saveBatch 批量保存
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    🚭 2、代码实现 - DishController
    @Autowired
    private DishService dishService;
    
    /**
     * 新增菜品
     *
     * @param dishDto
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody DishDto dishDto){ // 参数用来接收前端的发送的数据 flavors特殊 (所以Dish 不可以)
        // flavors 没有办法接收 方法: 封装类外一个类(DishDto) 在这个类中 把所有的参数都接收到
        log.info(dishDto.toString());
    
        dishService.saveWithFlavor(dishDto);
    
        return R.success("新增菜品成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    🚒 菜品信息分页查询

    🚬 交互过程:
    • 1、页面(backend/page/food/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据
    • 2、页面发送请求,请求服务端进行图片下载,用于页面图片展示

    开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

    🚬 分页查询方法实现
    • BeanUtils 对象拷贝工具类
        @Autowired
        private CategoryService categoryService;
    
    /**
     * 菜品信息分页查询
     *
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize, String name){
    
        // 构造分页构造器对象
        Page<Dish> pageInfo = new Page<>(page,pageSize); // dish 只有 categoryId(菜品分类id) 想展示 分类名称(无法做到)
        Page<DishDto> dishDtoPage = new Page<>();// 菜品分类的名称
    
        // 条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        // 添加过滤条件
        queryWrapper.like(name != null,Dish::getName,name);
        // 添加排序条件
        queryWrapper.orderByDesc(Dish::getUpdateTime);
    
        // 执行分页查询
        dishService.page(pageInfo,queryWrapper);// 经过查询之后数据就已经存在值 (需要赋值的只是categoryId )
    
        // 对象拷贝  copyProperties 拷贝属性 pageInfo 从 拷贝到 dishDtoPage 不需要拷贝全部(除了 dishDtoPage 之外的属性)
        // records 对应的是list结合就是页面上展示出来的列表数据 所承载的集合 这个地方需要进行处理
        BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
    
        List<Dish> records = pageInfo.getRecords(); // 把 Dish 处理成 DishDto
    
        List<DishDto> list = records.stream().map((item) -> { //   item 表示  Dish 遍历出来的每一个 菜品对象
            DishDto dishDto = new DishDto();
    
            BeanUtils.copyProperties(item,dishDto);// 把 item 中的属性 拷贝到  dishDto中
    
            Long categoryId = item.getCategoryId();//分类id
    
            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);
    
            if(category != null){
                String categoryName = category.getName();// 分类的名称
                dishDto.setCategoryName(categoryName);
            }
            return dishDto;
        }).collect(Collectors.toList());
    
        dishDtoPage.setRecords(list);
    
        return R.success(dishDtoPage);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    🚤 修改菜品

    🚬 交互过程:
    • 1、页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示
    • 2、页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显
    • 3、页面发送请求,请求服务端进行图片下载,用于页图片回显
    • 4、点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端

    开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

    🚬 修改菜品功能实现
    🚭 1、代码实现 - DishServiceImpl
        /**
         * 根据id查询菜品信息和对应的口味信息
         *
         * @param id
         * @return
         */
        @Override
        public DishDto getByIdWithFlavor(Long id) {
            // 查询菜品基本信息,从dish表查询
            Dish dish = this.getById(id);
    
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(dish,dishDto);
    
            // 查询当前菜品对应的口味信息,从dish_flavor表查询
            LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(DishFlavor::getDishId,dish.getId());
            List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
            dishDto.setFlavors(flavors);
    
            return dishDto;
        }
    
        /**
         * 更新菜品信息,同时更新对应的口味信息
         * @param dishDto
         */
        @Override
        @Transactional
        public void updateWithFlavor(DishDto dishDto) {
            // 更新dish表基本信息
            this.updateById(dishDto);
    
            // 清理当前菜品对应口味数据---dish_flavor表的delete操作
            // delete from dish_flavor where dish_id = ??? 清理口味数据
            LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();
            queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());// 根据 DishFlavor中的 DishId 传入 DishId
    
            dishFlavorService.remove(queryWrapper);
    
            // 添加当前提交过来的口味数据---dish_flavor表的insert操作
            List<DishFlavor> flavors = dishDto.getFlavors();
    
            flavors = flavors.stream().map((item) -> { // dishId 没有封装 同 saveWithFlavor 新增 一样的处理方式
                item.setDishId(dishDto.getId());
                return item;
            }).collect(Collectors.toList());
    
            dishFlavorService.saveBatch(flavors);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    🚭 2、代码实现 - DishController
    /**
     * 根据id查询菜品信息和对应的口味信息
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public R<DishDto> get(@PathVariable Long id){
    
        DishDto dishDto = dishService.getByIdWithFlavor(id);
    
        return R.success(dishDto);
    }
    
        /**
         * 修改菜品
         *
         * @param dishDto
         * @return
         */
        @PutMapping
        public R<String> update(@RequestBody DishDto dishDto){
            log.info(dishDto.toString());
    
            dishService.updateWithFlavor(dishDto);
    
    		return R.success("修改菜品成功");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    🌟 补全功能

    🚀 停售和起售

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YmdRHq5E-1662297617031)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220904211821411.png)]

    🚬 代码实现
    /**
     * 起售和停售
     * 
     * @param status
     * @param ids
     * @return
     */
    @PostMapping("/status/{status}")
    public R<String> status(@PathVariable Integer status,Long ids){
        log.info("status:{}",status);
        log.info("ids:{}",ids);
    
        Dish dish = dishService.getById(ids);
        if (dish != null) {
            dish.setStatus(status);
            dishService.updateById(dish);
            return R.success("开始起售");
        }
        return R.error("停售售卖");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    🚄 菜品批量启售和批量停售

    对单个菜品的售卖状态的方法进行修改;

    在这里插入图片描述

    🚬 代码实现
        /**
         * 批量起售和批量停售
         *
         * @param status
         * @param ids
         * @return
         */
        @PostMapping("/status/{status}")
        public R<String> status(@PathVariable Integer status, @RequestParam List<Long> ids) { 
            // @RequestParam 形参的参数名,请求参数的参数名不一致
            // @RequestParam 是将请求参数和控制器方法的形参创建映射关系
            
            log.info("status:{}", status);
            log.info("ids:{}", ids);
    
            LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.in(ids != null, Dish::getId, ids);
            // 根据数据进行批量查询
            List<Dish> list = dishService.list(queryWrapper);
    
            for (Dish dish : list) {
                if (dish != null) {
                    dish.setStatus(status);
                    dishService.updateById(dish);
                }
            }
            return R.success("批量状态修改成功");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    🚒 删除和批量删除

    • 逻辑删除 并非是真的删除,在表示逻辑删除 的字段上加入注解@TableLogic 注解,由mybatis-plus 提供。
    • 显示启售是停售的状态,之后再停售的状态下才可以删除
    🚬 代码实现DishFlavor Dish和 实体
         /**
         * 是否删除
         */
        @TableField(value = "is_deleted")
        @TableLogic
        private Integer isDeleted;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    🚬 代码实现 - DishServiceImpl
        /**
         * 套餐批量删除和单个删除
         *
         * @param ids
         */
        @Override
        @Transactional
        public void removeWitchDishFlavor(List<Long> ids) {
            
            //构造条件查询器
            LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.in(Dish::getCategoryId, ids);
            queryWrapper.eq(Dish::getStatus, 1);
    
            int count = this.count(queryWrapper);
            if (count > 0 ) {
                //如果不能删除,抛出一个业务异常
                throw new CustomException("菜品正在售卖,不能删除");
            }
    
            // 删除菜品表中数据
            this.removeByIds(ids);
    
            LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.in(DishFlavor::getDishId, ids);
            
            dishFlavorService.remove(lambdaQueryWrapper);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    🚬 代码实现 - DishController
        /**
         * 套餐批量删除和单个删除
         *
         * @return
         */
        @DeleteMapping
        public R<String> delete(@RequestParam("ids") List<Long> ids) {
            log.info("ids:{}", ids);
            dishService.removeWitchDishFlavor(ids);
            return R.success("菜品删除成功");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    🚏 5、新增套餐、套餐信息分页查询、删除套餐

    🚀 1、新增套餐

    setmeal 套餐表

    setmeal_dish 套餐菜品关系表

    🚬 交互过程:
    • 1、页面(backend/page/combo/add.html)发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中
    • 2、页面发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中
    • 3、页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中
    • 4、页面发送请求进行图片上传,请求服务端将图片保存到服务器
    • 5、页面发送请求进行图片下载,将上传的图片进行回显
    • 6、点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端

        开发新增套餐功能,其实就是在服务端编写代码去处理前端页面发送的这6次请求即可。

    🚬 代码实现
    🚭 1、查询套餐分类数据

        在CategoryController中提供方法,查询分类数据。注意:此方法在前面实现新增菜品时已经创建过了,此处直接调用即可。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UOdcxKQw-1662516486598)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220907094300407.png)]

    🚭 2、查询菜品分类数据

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RJBOp72H-1662516486599)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220907094239387.png)]

    🚭 3、查询菜品数据

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ch1KSMJM-1662516486599)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220907100727617.png)]

    /**
     * 根据条件查询对应的菜品数据
     *
     * @param dish
     * @return
     */
    @GetMapping("/list")
    public R<List<Dish>> list(Dish dish){
        //构造查询条件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());
        //添加条件,查询状态为1(起售状态)的菜品
        queryWrapper.eq(Dish::getStatus,1);
    
        //添加排序条件
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
    
        List<Dish> list = dishService.list(queryWrapper);
    
        return R.success(list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    🚭 4、图片上传

        直接使用我们前面开发的CommonController的upload方法来处理即可。

    🚭 5、图片下载

        直接使用我们前面开发的CommonController的download方法来处理即可。

    🚭 6、查看请求

    点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端。请求信息如下:

    @Data
    public class SetmealDto extends Setmeal {
    
        private List<SetmealDish> setmealDishes;
    
        private String categoryName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eGtNv7s6-1662519814935)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220907105012044.png)]

    🚭 7、功能实现

    控制层

    /**
     * 新增套餐
     * @param setmealDto
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody SetmealDto setmealDto){
        log.info("套餐信息:{}",setmealDto);
    
        setmealService.saveWithDish(setmealDto);
    
        return R.success("新增套餐成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    业务层实现

    @Service
    @Slf4j
    public class SetmealServiceImpl extends ServiceImpl<SetmealMapper,Setmeal> implements SetmealService {
    
        @Autowired
        private SetmealDishService setmealDishService;
    
        /**
         * 新增套餐,同时需要保存套餐和菜品的关联关系
         * @param setmealDto
         */
        @Transactional
        public void saveWithDish(SetmealDto setmealDto) {
            //保存套餐的基本信息,操作setmeal,执行insert操作
            this.save(setmealDto);
    
            List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
            setmealDishes.stream().map((item) -> {
                item.setSetmealId(setmealDto.getId());
                return item;
            }).collect(Collectors.toList());
    
            //保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
            setmealDishService.saveBatch(setmealDishes);
        }
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    🚄 2、套餐信息分页查询

    🚬 交互过程:
    • 1、页面(backend/page/combo/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据
    • 2、页面发送请求,请求服务端进行图片下载,用于页面图片展示
    🚬 代码实现
    /**
     * 套餐分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    🚒 3、删除套餐、和批量删除

        删除单个套餐和批量删除套餐的请求信息可以发现,两种请求的地址请求方式都是相同的,不同的则是传递的id个数,所以在服务端可以提供一个方法来统一处理。

    🚬 代码实现 - DishServiceImpl
    /**
     * 删除套餐,同时需要删除套餐和菜品的关联数据
     * @param ids
     */
    @Override
    @Transactional
    public void removeWithDish(List<Long> ids) {
        //select count(*) from setmeal where id in (1,2,3) and status = 1
        //查询套餐状态,确定是否可用删除
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.in(Setmeal::getId,ids);
        queryWrapper.eq(Setmeal::getStatus,1);
    
        int count = this.count(queryWrapper);
        if(count > 0){
            //如果不能删除,抛出一个业务异常
            throw new CustomException("套餐正在售卖中,不能删除");
        }
    
        //如果可以删除,先删除套餐表中的数据---setmeal
        this.removeByIds(ids);
    
        //delete from setmeal_dish where setmeal_id in (1,2,3)
        LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
        //删除关系表中的数据----setmeal_dish
        setmealDishService.remove(lambdaQueryWrapper);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    🚬 代码实现 - DishController
        /**
         * 套餐批量删除和单个删除
         *
         * @return
         */
        @DeleteMapping
        public R<String> delete(@RequestParam("ids") List<Long> ids) {
            log.info("ids:{}", ids);
            dishService.removeWitchDishFlavor(ids);
            return R.success("菜品删除成功");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    🌟 补全功能

    🚀 修改套餐

    🚬 套餐数据回显

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mHJT8ELq-1662555891640)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220907204312696.png)]

    SetmealServiceImpl

    /**
     * 根据条件查询对应的套餐数据
     * 
     * @param id
     */
    @Override
    public SetmealDto getDate(Long id) {
        // 根据id获取 setmeal 数据
        Setmeal setmeal = this.getById(id);
        SetmealDto setmealDto = new SetmealDto();
        LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(id != null, SetmealDish::getSetmealId,id);
        
        if (setmeal!= null) {
            // 拷贝数据
            BeanUtils.copyProperties(setmeal, setmealDto);
            // 根据套餐id查询数据
            List<SetmealDish> list = setmealDishService.list(queryWrapper);
            // 赋值
            setmealDto.setSetmealDishes(list);
            return setmealDto;
        }
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    SetmealController

    /**
     * 根据条件查询对应的套餐数据
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public R<SetmealDto> list(@PathVariable Long id){
        log.info("id:{}",id);
    
        SetmealDto setmealDto  = setmealService.getDate(id);
    
        return R.success(setmealDto);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    🚬 修改套餐数据

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4a2mEGH0-1662555891653)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220907204348068.png)]

    SetmealServiceImpl

    /**
     * 更新套餐信息,同时更新对应的套餐的菜品
     *
     * @param setmealDto
     */
    @Override
    @Transactional
    public void updateWithSetmeal(SetmealDto setmealDto) {
        // 修改setmeal表中信息
        this.updateById(setmealDto);
    
        // 删除对应的套餐菜品
        LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SetmealDish::getSetmealId, setmealDto.getId());
    
        setmealDishService.remove(queryWrapper);
    
        // 提交当前的套餐菜品信息
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
    
        setmealDishes = setmealDishes.stream().map((item) ->{
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());
    
        setmealDishService.saveBatch(setmealDishes);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    SetmealController

    /**
     * 修改套餐数据
     *
     * @param setmealDto
     * @return
     */
    @PutMapping
    public R<SetmealDto> update(@RequestBody SetmealDto setmealDto) {
        log.info(setmealDto.toString());
    
        setmealService.updateWithSetmeal(setmealDto);
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    🚄 套餐批量启售和批量停售

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dUEuNGbv-1662556924103)(%E7%91%9E%E5%90%89%E5%A4%96%E5%8D%96.assets/image-20220907210921218.png)]

    🚬 代码实现
        /**
         * 套餐批量起售和批量停售
         *
         * @param status
         * @param ids
         * @return
         */
        @PostMapping("/status/{status}")
        public R<String> status(@PathVariable Integer status, @RequestParam List<Long> ids) {
    
            log.info("status:{}", status);
            log.info("ids:{}", ids);
    
            LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.in(ids != null, Setmeal::getId, ids);
            // 根据数据进行查询
            List<Setmeal> list = setmealService.list(queryWrapper);
    
            for (Setmeal setmeal : list) {
                if (setmeal != null) {
                    setmeal.setStatus(status);
                    setmealService.updateById(setmeal);
                }
            }
            return R.success("批量修改状态成功");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    【算法】顺序表力扣OJ
    C++多态
    《算法导论》16.3 赫夫曼编码(含C++代码)
    Java学习笔记 --- Arrays类
    如何检验谷歌开发者账号注册资料的可靠性?
    HMS Core热门Kit Top问题合集,你要问的,这里全都有!
    chromedriverUnable to obtain driver for chrome using ,selenium找不到chromedriver
    Scrum Master的职责
    使用xlsxwriter简单的将截图插入excel表格中
    【C++模板】typename关键字的用法
  • 原文地址:https://blog.csdn.net/gh_xiaohe/article/details/126593395