学习【瑞吉外卖③】SpringBoot单体项目_分类管理业务开发


  • 视频链接:黑马程序员【Java 项目实战《瑞吉外卖》,轻松掌握 SpringBoot + MybatisPlus 开发核心技术】
  • 资料链接:2022 最新版 Java学习 路线图>第 5 阶段一 企业级项目实战>7.黑马程序员 瑞吉外卖平台实战开发(提取码:dor4)

  • 本文章发布在CSDN上,一是方便博主自己线上阅览,二是巩固自己所学知识。
  • 博客内容主要参考上述视频和资料。视频中出现的 PPT 的内容也写在博客里了(官方提供的资料并没有 PPT 的内容)。

  • 若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。

文章目录

  • 0.总目录
  • 1.公共字段自动填充
    • 1.1.问题分析
    • 1.2.代码实现
      • 1.2.1.思路整理
      • 1.2.2.代码开发
    • 1.3.功能完善
      • 1.3.1.思路整理
      • 1.3.2.ThreadLocal
      • 1.3.3.代码开发
  • 2.新增分类
    • 2.1.需求分析
    • 2.2.数据模型
    • 2.3.代码开发
      • 2.3.1.思路整理
      • 2.3.2.创建基本结构
      • 2.3.3.新增分类方法
  • 3.分类信息分页查询
    • 3.1.需求分析
    • 3.2.代码开发
      • 3.2.1.思路整理
      • 3.2.2.代码实现
  • 4.删除分类
    • 4.1.需求分析
    • 4.2.代码开发
      • 4.2.1.思路整理
      • 4.2.2.代码实现
    • 4.3.代码完善
      • 4.3.1.思路整理
      • 4.3.2.创建基本结构
      • 4.3.3.异常处理和删除方法
    • 4.4.前端js文件的修改
  • 5.修改分类
    • 5.1.需求分析
    • 5.2.代码实现


上一篇:学习【瑞吉外卖②】SpringBoot单体项目:https://blog.csdn.net/yanzhaohanwei/article/details/124993760


0.总目录


  • 学习【瑞吉外卖①】SpringBoot单体项目
    • 软件开发流程、瑞吉外卖项目介绍、环境搭建、后台登录功能、后台退出功能
  • 学习【瑞吉外卖②】SpringBoot单体项目后台
    • 完善登录功能、 新增员工功能、员工信息分页查询功能、启用 / 禁用员工账号功能、编辑员工信息功能
  • 学习【瑞吉外卖③】SpringBoot单体项目后台
    • 公共字段自动填充功能、新增分类功能、分类信息分页查询功能、删除分类功能、修改分类功能
  • 学习【瑞吉外卖④】SpringBoot单体项目后台
    • 文件上传下载功能、新增菜品功能、菜品信息分页查询功能、修改菜品功能
    • 其他功能:删除菜品(单个 / 批量)功能、停售 / 启售菜品(单个 / 批量)功能。
  • 学习【瑞吉外卖⑤】SpringBoot单体项目后台
    • 新增套餐功能、套餐分页查询功能、删除套餐功能(单个 / 批量)
    • 其他功能:停售 / 启售(批量 / 单个)套餐功能、修改套餐功能
  • 学习【瑞吉外卖⑥】SpringBoot单体项目移动端
    • 手机验证码登录功能(短信发送、手机验证码登录)
  • 学习【瑞吉外卖⑦】SpringBoot单体项目移动端
    • 用户地址簿功能:增删改查功能,设置、查看默认地址功能。
    • 菜品展示功能(套餐展示功能也包含在其中)
    • 购物车功能:购物车中增加/减少 套餐/菜品的功能,菜品/套餐 在购物车中的展示功能,购物车中 菜品/套餐 清空的功能
    • 订单功能:用户下单功能、用户查看订单功能、用户再来一单功能。
    • 用户登出功能
  • 学习【瑞吉外卖⑧】SpringBoot单体项目后台
    • 订单展示功能、订单状态修改功能

1.公共字段自动填充


1.1.问题分析


前面我们已经完成了后台系统的员工管理功能开发。

在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改人等字段。

这些字段属于公共字段,也就是很多表中都有这些字段。

具体如下:

列名 数据类型
create_time datetime
update_time datetime
create_user bigint
update_user bigint

com/itheima/reggie/controller/EmployeeController.java

@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("新增员工成功");
}

那么能不能对于这些公共字段在某个地方统一处理,来简化开发呢?

Mybatis-Plus 提供的 公共字段 自动填充功能 就可以办到这一点。


1.2.代码实现


1.2.1.思路整理


MyBatis-Plus 公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值。

使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。


  • 实现步骤
    • 在实体类的属性上加入 @TableField 注解,指定自动填充的策略
    • 按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现 MetaObjectHandle 接口

1.2.2.代码开发


1.在实体类的属性上加入 @TableField 注解,指定自动填充的策略

com/itheima/reggie/entity/Employee.java

@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.按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现 MetaObjectHandle 接口

com/itheima/reggie/common/MyMetaObjecthandler.java

@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
    //插入时自动填充
    @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));
        metaObject.setValue("updateUser", new Long(1));
    }
    //更新时自动填充
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段自动填充[update]...");
        log.info(metaObject.toString());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("updateUser", new Long(1));
    }
}

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

同时我们需要注释掉一些代码

com/itheima/reggie/controller/EmployeeController.java

/**
 * 新增员工
 *
 * @param employee
 * @return
 */
@PostMapping
public R<String> save(HttpServletRequest request, @RequestBody Employee employee) {
	... ...
	
    //employee.setCreateTime(LocalDateTime.now());
    //employee.setUpdateTime(LocalDateTime.now());
    //获得当前登录用户的 id
    //Long empId = (Long) request.getSession().getAttribute("employee");
    //employee.setCreateUser(empId);
    //employee.setUpdateUser(empId);
    
	... ...
}

1.3.功能完善


1.3.1.思路整理


前面我们已经完成了公共字段自动填充功能的代码开发。

但是我们在自动填充 createUserupdateUser 时设置的用户 id 是固定值,现在我们需要改造成动态获取当前登录用户的 id

在之前的操作中,用户登录成功后,我们将用户 id 存入了 HttpSession 中。

那么我们可以从 HttpSession 中获取吗?答案是不可以的。


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

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

在学习 ThreadLocal 之前,我们需先确认一事:即客户端发送的每次 http 请求,对应的在服务端都会分配一个新的线程来处理。

在处理过程中涉及到下面类中的方法都属于相同的一个线程

  1. LoginCheckFilterdoFilter 方法。
  2. EmployeeContrallerupdate 方法
  3. MyMetaObjectHandlerupdateFill 方法

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

long id = Thread.currentThread().getId();
log.info("线程 id 是:{}", id);

执行编辑员工功能进行验证,通过观察控制台输出可以发现,一次请求对应的 id 是相同的。

2022-06-03 13:33:05.948  INFO 19284 --- [nio-8080-exec-6] c.i.raggie.filter.LoginCheckFilter       : 拦截到请求:/employee
2022-06-03 13:33:05.948  INFO 19284 --- [nio-8080-exec-6] c.i.raggie.filter.LoginCheckFilter       : 用户已登录,用户 id 为:1
2022-06-03 13:33:05.948  INFO 19284 --- [nio-8080-exec-6] c.i.raggie.filter.LoginCheckFilter       : 线程 id:35
2022-06-03 13:40:28.951  INFO 19284 --- [nio-8080-exec-6] c.i.r.controller.EmployeeController      : 线程 id:35
... ...
2022-06-03 13:33:05.953  INFO 19284 --- [nio-8080-exec-6] c.i.raggie.common.MyMetaObjecthandler    : 线程id为:35

1.3.2.ThreadLocal


  • 什么是 ThreadLocal

ThreadLocal 并不是一个 Thread,而是 Thread 的局部变量。

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

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

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

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


1.3.3.代码开发


com/itheima/reggie/common/BaseContext.java

package com.itheima.reggie.common;

/**
 * 基于 ThreadLocal 封装工具类,用户保存和获取当前登录用户 id
 */
public class BaseContext {
    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();
    }
}

com/itheima/reggie/filter/LoginCheckFilter.java

//4.判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee") != null) {
    log.info("用户已登录,用户id为:{}", request.getSession().getAttribute("employee"));
    
    Long empId = (Long) request.getSession().getAttribute("employee");
    BaseContext.setCurrentId(empId);

    filterChain.doFilter(request, response);
    return;
}

com/itheima/reggie/common/MyMetaObjecthandler.java

package com.itheima.reggie.common;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * 自定义元数据对象处理器
 */
@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());
        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());
    }
}

2.新增分类


2.1.需求分析


后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。

当我们在后台系统中添加菜品时需要选择一个菜品分类。

当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。

学习【瑞吉外卖③】SpringBoot单体项目_分类管理业务开发_第1张图片

我们也可以再后台系统的分类管理页面分别添加菜品分类和套餐分类。

学习【瑞吉外卖③】SpringBoot单体项目_分类管理业务开发_第2张图片


2.2.数据模型


新增分类,实际上就是将我们新增窗口录入的分类数据插入到 category 表中。

学习【瑞吉外卖③】SpringBoot单体项目_分类管理业务开发_第3张图片

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

学习【瑞吉外卖③】SpringBoot单体项目_分类管理业务开发_第4张图片


2.3.代码开发


2.3.1.思路整理


  • 在开发业务功能前,先将需要用到的类和接口基本结构创建好。
    • 实体类 Category
    • Mapper 接口 CategoryMapper
    • 业务层接口 CategoryService
    • 业务层实现类 CategoryServicelmpl
    • 控制层 CategoryController

  • 我们在这里也需要梳理一下整个程序的执行过程:
    • 页面(backend/page/category/list.html)发送 ajax 请求,将新增分类窗口输入的数据以 json 形式提交到服务端。
    • 服务端 Controller 接收页面提交的数据并调用 Service 将数据进行保存。
    • Service 调用 Mapper 操作数据库,保存数据。

可以看到新增菜品分类和新增套餐分类请求的服务端地址和提交的 json 数据结构相同,所以服务端只需要提供一个方法统一处理即可。

学习【瑞吉外卖③】SpringBoot单体项目_分类管理业务开发_第5张图片


2.3.2.创建基本结构


  1. 实体类 Category

com/itheima/reggie/entity/Category.java

package com.itheima.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 分类
 */
@Data
public class Category implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;
    
    //类型 1 菜品分类 2 套餐分类
    private Integer type;

    //分类名称
    private String name;
    
    //顺序
    private Integer sort;
    
    //创建时间
    @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;
}

  1. Mapper 接口 CategoryMapper

com/itheima/reggie/mapper/CategoryMapper.java

package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.raggie.entity.Category;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CategoryMapper extends BaseMapper<Category> { }

  1. 业务层接口 CategoryService

com/itheima/reggie/service/CategoryService.java

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.raggie.entity.Category;

public interface CategoryService extends IService<Category> { }

  1. 业务层实现类 CategoryServicelmpl

com/itheima/reggie/service/impl/CategoryServiceImpl.java

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.mapper.CategoryMapper;
import com.itheima.reggie.service.CategoryService;
import org.springframework.stereotype.Service;

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService { }

  1. 控制层 CategoryController

com/itheima/reggie/controller/CategoryController.java

package com.itheima.reggie.controller;

import com.itheima.reggie.service.CategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 分类管理
 */
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
}

2.3.3.新增分类方法


com/itheima/reggie/controller/CategoryController.java

/**
 * 新增分类
 *
 * @param category
 * @return
 */
@PostMapping
public R<String> save(@RequestBody Category category) {
    log.info("category:{}", category);
    categoryService.save(category);
    return R.success("新增分类成功");
}

3.分类信息分页查询


3.1.需求分析


当系统中的分类很多时,若在一个页面中全部展示出来,就会显得比较乱,不便于查看。

所以一般的系统中都会以分页的方式来展示列表数据。


3.2.代码开发


3.2.1.思路整理


在开发代码之前,需要梳理一下整个程序的执行过程

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

学习【瑞吉外卖③】SpringBoot单体项目_分类管理业务开发_第6张图片


3.2.2.代码实现


com/itheima/reggie/controller/CategoryController.java

/**
 * 分页查询
 *
 * @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);
}

4.删除分类


4.1.需求分析


在分类管理列表页面,可以对某个分类进行删除操作。

需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。


4.2.代码开发


4.2.1.思路整理


在开发代码之前,需要梳理一下整个程序的执行过程。

  1. 页面发送 ajax 请求,将参数(id)提交到服务端
  2. 服务端 Controller 接收页面提交的数据并调用 Service 删除数据
  3. Service 调用 Mapper 操作数据库

4.2.2.代码实现


/**
 * 根据 id 删除分类
 *
 * @param id
 * @return
 */
@DeleteMapping
public R<String> delete(Long id) {
    log.info("删除分类,id为:{}", id);
    categoryService.removeById(id);
    return R.success("分类信息删除成功");
}

4.3.代码完善


4.3.1.思路整理


前面我们已经实现了根据 id 删除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐。

所以我们需要进行功能完善。


要完善分类删除功能,需要先准备基础的类和接口:

  1. 实体类 DishSetmeal
  2. Mapper 接口 DishMapperSetmealMapper
  3. Service 接口和 DishServiceSetemealService
  4. Service 实现类 DishServiceImplSetmealServiceImpl

前面我们在 Contorller 层中使用了由 MyBatisPlus 中提供了 removeById() 来实现删除方法。

但是现在我们需要在进行删除操作前,判断删除的分类是否关联了菜品或者套餐,故要重写该方法。

  • CategoryService 添加 remove 方法
  • 定义异常类 CustomException
  • 在全局异常处理器 GlobalExceptionHandler 类中添加异常处理方法
  • CategoryServicelmpl 实现 remove 方法

4.3.2.创建基本结构


  1. 实体类

com/itheima/reggie/entity/Dish.java

package com.itheima.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 菜品
 */
@Data
public class Dish implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    //菜品名称
    private String name;

    //菜品分类id
    private Long categoryId;

    //菜品价格
    private BigDecimal price;

    //商品码
    private String code;

    //图片
    private String image;

    //描述信息
    private String description;

    //0 停售 1 起售
    private Integer status;

    //顺序
    private Integer sort;

    @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;
}

com/itheima/reggie/entity/Setmeal.java

package com.itheima.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 套餐
 */
@Data
public class Setmeal implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    //分类 id
    private Long categoryId;

    //套餐名称
    private String name;

    //套餐价格
    private BigDecimal price;

    //状态 0:停用 1:启用
    private Integer status;

    //编码
    private String code;

    //描述信息
    private String description;

    //图片
    private String image;

    @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;
}

  1. Mapper 接口

com/itheima/reggie/mapper/DishMapper.java

package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Dish;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface DishMapper extends BaseMapper<Dish> { }

com/itheima/reggie/mapper/SetmealMapper.java

package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Setmeal;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SetmealMapper extends BaseMapper<Setmeal> { }

  1. Service 接口

com/itheima/reggie/service/DishService.java

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Dish;

public interface DishService extends IService<Dish> { }

com/itheima/reggie/service/SetmealService.java

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Setmeal;

public interface SetmealService extends IService<Setmeal> { }

  1. Service 实现类

com/itheima/reggie/service/impl/DishServiceImpl.java

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.mapper.DishMapper;
import com.itheima.reggie.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService { }

com/itheima/reggie/service/impl/SetmealServiceImpl.java

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.mapper.SetmealMapper;
import com.itheima.reggie.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService { }

4.3.3.异常处理和删除方法


前面我们使用了由 MyBatisPlus 中提供了 removeById() 方法。

但是现在我们需要在进行删除操作前,判断检查删除的分类是否关联了菜品或者套餐,故要重写该方法。


  • service 中添加方法

com/itheima/reggie/service/CategoryService.java

public void remove(Long id);

之后我们需要在 service 的实现类中实现 remove() 方法的具体逻辑代码。

该方法是根据 id 删除分类的。

在此编写逻辑代码之前,我们需要先编写异常处理的代码来处理分类关联了菜品(或套餐)时的情况。


  • 自定义业务异常类

com/itheima/reggie/common/CustomException.java

package com.itheima.reggie.common;

/**
 * 自定义业务异常类
 */
public class CustomException extends RuntimeException {
    public CustomException(String message) {
        super(message);
    }
}

  • 在全局处理中加入异常处理方法

com/itheima/reggie/common/GlobalExceptionHandler.java

/**
 * 异常处理方法
 *
 * @return
 */
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex) {
    log.error(ex.getMessage());

    return R.error(ex.getMessage());
}

  • service 实现类中编写具体逻辑代码

com/itheima/reggie/service/impl/CategoryServiceImpl.java

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.common.CustomException;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.mapper.CategoryMapper;
import com.itheima.reggie.service.CategoryService;
import com.itheima.reggie.service.DishService;
import com.itheima.reggie.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@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);
    }
}

  • 最后我们需要在 Controller 中调用我们刚才写的 remove 方法

com/itheima/reggie/controller/CategoryController.java

/**
 * 根据 id 删除分类
 *
 * @param id
 * @return
 */
@DeleteMapping
public R<String> delete(Long id) {
    log.info("删除分类,id为:{}", id);

    //categoryService.removeById(id);
    categoryService.remove(id);

    return R.success("分类信息删除成功");
}

实际上就是注释掉 之前写的categoryService.removeById(id);,再调用 categoryService.remove(id);


4.4.前端js文件的修改


这里补充一下,官方提供的资料是有问题的。

或许就是官方故意的,等着各位发现问题,并解决问题。

resources 目录下的 backend/page/category/list.html 中的 deleteHandle 传递的是 id

//删除
deleteHandle(id) {
    this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
        'confirmButtonText': '确定',
        'cancelButtonText': '取消',
        'type': 'warning'
    }).then(() => {
        deleCategory(id).then(res => {
            if (res.code === 1) {
                this.$message.success('删除成功!')
                this.handleQuery()
            } else {
                this.$message.error(res.msg || '操作失败')
            }
        }).catch(err => {
            this.$message.error('请求出错了:' + err)
        })
    })
},

为保持一致,resources 目录下的 backend/api/category.js 中的 ids 也应该改为 id

// 删除当前列的接口
const deleCategory = (id) => {
  return $axios({
    url: '/category',
    method: 'delete',
    params: { id }
  })
}

5.修改分类


5.1.需求分析


在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作。

学习【瑞吉外卖③】SpringBoot单体项目_分类管理业务开发_第7张图片


5.2.代码实现


com/itheima/reggie/controller/CategoryController.java

/**
 * 根据 id 修改分类信息
 *
 * @param category
 * @return
 */
@PutMapping
public R<String> update(@RequestBody Category category) {
    log.info("修改分类信息:{}", category);

    categoryService.updateById(category);

    return R.success("修改分类信息成功");
}

下一篇:学习【瑞吉外卖④】SpringBoot单体项目_菜品管理业务开发:https://blog.csdn.net/yanzhaohanwei/article/details/125138771


你可能感兴趣的:(Java,项目,#,瑞吉外卖,spring,boot,学习,java)