DAY03_瑞吉外卖——公共字段自动填充新增分类分类信息分页查询删除分类修改分类

news2025/1/16 1:13:22

目录

  • 1. 公共字段自动填充
    • 1.1 问题分析
    • 1.2 基本功能实现
      • 1.2.1 思路分析
      • 1.2.2 代码实现
      • 1.2.3 功能测试
    • 1.3 功能完善
      • 1.3.1 思路分析
      • 1.3.2 ThreadLocal
      • 1.3.3 操作步骤
      • 1.3.4 代码实现
      • 1.3.5 功能测试
  • 2. 新增分类
    • 2.1 需求分析
    • 2.2 数据模型
    • 2.3 前端页面分析
    • 2.4 代码实现
    • 2.5 功能测试
  • 3. 分类信息分页查询
    • 3.1 需求分析
      • 3.2 前端页面分析
    • 3.3 代码实现
    • 3.4 功能测试
  • 4. 删除分类
    • 4.1 需求分析
    • 4.2 前端页面分析
    • 4.3 代码实现
    • 4.4 功能测试
    • 4.5 功能完善
      • 4.5.1 思路分析
      • 4.5.2 准备工作
      • 4.5.3 代码实现
      • 4.5.4 功能测试
  • 5. 修改分类
    • 5.1 需求分析
    • 5.2 前端页面分析
    • 5.3 代码实现
    • 5.4 功能测试

1. 公共字段自动填充

1.1 问题分析

前面我们已经完成了后台系统的员工管理功能的开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间、修改人等字段。这些字段属于公共字段,也就是也就是在我们的系统中很多表中都会有这些字段,如下:
在这里插入图片描述

而针对于这些字段,我们的赋值方式为:

A. 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。

B. 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。

目前,在我们的项目中处理这些字段都是在每一个业务方法中进行赋值操作,如下:

在这里插入图片描述

在这里插入图片描述

如果都按照上述的操作方式来处理这些公共字段, 需要在每一个业务方法中进行操作, 编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?

答案是可以的,我们使用Mybatis Plus提供的公共字段自动填充功能。

1.2 基本功能实现

1.2.1 思路分析

Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:

字段名赋值时机说明
createTime插入(INSERT)当前时间
updateTime插入(INSERT) , 更新(UPDATE)当前时间
createUser插入(INSERT)当前登录用户ID
updateUser插入(INSERT) , 更新(UPDATE)当前登录用户ID

实现步骤:

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

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

1.2.2 代码实现

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

在员工Employee实体类的公共字段属性上, 加上注解, 指定填充策略。(ps.在资料中提供的实体类,已经添加了该注解,并指定了填充策略)

在这里插入图片描述

FieldFill.INSERT: 插入时填充该属性值

FieldFill.INSERT_UPDATE: 插入/更新时填充该属性值

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

所属包: 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",new Long(1));
        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 功能测试

编写完了元数据对象处理器之后,我们就可以将之前在新增和修改方法中手动赋值的代码删除或注释掉。

在这里插入图片描述

在这里插入图片描述

然后,我们启动项目,在员工管理模块中,测试增加/更新员工信息功能,然后通过debug 或者 直接查询数据库数据变更的形式,看看我们在新增/修改数据时,这些公共字段数据是否能够完成自动填充。

1.3 功能完善

1.3.1 思路分析

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

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

在这里插入图片描述

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

那么我先搞清楚一点,当我们在修改员工信息时, 我们业务的执行流程是什么样子的,如下图:

在这里插入图片描述

客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:

1). LoginCheckFilter的doFilter方法

2). EmployeeController的update方法

3). MyMetaObjectHandler的updateFill方法

我们可以在上述类的方法中加入如下代码(获取当前线程ID,并输出):

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

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

在这里插入图片描述

经过上述的分析之后,发现我们可以使用JDK提供的一个类, 来解决此问题,它是JDK中提供的 ThreadLocal。

1.3.2 ThreadLocal

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

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

ThreadLocal常用方法:

A. public void set(T value) : 设置当前线程的线程局部变量的值

B. public T get() : 返回当前线程所对应的线程局部变量的值

C. public void remove() : 删除当前线程所对应的线程局部变量的值

我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。 如果在后续的操作中, 我们需要在Controller / Service中要使用当前登录用户的ID, 可以直接从ThreadLocal直接获取。

1.3.3 操作步骤

实现步骤:

1). 编写BaseContext工具类,基于ThreadLocal封装的工具类

2). 在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id

3). 在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id

1.3.4 代码实现

1). BaseContext工具类

所属包: 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();
    }
}

2).LoginCheckFilter中存放当前登录用户到ThreadLocal

在doFilter方法中, 判定用户是否登录, 如果用户登录, 在放行之前, 获取HttpSession中的登录用户信息, 调用BaseContext的setCurrentId方法将当前登录用户ID存入ThreadLocal。

Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);

在这里插入图片描述

3). MyMetaObjectHandler中从ThreadLocal中获取

将之前在代码中固定的当前登录用户1, 修改为动态调用BaseContext中的getCurrentId方法获取当前登录用户ID

在这里插入图片描述
在这里插入图片描述

1.3.5 功能测试

完善了元数据对象处理器之后,我们就可以重新启动项目,完成登录操作后, 在员工管理模块中,测试增加/更新员工信息功能, 直接查询数据库数据变更,看看我们在新增/修改数据时,这些公共字段数据是否能够完成自动填充, 并且看看填充的create_user 及 update_user字段值是不是本地登录用户的ID。

2. 新增分类

2.1 需求分析

后台系统中可以管理分类信息,分类包括两种类型,分别是 菜品分类套餐分类 。当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。

在这里插入图片描述

在分类管理中,我们新增分类时, 可以选择新增菜品分类(川菜、湘菜、粤菜…), 也可以选择新增套餐分类(营养早餐、超值午餐…)。 在添加套餐的时候, 输入的排序字段, 控制的是移动端套餐列表的展示顺序。

在这里插入图片描述

2.2 数据模型

新增分类,其实就是将我们新增窗口录入的分类数据,插入到category表,具体表结构如下:

在这里插入图片描述

我们添加的套餐名称,是唯一的,不能够重复的,所以在设计表结构时,已经针对于name字段建立了唯一索引,如下:

在这里插入图片描述

2.3 前端页面分析

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

1). 在页面(backend/page/category/list.html)的新增分类表单中填写数据,点击 “确定” 发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端

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

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

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

在这里插入图片描述

具体请求信息整理如下:

请求说明
请求方式POST
请求路径/category
请求参数json格式 - {“name”:“川菜”,“type”:“1”,“sort”:2}

2.4 代码实现

代码实现的具体步骤如下:

  • 实体类Category(直接从课程资料中导入即可)
  • Mapper接口CategoryMapper
  • 业务层接口CategoryService
  • 业务层实现类CategoryServiceImpl
  • 控制层CategoryController

1). 实体类Category

所属包: 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;
}

2). Mapper接口CategoryMapper

所属包: com.itheima.reggie.mapper

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

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

3). 业务层接口CategoryService

所属包: com.itheima.reggie.service

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

public interface CategoryService extends IService<Category> {
}

4). 业务层实现类CategoryServiceImpl

所属包: 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{
}

5). 控制层CategoryController

import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.service.CategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 分类管理
 */
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
	
    /**
     * 新增分类
     * @param category
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody Category category){
        log.info("category:{}",category);
        categoryService.save(category);
        return R.success("新增分类成功");
    }
}    

2.5 功能测试

新增分类的代码编写完毕之后, 我们需要重新启动项目,进入管理系统访问分类管理, 然后进行新增分类测试,需要将所有情况都覆盖全,例如:

1). 输入的分类名称不存在

2). 输入已存在的分类名称

3). 新增菜品分类

4). 新增套餐分类

3. 分类信息分页查询

3.1 需求分析

系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

在这里插入图片描述

3.2 前端页面分析

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

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

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

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

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

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

页面加载时,就会触发Vue声明周期的钩子方法,然后执行分页查询,发送异步请求到服务端,前端代码如下:

在这里插入图片描述

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

在这里插入图片描述

我们通过浏览器,也可以抓取到分页查询的请求信息, 如下:

在这里插入图片描述

具体的请求信息整理如下:

请求说明
请求方式GET
请求路径/category/page
请求参数?page=1&pageSize=10

3.3 代码实现

在CategoryController中增加分页查询的方法,在方法中传递分页条件进行查询,并且需要对查询到的结果,安排设置的套餐顺序字段sort进行排序。

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

3.4 功能测试

分页查询的代码编写完毕之后, 我们需要重新启动项目,然后登陆系统后台,点击分类管理,查询分类列表是否可以正常展示。测试过程中可以使用浏览器的监控工具查看页面和服务端的数据交互细节。

测试完毕后,大家会发现,我们查询数据库返回的类型为 1 或者 2, 但是实际展示到页面上的却是 “菜品分类” 或 “套餐分类”,这一块是在前端页面中进行处理的,处理代码如下:

在这里插入图片描述

在这里插入图片描述

4. 删除分类

4.1 需求分析

在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。

在这里插入图片描述

4.2 前端页面分析

在前端页面中,点击 “删除” 按钮,就会触发定义的方法,然后往服务端发送异步请求,并传递参数id,执行删除分类操作。

在这里插入图片描述

删除操作的具体执行流程如下:

1). 点击删除,页面发送ajax请求,将参数(id)提交到服务端

2). 服务端Controller接收页面提交的数据并调用Service删除数据

3). Service调用Mapper操作数据库

在这里插入图片描述

从上述的分析中,我们可以得到请求的信息如下:

请求说明
请求方式DELETE
请求路径/category
请求参数?id=1395291114922618881

4.3 代码实现

在CategoryController中增加根据ID删除的方法,在方法中接收页面传递参数id,然后执行删除操作。

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

4.4 功能测试

基本的删除操作代码实现完毕后,重启项目,进行测试。可以通过debug断点调试进行测试,同时结合浏览器监控工具查看请求和响应的具体数据。

4.5 功能完善

4.5.1 思路分析

在上述的测试中,我们看到分类数据是可以正常删除的。但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善。完善后的逻辑为:

  • 根据当前分类的ID,查询该分类下是否存在菜品,如果存在,则提示错误信息
  • 根据当前分类的ID,查询该分类下是否存在套餐,如果存在,则提示错误信息
  • 执行正常的删除分类操作

那么在这里又涉及到我们后面要用到的两张表结构 dish(菜品表) 和 setmeal(套餐表)。具体的表结构,我们目前先了解一下:

在这里插入图片描述

在这里插入图片描述

4.5.2 准备工作

1). 准备菜品(Dish)及套餐(Setmeal)实体类(课程资料中直接拷贝)

所属包: 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;
}
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;
}

2). Mapper接口DishMapper和SetmealMapper

所属包: 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> {
}
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> {
}

3). Service接口DishService和SetmealService

所属包: com.itheima.reggie.service

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

public interface DishService extends IService<Dish> {
}
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Setmeal;

public interface SetmealService extends IService<Setmeal> {
}

4). Service实现类DishServiceImpl和SetmealServiceImpl

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 {
}
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.5.3 代码实现

1). 创建自定义异常

在业务逻辑操作过程中,如果遇到一些业务参数、操作异常的情况下,我们直接抛出此异常。

所在包: com.itheima.reggie.common

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

2). 在CategoryService中扩展remove方法

public interface CategoryService extends IService<Category> {
	//根据ID删除分类
    public void remove(Long id);
}

3). 在CategoryServiceImpl中实现remove方法

@Autowired
private DishService dishService;
@Autowired
private SetmealService setmealService;

/**
 * 根据id删除分类,删除之前需要进行判断
 * @param id
 */
@Override
public void remove(Long id) {
    //添加查询条件,根据分类id进行查询菜品数据
    LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
    dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
    int count1 = dishService.count(dishLambdaQueryWrapper);
    //如果已经关联,抛出一个业务异常
    if(count1 > 0){
        throw new CustomException("当前分类下关联了菜品,不能删除");//已经关联菜品,抛出一个业务异常
    }

    //查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
    LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
    setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
    int count2 = setmealService.count(setmealLambdaQueryWrapper);
    if(count2 > 0){
        throw new CustomException("当前分类下关联了套餐,不能删除");//已经关联套餐,抛出一个业务异常
    }

    //正常删除分类
    super.removeById(id);
}

那么在上述的业务逻辑中,当分类下关联的有菜品或者套餐时,我们在业务代码中抛出了自定义异常,并且在异常中封装了错误提示信息,那这个错误提示信息如何提示给页面呢?

异常抛出之后,会被异常处理器捕获,我们只需要在异常处理器中捕获这一类的异常,然后给页面返回对应的提示信息即可。

4). 在GlobalExceptionHandler中处理自定义异常

在全局异常处理器中增加方法,用于捕获我们自定义的异常 CustomException

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

5). 改造CategoryController的delete方法

注释掉原有的代码,在delete方法中直接调用categoryService中我们自定义的remove方法。

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

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

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

4.5.4 功能测试

功能完善的代码编写完毕之后, 我们需要重新启动项目,进入管理系统访问分类管理, 然后进行删除分类的测试,需要将所有情况都覆盖全,例如:

1). 新增一个分类,然后再直接删除,检查是否可以正常删除成功。(新增的分类时没有关联菜品和套餐的)

2). 在数据库表(dish/setmeal)中,找到一个与菜品或套餐关联的分类,然后在页面中执行删除操作,检查是否可以正常的提示出对应的错误信息。

在这里插入图片描述

5. 修改分类

5.1 需求分析

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

在这里插入图片描述

5.2 前端页面分析

这里面大家会发现,修改功能我们还没有实现,但是当点击 “修改” 按钮的时候,我们并没有开发根据ID查询数据,进行页面回显的功能,但是页面的分类数据确实回显回来了。这是怎么做到的呢,我们来解析一下前端的代码实现(前端代码已经实现):

在这里插入图片描述

那么回显这一步的操作前端已经实现,我们就只需要开发一个方法,修改操作的方法即可。我们可以通过浏览器来抓取一下修改操作的请求信息,如图:

在这里插入图片描述

具体的请求信息,整理如下:

请求说明
请求方式PUT
请求路径/category
请求参数{id: “1399923597874081794”, name: “超值午餐”, sort: 0}

5.3 代码实现

html页面中相关的代码都已经提供好了,我们已经分析了请求的信息,接下来就可以来创建服务端的CategoryController方法update方法。

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

5.4 功能测试

按照前面分析的操作流程进行测试,查看数据是否正常修改即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1000498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法】二分查找算法——leetcode二分查找、搜索插入位置

文章目录 二分查找704. 二分查找35. 搜索插入位置 二分查找 二分查找算法是一种在有序数组中查找特定元素的搜索算法。算法的工作原理是&#xff0c;通过比较数组中间元素和目标值&#xff0c;如果目标值等于中间元素&#xff0c;那么查找结束。如果目标值小于或大于中间元素&a…

单元测试界的高富帅,Pytest框架 (三) 用例标记和测试执行篇

pytest用例标记和测试执行篇 上一篇文章入门篇咱们介绍了pytest的前后置方法和fixture机制&#xff0c;这个章节主要给大家介绍pytest中的标记机制和用例执行的方法。pytest可以通过标记将数据传入于测试函数中&#xff0c;也可以通过标记中对执行的用例做筛选&#xff0c;接下…

NTC 温度采样 二分查表及公式法

NTC 温度采样&#xff1a; 本文记录对NTC 温度采样&#xff0c;分别采用二分查表法及公式法进行描述 资源下载链接&#xff1a;Excel 生成数组表 https://download.csdn.net/download/qq_41359157/88326839?spm1001.2014.3001.5503 NTC参数&#xff1a; NTC采样电路&#xf…

2023 年 Vue 最流行的动画库

数字世界以短暂的注意力和激烈的竞争为主导&#xff0c;因此必须立即将受众的注意力吸引到您的网站上。使用 Vue 动画库&#xff0c;您可以毫不费力地实现这一目标。据报道&#xff0c;VueJs 是 JavaScript 类别中第 7 大最受欢迎的&#xff0c;来自世界各地的开发人员使用它来…

arthas基本应用

下载 arthas curl https://arthas.aliyun.com/arthas-boot.jar启动 arthas&#xff08;启动之前确保有一个 java进程服务&#xff09; java -jar arthas-boot.jar输入3&#xff0c;再输入回车/enter。Arthas 会 attach 到目标进程上&#xff0c;并输出志&#xff1a; 输入das…

如何抢占3020亿美元市场先机?送你一份指南

印度电商市场规模正在快速增长。预计到2023年&#xff0c;印度电商市场规模将达到2000亿美元。其中&#xff0c;B2C电商市场规模将占据主导地位&#xff0c;预计将增长至1000亿美元。 此外&#xff0c;印度政府也在积极推动数字化发展&#xff0c;为电商企业提供更多机会。政府…

Python语言:算术运算符知识点讲解

前言&#xff1a;学了几天python&#xff0c;可把我折磨坏了。为什么呢&#xff0c;就是python语言都特别爱空格&#xff0c;我有时候就忘了&#xff0c;就报错了啦。就比如这个&#xff1a;a 8&#xff0c;等于号前面和后面都需要空格&#xff0c;有点不习惯&#xff0c;在慢…

Spring中的事务与事务传播机制

事务 在学习MySQL时我们学习过事务&#xff0c;而到了Spring的学习时&#xff0c;同样会学习到事务&#xff0c;两个东西的事务的定义都是一样的&#xff1a;将一组操作封装成一个执行单元&#xff0c;要么全部成功&#xff0c;要么全部失败 在Spring中使用事务有两种方式 一…

GitHub星标超70K,阿里大佬的架构总结“分布式全解”笔记霸榜

分布式架构与微服务平台是当今IT界的关键技术&#xff0c;也是资深软件工程师和系统架构师必须掌握的核心技术。 因此小编为各位粉丝朋友带来这份阿里大佬的分布式笔记&#xff1a;从传统分布式架构迁移到基于容器技术的微服务架构为主线&#xff0c;全面、透彻地介绍了与分布…

十七、Webpack搭建本地服务器

一、为什么要搭建本地服务器&#xff1f; 目前我们开发的代码&#xff0c;为了运行需要有两个操作&#xff1a; 操作一&#xff1a;npm run build&#xff0c;编译相关的代码&#xff1b;操作二&#xff1a;通过live server或者直接通过浏览器&#xff0c;打开index.html代码…

【业务功能篇106】 微服务-springcloud-springboot-电商订单模块--秒杀服务-定时任务【下篇】

四、秒杀活动 1.秒杀活动关注点 秒杀活动的最大特点就是高并发而且是短时间内的高并发&#xff0c;那么对我们的服务要求就非常高&#xff0c;针对这种情况所产生的共性问题&#xff0c;对应的解决方案&#xff1a; 2. 秒杀服务前端 当我们点击 秒杀抢购按钮后&#xff0c;对应…

【C#-1】C#调用matlab生成的dll库

matlab打包dll 1、matlab示例程序&#xff1a; function untitled4(x)z peaks(x);figuresurf(z) end 2、输入deploytool打包matlab程序&#xff0c;具体如下&#xff1a; 3、拷贝 打包成功后&#xff0c;将生成for_redistribution_files_only文件夹中的dll文件拷贝到C#程序…

【设计模式】三、概述分类+单例模式

文章目录 概述设计模式类型 单例模式饿汉式&#xff08;静态常量&#xff09;饿汉式&#xff08;静态代码块&#xff09;懒汉式(线程不安全)懒汉式(线程安全&#xff0c;同步方法)懒汉式(线程安全&#xff0c;同步代码块)双重检查静态内部类枚举单例模式在 JDK 应用的源码分析 …

Llama2-Chinese项目:1-项目介绍和模型推理

Atom-7B与Llama2间的关系&#xff1a;Atom-7B是基于Llama2进行中文预训练的开源大模型。为什么叫原子呢&#xff1f;因为原子生万物&#xff0c;Llama中文社区希望原子大模型未来可以成为构建AI世界的基础单位。目前社区发布了6个模型&#xff0c;如下所示&#xff1a; FlagAl…

无线振动传感器革新:设备维护的新范式

随着工业界的不断发展和技术的进步&#xff0c;设备维护的方法也在不断演变。过去&#xff0c;维护通常是基于固定的时间表和例行的检查进行的&#xff0c;这种方法往往会导致资源的浪费和不必要的停机时间。然而&#xff0c;现在&#xff0c;随着无线振动传感器的革新&#xf…

【C++】内联函数 ⑤ ( 内联函数总结 | 内联函数代码示例 )

文章目录 一、内联函数总结二、内联函数代码示例1、代码示例 - 普通函数执行分析2、代码示例 - 内联函数执行分析3、代码示例 - 宏代码片段执行分析 一、内联函数总结 回顾下 之前的博客中 介绍的 内联函数 : 内联函数编译 : C 编译器 编译 内联函数 时 , 会直接 将 内联函数 …

前端开发纷繁复杂,是否有更高效的开发方式?

一、前言 此前&#xff0c;我曾跟大家聊到&#xff0c;低代码编程&#xff0c;在现阶段互联网业务疯狂增长的带动之下&#xff0c;被赋予了全新的使命和义务&#xff0c;即帮助开发者在前期以较低成本的方式&#xff0c;快速构建一个可投入市场的应用。 那么&#xff0c;有没有…

腾讯云轻量2核4G5M服务器_CPU内存_流量_带宽_系统盘

腾讯云轻量2核4G5M服务器&#xff1a;CPU内存流量带宽系统盘性能测评&#xff1a;轻量应用服务器2核4G5M带宽&#xff0c;免费500GB月流量&#xff0c;60GB系统盘SSD盘&#xff0c;5M带宽下载速度可达640KB/秒&#xff0c;流量超额按照0.8元每GB支付流量费&#xff0c;轻量2核4…

都2023年了,别再用ifconfig啦! 试试这个吧!

下午好&#xff0c;我的网工朋友。 ifconfig这玩意儿&#xff0c;还有多少人在用&#xff1f;举个手看看 总在说ifconfig即将被淘汰&#xff0c;但其实很多网工还在用吧。 比如说组合使用诸如ifconfig、route、arp和netstat等命令行工具&#xff08;来源于安装包net-tools&a…

调查称全球多所顶尖高校网站存在网络攻击风险

Cyber News的一项调查研究显示&#xff0c;全球多所顶尖高校的网站未能及时更新安全补丁&#xff0c;存在敏感信息泄露&#xff0c;甚至被攻击者全面接管的风险。 Cyber​​ News 研究团队详细调查了 20 个每月有数百万访问量的高校网站&#xff0c;其中至少有6个是位于全球Top…