苍穹外卖-员工管理、菜品分类管理

目录

1. 新增员工

1.1 需求分析和设计

1.1.1 产品原型

1.1.2 接口设计

1.1.3 表设计

1.2 代码开发

1.2.1 设计DTO类

1.2.2 Controller层

1.2.3 Service层接口

1.2.4 Service层实现类

1.2.5 Mapper层

1.3 功能测试

1.3.1 接口文档测试

1.4 代码完善

1.4.1 问题一

1.4.2 问题二

1.4.3 ThreadLocal

1.5 代码提交

2. 员工分页查询

2.1 需求分析和设计

2.1.1 产品原型

2.1.2 接口设计

2.2 代码开发

2.2.1 设计DTO类

2.2.2 封装PageResult

2.2.3 Controller层

2.2.4 Service层接口

2.2.5 Service层实现类

2.2.6 Mapper层

2.3 功能测试

2.3.1 接口文档测试

2.3.2 前后端联调测试

2.4 代码完善

3. 启用禁用员工账号

3.1 需求分析与设计

3.1.1 产品原型

3.1.2 接口设计

3.2 代码开发

3.2.1 Controller层

3.2.2 Service层接口

3.2.3 Service层实现类

3.2.4 Mapper层

3.3 功能测试

3.3.1 前后端联调测试

4. 编辑员工

4.1 需求分析与设计

4.1.1 产品原型

4.1.2 接口设计

4.2 代码开发

4.2.1 回显员工信息功能

4.3 功能测试

4.3.1 接口文档测试

4.3.2 前后端联调测试

4.4 代码提交

5. 导入分类模块功能代码

5.1 需求分析与设计

5.1.1 产品原型

5.1.2 接口设计

5.1.3 表设计

5.2 代码

5.2.1 Mapper层

5.2.2 Service层

5.2.3 Controller层

5.3 功能测试


  • 新增员工

  • 员工分页查询

  • 启用禁用员工账号

  • 编辑员工

  • 导入分类模块功能代码

功能实现:员工管理、菜品分类管理

员工管理效果:

菜品分类管理效果:

1. 新增员工

1.1 需求分析和设计

1.1.1 产品原型

一般在做需求分析时,往往都是对照着产品原型进行分析,因为产品原型比较直观,便于我们理解业务。

后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。

新增员工原型:

当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

注意事项:

  1. 账号必须是唯一的

  2. 手机号为合法的11位手机号码

  3. 身份证号为合法的18位身份证号码

  4. 密码默认为123456

1.1.2 接口设计

明确新增员工接口的请求路径、请求方式、请求参数、返回数据

本项目约定:

  • 管理端发出的请求,统一使用/admin作为前缀。

  • 用户端发出的请求,统一使用/user作为前缀。

1.1.3 表设计

新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。

employee表结构:

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)姓名
usernamevarchar(32)用户名唯一
passwordvarchar(64)密码
phonevarchar(11)手机号
sexvarchar(2)性别
id_numbervarchar(18)身份证号
statusInt账号状态1正常 0锁定
create_timeDatetime创建时间
update_timedatetime最后修改时间
create_userbigint创建人id
update_userbigint最后修改人id

其中,employee表中的status字段已经设置了默认值1,表示状态正常。

1.2 代码开发

1.2.1 设计DTO类

根据新增员工接口设计对应的DTO

DTO类用来储存前端传递的参数列表(也可以不单独作为一个类,用employee即可,但是这样做更规范,因为employee中有多余的属性值,但当相差的属性值不多时,可以直接采用原始的实体类)

前端传递参数列表与实体类对比:

注意:当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据

由于上述传入参数和实体类有较大差别,所以自定义DTO类。

进入sky-pojo模块,在com.sky.dto包下,已定义EmployeeDTO

package com.sky.dto;

import lombok.Data;

import java.io.Serializable;

@Data
public class EmployeeDTO implements Serializable {

    private Long id;

    private String username;

    private String name;

    private String phone;

    private String sex;

    private String idNumber;

}

1.2.2 Controller层

EmployeeController中创建新增员工方法

进入到sky-server模块中,在com.sky.controller.admin包下,在EmployeeController中创建新增员工方法,接收前端提交的参数。

 /**
     * 新增员工
     * @param dto
     * @return
     */
    @ApiOperation("新增员工")
    @PostMapping
    public Result addEmp(@RequestBody EmployeeDTO dto){
        log.info("新增员工:{}",dto);
        employeeService.addEmp(dto);
        return Result.success();
    }

注意新增员工的参数前面要加@RequestBody注解:
@RequestBody的作用
@RequestBody是Spring MVC中的一个注解,主要用于:
指示参数来源:告诉Spring MVC这个参数应该从HTTP请求的正文(body)中获取,而不是从URL参数或表单数据中获取。
触发数据绑定:将HTTP请求正文中的JSON数据自动反序列化并绑定到Java对象上。
内容类型处理:配合Content-Type: application/json请求头,处理JSON格式的请求数据。

@RequestBody注解使得Spring MVC能够:
读取请求正文中的JSON数据
将JSON数据自动转换为EmployeeDTO对象
将转换后的对象赋值给dto参数

注:Result类定义了后端统一返回结果格式。

进入sky-common模块,在com.sky.result包下定义了Result.java

package com.sky.result;

import lombok.Data;

import java.io.Serializable;

/**
 * 后端统一返回结果
 * @param <T>
 */
@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}

1.2.3 Service层接口

在EmployeeService接口中声明新增员工方法

进入到sky-server模块中,com.sky.server.EmployeeService

package com.sky.service;

import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.entity.Employee;

public interface EmployeeService {

    /**
     * 员工登录
     * @param employeeLoginDTO
     * @return
     */
    Employee login(EmployeeLoginDTO employeeLoginDTO);

    /**
     * 新增员工
     * @param dto
     */
    void addEmp(EmployeeDTO dto);
}

1.2.4 Service层实现类

在EmployeeServiceImpl中实现新增员工方法

com.sky.server.impl.EmployeeServiceImpl中创建方法

/**
     * 新增员工
     * @param dto
     */
    @Override
    public void addEmp(EmployeeDTO dto) {
        Employee employee = new Employee();
        //属性拷贝
        BeanUtils.copyProperties(dto,employee);//将dto中的属性值复制给employee
        //1.补充缺失的属性值
        //补充密码字段,需要进行MD5加密后才能存储到数据库
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));    
        //设置账号的状态,默认正常状态1表示正常 0表示锁定
        employee.setStatus(StatusConstant.ENABLE);
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        //TODO:
        employee.setCreateUser(10L);
        employee.setUpdateUser(10L);
        //2.调用mapper的新增方法,将员工对象存入employee表中
        employeeMapper.insert(employee);

    }

TODO的作用:

TODO 是一种常见的代码注释标记,用于标识代码中需要完成或改进的部分。它的主要作用包括:
1.标记未完成的功能
2.提醒开发者需要处理的事项
3.便于团队协作中的任务分配和跟踪
4.作为代码审查时的检查点

在sky-common模块com.sky.constants包下已定义StatusConstant.java

package com.sky.constant;

/**
 * 状态常量,启用或者禁用
 */
public class StatusConstant {

    //启用
    public static final Integer ENABLE = 1;

    //禁用
    public static final Integer DISABLE = 0;
}

1.2.5 Mapper层

在EmployeeMapper中声明insert方法

com.sky.EmployeeMapper中添加方法

/**
     * 插入员工数据
     * @param employee
     */
    @Insert("INSERT into employee values (null,#{name},#{username},#{password},#{phone},#{sex},#{idNumber}" +
            ",#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
    void insert(Employee employee);

在application.yml中已开启驼峰命名,故id_number和idNumber可对应。

mybatis:
  configuration:
    #开启驼峰命名
    map-underscore-to-camel-case: true

!  !  !

新增员工时在mapper层的insert注意不是数据库中表格的字段名,而是定义的实体类中的属性名。

1.3 功能测试

代码已经发开发完毕,对新增员工功能进行测试。

功能测试实现方式:

  • 通过接口文档测试

  • 通前后端联调测试

接下来我们使用上述两种方式分别测试。

1.3.1 接口文档测试

启动服务:访问http://localhost:8080/doc.html,进入新增员工接口

json数据:

{
  "id": 0,
  "idNumber": "111222333444555666",
  "name": "xiaozhi",
  "phone": "13812344321",
  "sex": "1",
  "username": "小智"
}

响应码:401 报错

通过断点调试:进入到JwtTokenAdminInterceptor拦截器

 	/**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌 jwtProperties.getAdminTokenName()获取为token
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }

报错原因:由于JWT令牌校验失败,导致EmployeeController的addEmp()方法没有被调用

解决方法:调用员工登录接口获得一个合法的JWT令牌

使用admin用户登录获取令牌

添加令牌:

将合法的JWT令牌添加到全局参数中

文档管理-->全局参数设置-->添加参数

接口测试:

查看employee表:

测试成功。

注意:由于开发阶段前端和后端是并行开发的,后端完成某个功能后,此时前端对应的功能可能还没有开发完成, 导致无法进行前后端联调测试。所以在开发阶段,后端测试主要以接口文档测试为主。

1.4 代码完善

目前,程序存在的问题主要有两个:

  • 录入的用户名已存,抛出的异常后没有处理

  • 新增员工时,创建人id和修改人id设置为固定值

接下来,我们对上述两个问题依次进行分析和解决。

1.4.1 问题一

描述:录入的用户名已存,抛出的异常后没有处理

分析:

新增username=zhangsan的用户,若employee表中之前已存在。

后台报错信息:

查看employee表结构:

我们发现username已经添加了唯一约束,不能重复。

解决:

通过全局异常处理器来处理。

在GlobalExceptionHandler.java中添加方法,这个异常方法里我们只需要捕获SQL的异常即可

/**
     * 捕获SQL异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result doSQLException(SQLIntegrityConstraintViolationException ex) {
        log.error("异常信息:{}",ex.getMessage());
        String message = ex.getMessage();
        if (message.contains("Duplicate")) {
            return Result.error(message.split(" ")[2] + MessageConstant.ALREADY_EXIST);
        }
        return Result.error(MessageConstant.UNKNOWN_ERROR);
    }

因为在username冲突时,后端会报同样的错误语句,我们可以利用split分割空格得到的数组,获取数组中的下标为2的值,就能捕获异常并把重复的用户名打印出来。

拼接输出的异常信息时,我们可以把"username"后面的已存在设置为常量,避免“魔数”的出现,也可以让代码更加规范,在报错信息的常量类MessageConstant里设置如下:

package com.sky.constant;

/**
 * 信息提示常量类
 */
public class MessageConstant {

    public static final String PASSWORD_ERROR = "密码错误";
    public static final String ACCOUNT_NOT_FOUND = "账号不存在";
    public static final String ACCOUNT_LOCKED = "账号被锁定";
    public static final String UNKNOWN_ERROR = "未知错误";
    public static final String USER_NOT_LOGIN = "用户未登录";
    public static final String CATEGORY_BE_RELATED_BY_SETMEAL = "当前分类关联了套餐,不能删除";
    public static final String CATEGORY_BE_RELATED_BY_DISH = "当前分类关联了菜品,不能删除";
    public static final String SHOPPING_CART_IS_NULL = "购物车数据为空,不能下单";
    public static final String ADDRESS_BOOK_IS_NULL = "用户地址为空,不能下单";
    public static final String LOGIN_FAILED = "登录失败";
    public static final String UPLOAD_FAILED = "文件上传失败";
    public static final String SETMEAL_ENABLE_FAILED = "套餐内包含未启售菜品,无法启售";
    public static final String PASSWORD_EDIT_FAILED = "密码修改失败";
    public static final String DISH_ON_SALE = "起售中的菜品不能删除";
    public static final String SETMEAL_ON_SALE = "起售中的套餐不能删除";
    public static final String DISH_BE_RELATED_BY_SETMEAL = "当前菜品关联了套餐,不能删除";
    public static final String ORDER_STATUS_ERROR = "订单状态错误";
    public static final String ORDER_NOT_FOUND = "订单不存在";
    public static final String ALREADY_EXIST = "已存在";

}

再次,接口测试:

1.4.2 问题二

描述:新增员工时,我们把表中的创建人id和修改人id设置为固定值10,但是实际开发中需要获取到当前登录人员的id

分析:

	/**
     * 新增员工
     *
     * @param employeeDTO
     */
    public void save(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        //................
        //////////当前设置的id为固定值10//////////
        employee.setCreateUser(10L);
        employee.setUpdateUser(10L);
        //////////////////////////////////////
        //.................................

        employeeMapper.insert(employee);//后续步骤定义
    }

解决:

通过某种方式动态获取当前登录员工的id。

员工登录成功后会生成JWT令牌并响应给前端:

 /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @ApiOperation("员工登录")
    @PostMapping("/login")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }

在拦截器中我们用解析token获取到了员工id,但是我们最终存入当前id时是在service中操作的,但在拦截器中的员工id是一个局部变量,无法在Service层中使用。

方法一:我们可以获取token,token含有员工id,然后在存入当前员工id前解析token,但是后续有很多新增操作都需要存入员工id,每一次存入前都需要写这段代码,非常冗余。

方法二:另一种方法是把拦截器中的员工id设置为全局变量,这样在别的类中也可以直接使用,但是这种方法会将id直接暴露在所有的类都可以调用和修改非常不安全

登录方法中,前端会携带JWT令牌,而我们通过令牌可以解析出员工存入的id:

在拦截器类中,我们可以从用JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token)解析出员工id:

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("JwtTokenAdminInterceptor:线程id={}",Thread.currentThread().getId());
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            //将获取到的登录人员id存入到TreadLocal中,方便后续使用
            //BaseContext.setCurrentId(empId);
            log.info("当前员工id:", empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

思考:解析出登录员工id后,如何传递给Service的addEmp方法?

通过ThreadLocal进行传递。

1.4.3 ThreadLocal

介绍:

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

常用方法:

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

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

  • public void remove() 移除当前线程的线程局部变量

对ThreadLocal有了一定认识后,接下来继续解决问题二

一次请求在后端所用的线程是同一个:

发送请求——>拦截器——>Controller——>Service——>Mapper

解析了token可以拿到emp_id,需要在service层拿到拦截器解析出来的emp_id

这就是ThreadLocal的好处,在一条线程上的数据可以共享,我们可以先把emp_id在拦截器中就存入ThreadLocal中的线程局部变量,然后在service再取出就可以用了。

初始工程中已经封装了 ThreadLocal 操作的工具类:

在sky-common模块:

package com.sky.context;

public class BaseContext {

    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    public static Long getCurrentId() {
        return threadLocal.get();
    }

    public static void removeCurrentId() {
        threadLocal.remove();
    }

}

在拦截器中解析出当前登录员工id,并放入线程局部变量中:

在sky-server模块中,拦截器:

package com.sky.interceptor;

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
		//.............................
       
        //2、校验令牌
        try {
            //.................
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            /////将用户id存储到ThreadLocal////////
            BaseContext.setCurrentId(empId);
            ////////////////////////////////////
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //......................
        }
    }
}

在Service中获取线程局部变量中的值:

/**
     * 新增员工
     * @param dto
     */
    @Override
    public void addEmp(EmployeeDTO dto) {
        log.info("EmployeeServiceImpl:线程id={}",Thread.currentThread().getId());
        Employee employee = new Employee();
        //属性拷贝
        BeanUtils.copyProperties(dto,employee);//将dto中的属性值复制给employee
        //1.补充缺失的属性值
        //补充密码字段,需要进行MD5加密后才能存储到数据库
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
        employee.setStatus(StatusConstant.ENABLE);
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        //从ThreadLocal中获取当前登录用户的id
        employee.setCreateUser(BaseContext.getCurrentId());
        employee.setUpdateUser(BaseContext.getCurrentId());
        //2.调用mapper的新增方法,将员工对象存入employee表中
        employeeMapper.insert(employee);

    }

测试:使用admin(id=1)用户登录后添加一条记录

查看employee表记录

1.5 代码提交

2. 员工分页查询

2.1 需求分析和设计

2.1.1 产品原型

系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。而在我们的分页查询页面中, 除了分页条件以外,还有一个查询条件 "员工姓名"。

查询员工原型:

业务规则

  • 根据页码展示员工信息

  • 每页展示10条数据

  • 分页查询时可以根据需要,输入员工姓名进行查询

2.1.2 接口设计

注意事项:

  • 请求参数类型为Query,不是json格式提交,在路径后直接拼接。/admin/employee/page?name=zhangsan

  • 返回数据中records数组中使用Employee实体类对属性进行封装。

2.2 代码开发

2.2.1 设计DTO类

根据请求参数进行封装,在sky-pojo模块中

package com.sky.dto;

import lombok.Data;

import java.io.Serializable;

@Data
public class EmployeePageQueryDTO implements Serializable {

    //员工姓名
    private String name;

    //页码
    private Integer page=1;

    //每页显示记录数
    private Integer pageSize=10;

}

在EmployeePageQueryDTO类中,page和pageSize使用Integer类型而不是int类型的原因如下:
1. 可以为null
使用Integer包装类型最重要的原因是它可以为null。这在处理分页参数时非常有用:
当前端没有传递分页参数时,这些字段可以保持为null
后端可以更容易地区分"未传递参数"和"传递了参数但值为0"这两种情况
可以在后端设置默认值,用了int类型,当未传递参数时默认会为0,不符合业务需求

2. 与Spring MVC的数据绑定机制配合更好
Spring MVC在处理HTTP请求参数时,如果使用基本类型int,当参数缺失时会抛出异常。而使用Integer类型时:
如果参数缺失,字段值为null
可以通过初始化赋值设置默认值
避免了类型转换异常


3. 更好的灵活性
使用Integer类型提供了更大的灵活性

4. 符合DTO设计原则
在数据传输对象(DTO)中,通常使用包装类型而非基本类型,因为:
可以更好地表示"无值"状态
与JSON序列化/反序列化配合更好
避免默认值干扰业务逻辑


5. 与分页插件配合
在使用PageHelper等分页插件时,通常需要检查参数是否有效

为什么要实现Serializable接口?

让对象可以被"打包"和"解包":
网络传输 - 对象可以在网络中传输(比如微服务之间调用)
存储到缓存 - 可以存入Redis等缓存系统
保存到文件 - 对象可以保存成文件,需要时再恢复
Session存储 - Web应用中可以保存在用户会话里
项目中的意义
在sky-take-out项目中,虽然目前可能用不上,但实现Serializable接口是为了:
未来扩展:万一以后需要缓存查询结果或分布式部署
规范做法:Java开发的通用做法,特别在企业级项目中
避免问题:防止在某些框架或场景下出现序列化相关的错误
简单类比
就像你要寄一个包裹:
不实现Serializable = 包裹无法打包,不能邮寄
实现Serializable = 包裹可以打包,能邮寄、能存储、能运输
所以这是一个"有备无患"的设计,保证对象在各种场景下都能正常使用。

2.2.2 封装PageResult

后面所有的分页查询,统一都封装为PageResult对象。

在sky-common模块

/**
 * 封装分页查询结果
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {

    private long total; //总记录数

    private List records; //当前页数据集合

}

员工信息分页查询后端返回的对象类型为: Result<PageResult>

package com.sky.result;

import lombok.Data;

import java.io.Serializable;

/**
 * 后端统一返回结果
 * @param <T>
 */
@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}

2.2.3 Controller层

在sky-server模块中,com.sky.controller.admin.EmployeeController中添加分页查询方法。

/**
     * 员工分页查询
     * @param dto
     * @return
     */
    @ApiOperation("员工分页查询")
    @GetMapping("/page")
    public Result<PageResult> page(EmployeePageQueryDTO dto) {
        log.info("员工分页查询{}",dto);
        PageResult pageResult = employeeService.page(dto);
        return Result.success(pageResult);
    }

2.2.4 Service层接口

在EmployeeService接口中声明page方法:

/**
     * 分页查询
     * @param dto
     * @return
     */
    PageResult page(EmployeePageQueryDTO dto);

2.2.5 Service层实现类

在EmployeeServiceImpl中实现page方法:

/**
     * 分页查询员工--用上分页插件PageHelper
     * @param dto
     * @return
     */
    @Override
    public PageResult page(EmployeePageQueryDTO dto) {
        //1.设置分页参数
        PageHelper.startPage(dto.getPage(),dto.getPageSize());

        //2.调用mapper的查询方法,并强转返回类型为Page
        Page<Employee> page = employeeMapper.list(dto.getName());

        //3.封装PageResult对象并返回
        return new PageResult(page.getTotal(),page.getResult());
    }

注意:此处使用 mybatis 的分页插件 PageHelper 来简化分页代码的开发。底层基于 mybatis 的拦截器实现。

PageHelper的用法,分页插件一定要再pom文件中导入依赖才可以使用

<dependency>
   <groupId>com.github.pagehelper</groupId>
   <artifactId>pagehelper-spring-boot-starter</artifactId>
   <version>${pagehelper}</version>
</dependency>

Page继承了ArrayList,所以也是一个集合,可以强转为Employee类型,因为最终到页面的数据就是一个个员工列表,然后将total和records封装到PageResult中返回。

2.2.6 Mapper层

在 EmployeeMapper 中声明list 方法:

/**
     * 条件查询
     * @param name
     * @return
     */
    Page<Employee> list(String name);
<!--根据姓名模糊查询,需要根据事件倒序排序-->
    <select id="list" resultType="com.sky.entity.Employee">
        select *
        from employee
        <where>
            <if test="name != null and name != ''">
                name like concat('%', #{name}, '%')
            </if>
        </where>
        order by create_time desc
    </select>

注意这里的create_time必须是对应数据库里面的字段名。

concat的作用?

concat('%',#{name],'%') 的作用是将三个部分连接起来:

  • 第一个 '%' - 百分号,表示任意字符开头
  • #{name} - 参数值,即用户输入的搜索关键词
  • 第二个 '%' - 百分号,表示任意字符结尾

这样连接后的效果相当于在搜索关键词的前后都加上了通配符%,实现了模糊匹配查询。

举个例子,如果用户搜索关键词是 "pizza",那么:
concat('%', 'pizza', '%') 会生成 '%pizza%'
这样就能匹配到所有包含 "pizza" 的名称,比如 "pizza hut"、"best pizza"、"pizza delivery" 等

为什么要使用 concat 函数而不是直接写 '%#{name}%' 呢?
这是出于以下考虑:
安全性:使用 #{name} 占位符可以防止SQL注入攻击,MyBatis会自动进行参数转义
规范性:这是一种标准的模糊查询写法,在各种数据库系统中都有良好的兼容性
可读性:明确地展示了模糊匹配的逻辑,便于理解和维护
这也是项目中定义的MyBatis模糊查询规范的要求:
使用 concat('%', #{value}, '%') 格式实现模糊查询
禁止字符串拼接
必须使用 #{} 占位符防止SQL注入

最后注意xml中的语句结束不能加分号

2.3 功能测试

可以通过接口文档进行测试,也可以进行前后端联调测试。

接下来使用两种方式分别测试:

2.3.1 接口文档测试

重启服务:访问http://localhost:8080/doc.html,进入员工分页查询

响应结果:

2.3.2 前后端联调测试

点击员工管理

输入员工姓名为zhangsan

不难发现,最后操作时间格式不清晰,在代码完善中解决。

2.4 代码完善

问题描述:操作时间字段显示有问题。原因是在配置类中有如下代码:

这个父类WebMvcConfigurationSupport有很多自带的序列化对象映射器,会将时间转换为类似数组的格式。

解决方式:

1). 方式一

在属性上加上注解,对日期进行格式化

但这种方式,需要在每个时间属性上都要加上该注解,使用较麻烦,不能全局处理。

2). 方式二(推荐 )

在WebMvcConfiguration中扩展SpringMVC的消息转换器,统一对日期类型进行格式处理

/**
     * 扩展消息转换器,将String类型的json转换成对象
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器对象...");
        //1.创建消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();

        //2.设置自定义的消息对象转换器
        converter.setObjectMapper(new JacksonObjectMapper());

        //3.将自定义的消息对象转换器设置为优先级最高
        converters.add(0, converter);
    }

时间格式的定义是在JacksonObjectMapper类中:

/**
 * 对象映射器:基于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_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
                .addSerializer(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);
    }
}

3. 启用禁用员工账号

3.1 需求分析与设计

3.1.1 产品原型

在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 "禁用",如果员工账号状态为已禁用,则按钮显示为"启用"。

启禁用员工原型:

业务规则:

  • 可以对状态为“启用” 的员工账号进行“禁用”操作

  • 可以对状态为“禁用”的员工账号进行“启用”操作

  • 状态为“禁用”的员工账号不能登录系统

3.1.2 接口设计

1). 路径参数携带状态值。

2). 同时,把id传递过去,明确对哪个用户进行操作。

3). 返回数据code状态是必须,其它是非必须。

3.2 代码开发

3.2.1 Controller层

在sky-server模块中,根据接口设计中的请求参数形式对应的在 EmployeeController 中创建启用禁用员工账号的方法:

/**
     * 启用禁用员工账号
     * @param status
     * @param id
     * @return
     */
    @ApiOperation("启用禁用员工账号")
    @PostMapping("/status/{status}")
    public Result enableOrDisable(@PathVariable Integer status,Long id) {
        log.info("员工状态修改:status={},id={}",status, id);
        employeeService.enableOrDisable(status,id);
        return Result.success();
    }

注意:这里要传入路径参数,需要加上@PathVaiable注解。

3.2.2 Service层接口

在 EmployeeService 接口中声明启用禁用员工账号的业务方法:

/**
     * 启用禁用员工账号
     * @param status
     * @param id
     */
    void enableOrDisable(Integer status, Long id);

3.2.3 Service层实现类

在 EmployeeServiceImpl 中实现启用禁用员工账号的业务方法:

/**
     * 启用禁用员工账号
     * @param status
     * @param id
     */
    @Override
    public void enableOrDisable(Integer status, Long id) {
        Employee employee = Employee.builder()
                .id(id)
                .status(status)
                .updateTime(LocalDateTime.now())
                .updateUser(BaseContext.getCurrentId())
                .build();
        employeeMapper.update(employee);
    }

注意这里面得到updateUser依然是用ThreadLocal线程局部变量中存的id来获取。

启用禁用员工账号有很多要修改的地方,传统的修改方式是用一堆set方法一个一个修改,但是我们可以在employee类的上方加上@Builder注解:

  • 只需要更新部分字段(status, updateTime, updateUser)
  • 保持代码简洁和可读性
  • 明确表达了只更新特定字段的意图

3.2.4 Mapper层

在 EmployeeMapper 接口中声明 update 方法:

/**
     * 更新员工(包括更新状态)
     * @param employee
     * @return
     */
    //update employee set name = #{name} where id = #{id}
    void update(Employee employee);

在 EmployeeMapper.xml 中编写SQL:

<!--更新员工状态-->
    <update id="update">
        update employee
        <set>
            <if test="name != null">name = #{name},</if>
            <if test="username != null">username = #{username},</if>
            <if test="password != null">password = #{password},</if>
            <if test="phone != null">phone = #{phone},</if>
            <if test="sex != null">sex = #{sex},</if>
            <if test="idNumber != null">id_Number = #{idNumber},</if>
            <if test="updateTime != null">update_Time = #{updateTime},</if>
            <if test="updateUser != null">update_User = #{updateUser},</if>
            <if test="status != null">status = #{status},</if>
        </set>
        where id = #{id}
    </update>

3.3 功能测试

3.3.1 前后端联调测试

测试前,查询employee表中员工账号状态

点击启用:

4. 编辑员工

4.1 需求分析与设计

4.1.1 产品原型

在员工管理列表页面点击 "编辑" 按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击 "保存" 按钮完成编辑操作。

员工列表原型:

修改页面原型

注:点击修改时,数据应该正常回显到修改页面。

4.1.2 接口设计

根据上述原型图分析,编辑员工功能涉及到两个接口:

  • 根据id查询员工信息

  • 编辑员工信息

1). 根据id查询员工信息

注:因为是修改功能,请求方式可设置为PUT。

4.2 代码开发

4.2.1 回显员工信息功能

1). Controller层

在 EmployeeController 中创建 getById 和update 方法:

/**
     * 回显员工
     * @param id
     * @return
     */
    @ApiOperation("回显员工")
    @GetMapping("/{id}")
    public Result<Employee> getById(@PathVariable Long id) {
        log.info("回显员工:{}",id);
        Employee employee = employeeService.getById(id);
        return Result.success(employee);
    }

    /**
     * 修改员工信息
     * @param dto
     * @return
     */
    @ApiOperation("编辑员工信息")
    @PutMapping
    public Result update(@RequestBody EmployeeDTO dto) {
        log.info("编辑员工信息:{}",dto);
        employeeService.update(dto);
        return Result.success();
    }

2). Service层接口

在 EmployeeService 接口中声明 getById 方法:

/**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    Employee getById(Long id);

    /**
     * 编辑员工信息
     * @param dto
     */
    void update(EmployeeDTO dto);

3). Service层实现类

在 EmployeeServiceImpl 中实现 getById 方法:

/**
     * 回显员工
     * @param id
     * @return
     */
    @Override
    public Employee getById(Long id) {
        return employeeMapper.getById(id);
    }

    /**
     * 编辑员工信息
     * @param dto
     */
    @Override
    public void update(EmployeeDTO dto) {
        Employee employee = new Employee();
        //拷贝属性值
        BeanUtils.copyProperties(dto,employee);
        //补充更新时间和更新人
        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.update(employee);
    }

注意这里有一个方法BeanUtils.copyProperties(dto,employee);是用来拷贝属性值,第一个参数是原来的实体类,第二个参数是目标实体类。

4). Mapper层

在 EmployeeMapper 接口中声明 getById 方法:

/**
     * 更新员工(包括更新状态)
     * @param employee
     * @return
     */
    //update employee set name = #{name} where id = #{id}
    void update(Employee employee);

    /**
     * 根据id查询员工
     * @param id
     * @return
     */
    Employee getById(Long id);
<!--根据id查询员工-->
    <select id="getById" resultType="com.sky.entity.Employee">
        select * from employee where id = #{id}
    </select>

4.3 功能测试

4.3.1 接口文档测试

分别测试根据id查询员工信息编辑员工信息两个接口

1). 根据id查询员工信息

查询employee表中的数据,以id=4的记录为例

2). 编辑员工信息

修改id=4的员工信息,namezhangsan改为张三丰username张三改为zhangsanfeng

查看employee表数据

4.3.2 前后端联调测试

进入到员工列表查询

对员工姓名为杰克的员工数据修改,点击修改,数据已回显

修改后,点击保存

4.4 代码提交

5. 导入分类模块功能代码

5.1 需求分析与设计

5.1.1 产品原型

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

先来分析菜品分类相关功能。

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

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

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

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

启用禁用菜品分类:在分类管理列表页面,可以对某个分类进行启用或者禁用操作。

分类类型查询:当点击分类类型下拉框时,从数据库中查询所有的菜品分类数据进行展示。

分类管理原型:

业务规则:

  • 分类名称必须是唯一的

  • 分类按照类型可以分为菜品分类和套餐分类

  • 新添加的分类状态默认为“禁用”

5.1.2 接口设计

根据上述原型图分析,菜品分类模块共涉及6个接口。

  • 新增分类

  • 分类分页查询

  • 根据id删除分类

  • 修改分类

  • 启用禁用分类

  • 根据类型查询分类

接下来,详细地分析每个接口。

找到资料-->项目接口文档-->苍穹外卖-管理端接口.html

1). 新增分类

2). 分类分页查询

3). 根据id删除分类

4). 修改分类

5). 启用禁用分类

6). 根据类型查询分类

5.1.3 表设计

category表结构:

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)分类名称唯一
typeint分类类型1菜品分类 2套餐分类
sortint排序字段用于分类数据的排序
statusint状态1启用 0禁用
create_timedatetime创建时间
update_timedatetime最后修改时间
create_userbigint创建人id
update_userbigint最后修改人id

5.2 代码

5.2.1 Mapper层

DishMapper.java

package com.sky.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface DishMapper {

    /**
     * 根据分类id查询菜品数量
     * @param categoryId
     * @return
     */
    @Select("select count(id) from dish where category_id = #{categoryId}")
    Integer countByCategoryId(Long categoryId);

}

SetmealMapper.java

package com.sky.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface SetmealMapper {

    /**
     * 根据分类id查询套餐的数量
     * @param id
     * @return
     */
    @Select("select count(id) from setmeal where category_id = #{categoryId}")
    Integer countByCategoryId(Long id);

}

CategoryMapper.java

package com.sky.mapper;

import com.github.pagehelper.Page;
import com.sky.enumeration.OperationType;
import com.sky.dto.CategoryPageQueryDTO;
import com.sky.entity.Category;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

@Mapper
public interface CategoryMapper {

    /**
     * 插入数据
     * @param category
     */
    @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
            " VALUES" +
            " (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    void insert(Category category);

    /**
     * 分页查询
     * @param categoryPageQueryDTO
     * @return
     */
    Page<Category> pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);

    /**
     * 根据id删除分类
     * @param id
     */
    @Delete("delete from category where id = #{id}")
    void deleteById(Long id);

    /**
     * 根据id修改分类
     * @param category
     */
    void update(Category category);

    /**
     * 根据类型查询分类
     * @param type
     * @return
     */
    List<Category> list(Integer type);
}

CategoryMapper.xml,进入到resources/mapper目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.CategoryMapper">

    <select id="pageQuery" resultType="com.sky.entity.Category">
        select * from category
        <where>
            <if test="name != null and name != ''">
                and name like concat('%',#{name},'%')
            </if>
            <if test="type != null">
                and type = #{type}
            </if>
        </where>
        order by sort asc , create_time desc
    </select>

    <update id="update" parameterType="Category">
        update category
        <set>
            <if test="type != null">
                type = #{type},
            </if>
            <if test="name != null">
                name = #{name},
            </if>
            <if test="sort != null">
                sort = #{sort},
            </if>
            <if test="status != null">
                status = #{status},
            </if>
            <if test="updateTime != null">
                update_time = #{updateTime},
            </if>
            <if test="updateUser != null">
                update_user = #{updateUser}
            </if>
        </set>
        where id = #{id}
    </update>

    <select id="list" resultType="Category">
        select * from category
        where status = 1
        <if test="type != null">
            and type = #{type}
        </if>
        order by sort asc,create_time desc
    </select>
</mapper>

5.2.2 Service层

CategoryService.java

package com.sky.service;

import com.sky.dto.CategoryDTO;
import com.sky.dto.CategoryPageQueryDTO;
import com.sky.entity.Category;
import com.sky.result.PageResult;
import java.util.List;

public interface CategoryService {

    /**
     * 新增分类
     * @param categoryDTO
     */
    void save(CategoryDTO categoryDTO);

    /**
     * 分页查询
     * @param categoryPageQueryDTO
     * @return
     */
    PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);

    /**
     * 根据id删除分类
     * @param id
     */
    void deleteById(Long id);

    /**
     * 修改分类
     * @param categoryDTO
     */
    void update(CategoryDTO categoryDTO);

    /**
     * 启用、禁用分类
     * @param status
     * @param id
     */
    void startOrStop(Integer status, Long id);

    /**
     * 根据类型查询分类
     * @param type
     * @return
     */
    List<Category> list(Integer type);
}

EmployeeServiceImpl.java

/**
 * 分类业务层
 */
@Service
@Slf4j
public class CategoryServiceImpl implements CategoryService {

    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;

    /**
     * 新增分类
     * @param categoryDTO
     */
    public void save(CategoryDTO categoryDTO) {
        Category category = new Category();
        //属性拷贝
        BeanUtils.copyProperties(categoryDTO, category);

        //分类状态默认为禁用状态0
        category.setStatus(StatusConstant.DISABLE);

        //设置创建时间、修改时间、创建人、修改人
        category.setCreateTime(LocalDateTime.now());
        category.setUpdateTime(LocalDateTime.now());
        category.setCreateUser(BaseContext.getCurrentId());
        category.setUpdateUser(BaseContext.getCurrentId());

        categoryMapper.insert(category);
    }

    /**
     * 分页查询
     * @param categoryPageQueryDTO
     * @return
     */
    public PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO) {
        PageHelper.startPage(categoryPageQueryDTO.getPage(),categoryPageQueryDTO.getPageSize());
        //下一条sql进行分页,自动加入limit关键字分页
        Page<Category> page = categoryMapper.pageQuery(categoryPageQueryDTO);
        return new PageResult(page.getTotal(), page.getResult());
    }

    /**
     * 根据id删除分类
     * @param id
     */
    public void deleteById(Long id) {
        //查询当前分类是否关联了菜品,如果关联了就抛出业务异常
        Integer count = dishMapper.countByCategoryId(id);
        if(count > 0){
            //当前分类下有菜品,不能删除
            throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_DISH);
        }

        //查询当前分类是否关联了套餐,如果关联了就抛出业务异常
        count = setmealMapper.countByCategoryId(id);
        if(count > 0){
            //当前分类下有菜品,不能删除
            throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_SETMEAL);
        }

        //删除分类数据
        categoryMapper.deleteById(id);
    }

    /**
     * 修改分类
     * @param categoryDTO
     */
    public void update(CategoryDTO categoryDTO) {
        Category category = new Category();
        BeanUtils.copyProperties(categoryDTO,category);

        //设置修改时间、修改人
        category.setUpdateTime(LocalDateTime.now());
        category.setUpdateUser(BaseContext.getCurrentId());

        categoryMapper.update(category);
    }

    /**
     * 启用、禁用分类
     * @param status
     * @param id
     */
    public void startOrStop(Integer status, Long id) {
        Category category = Category.builder()
                .id(id)
                .status(status)
                .updateTime(LocalDateTime.now())
                .updateUser(BaseContext.getCurrentId())
                .build();
        categoryMapper.update(category);
    }

    /**
     * 根据类型查询分类
     * @param type
     * @return
     */
    public List<Category> list(Integer type) {
        return categoryMapper.list(type);
    }
}

5.2.3 Controller层

CategoryController.java

/**
 * 分类管理
 */
@RestController
@RequestMapping("/admin/category")
@Api(tags = "分类相关接口")
@Slf4j
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

    /**
     * 新增分类
     * @param categoryDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增分类")
    public Result<String> save(@RequestBody CategoryDTO categoryDTO){
        log.info("新增分类:{}", categoryDTO);
        categoryService.save(categoryDTO);
        return Result.success();
    }

    /**
     * 分类分页查询
     * @param categoryPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("分类分页查询")
    public Result<PageResult> page(CategoryPageQueryDTO categoryPageQueryDTO){
        log.info("分页查询:{}", categoryPageQueryDTO);
        PageResult pageResult = categoryService.pageQuery(categoryPageQueryDTO);
        return Result.success(pageResult);
    }

    /**
     * 删除分类
     * @param id
     * @return
     */
    @DeleteMapping
    @ApiOperation("删除分类")
    public Result<String> deleteById(Long id){
        log.info("删除分类:{}", id);
        categoryService.deleteById(id);
        return Result.success();
    }

    /**
     * 修改分类
     * @param categoryDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改分类")
    public Result<String> update(@RequestBody CategoryDTO categoryDTO){
        categoryService.update(categoryDTO);
        return Result.success();
    }

    /**
     * 启用、禁用分类
     * @param status
     * @param id
     * @return
     */
    @PostMapping("/status/{status}")
    @ApiOperation("启用禁用分类")
    public Result<String> startOrStop(@PathVariable("status") Integer status, Long id){
        categoryService.startOrStop(status,id);
        return Result.success();
    }

    /**
     * 根据类型查询分类
     * @param type
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据类型查询分类")
    public Result<List<Category>> list(Integer type){
        List<Category> list = categoryService.list(type);
        return Result.success(list);
    }
}

5.3 功能测试

分页查询:

分类类型:

启用禁用:

点击禁用:

修改:

回显

修改后

新增:

点击确定,查询列表

删除:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值