02-员工管理&分类管理

今日内容

  • 新增员工
  • 员工分页查询
  • 启用禁用员工账号
  • 编辑员工
  • 导入分类模块功能代码

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

员工管理效果

image1

菜品分类管理效果

一、新增员工

1、需求分析和设计

1.1、产品原型

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

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

新增员工原型

image3

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

注意事项

  1. 账号必须是唯一的
  2. 手机号为合法的 11 位手机号码
  3. 身份证号为合法的 18 位身份证号码
  4. 密码默认为 123456

1.2、接口设计

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

image4
image5

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

本项目约定:

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

1.3、表设计

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

employee 表结构

字段名 数据类型 说明 备注
id bigint 主键 自增
name varchar(32) 姓名
username varchar(32) 用户名 唯一
password varchar(64) 密码
phone varchar(11) 手机号
sex varchar(2) 性别
id_number varchar(18) 身份证号
status Int 账号状态 1正常 0锁定
create_time Datetime 创建时间
update_time datetime 最后修改时间
create_user bigint 创建人id
update_user bigint 最后修改人id

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

image6

2、代码开发

2.1、设计DTO类

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

前端传递参数列表:

image7

思考:是否可以使用对应的实体类来接收呢?

image8

注意:当前端提交的数据和实体类中对应的属性差别比较大时,建议使用 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;

}

2.2、Controller层

EmployeeController 中创建新增员工方法

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

/**
* 新增员工
* @param employeeDTO
* @return
*/
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
    log.info("新增员工:{}",employeeDTO);
    employeeService.save(employeeDTO);//该方法后续步骤会定义
    return Result.success();
}

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

}

2.3、Service层接口

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

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

/**
* 新增员工
* @param employeeDTO
*/
void save(EmployeeDTO employeeDTO);

2.4、Service层实现类

在 EmployeeServiceImpl 中实现新增员工方法

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

/**
* 新增员工
* @param employeeDTO
*/
public void save(EmployeeDTO employeeDTO) {
    Employee employee = new Employee();

    //对象属性拷贝
    BeanUtils.copyProperties(employeeDTO, employee);

    //设置账号的状态,默认为正常状态,1表示正常 0表示锁定
    employee.setStatus(StatusConstant.ENABLE);

    //设置密码,默认密码123456
    employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));

    //设置当前记录的创建时间和修改时间
    employee.setCreateTime(LocalDateTime.now());
    employee.setUpdateTime(LocalDateTime.now());

    //设置当前记录创建人id和修改人id
    //TODO 后期需要改为当前登录用户的id
    employee.setCreateUser(10L);//目前写个假数据,后期修改
    employee.setUpdateUser(10L);

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

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

2.5、Mapper层

在 EmployeeMapper 中声明 insert 方法

com.sky.EmployeeMapper 中添加方法

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

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

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

3、功能测试

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

功能测试实现方式

  • 通过接口文档测试
  • 通前后端联调测试

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

3.1、接口文档测试

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

image9

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 的 save 方法没有被调用

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

使用 admin 用户登录获取令牌

image10

添加令牌

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

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

image11

接口测试

image12

其中,请求头部含有 JWT 令牌

image13

查看 employee 表

image14

测试成功。

3.2、前后端联调测试

启动 nginx,访问 http://localhost

登录 –> 员工管理 –> 添加员工

image15

保存后,查看 employee 表

image16

测试成功。

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

4、代码完善

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

  • 若录入的用户名已存在,抛出的异常后没有处理
  • 新增员工时,创建人 id 和修改人 id 设置为固定值

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

4.1、问题一

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

分析

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

image17

后台报错信息:

image18

查看 employee 表结构:

image19

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

解决

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

进入到 sky-server 模块,com.sky.hander 包下,GlobalExceptionHandler.java 添加方法

/**
* 处理SQL异常
* @param ex
* @return
*/
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
    //Duplicate entry 'zhangsan' for key 'employee.idx_username'
    String message = ex.getMessage();
    if(message.contains("Duplicate entry")){
        String[] split = message.split(" ");
        String username = split[2];
        String msg = username + MessageConstant.ALREADY_EXISTS;
        return Result.error(msg);
    }else{
        return Result.error(MessageConstant.UNKNOWN_ERROR);
    }
}

进入到 sky-common 模块,在 MessageConstant.java 添加

public static final String ALREADY_EXISTS = "已存在";

再次进行接口测试:

image20

4.2、问题二

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

分析

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

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

解决

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

image21

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

在 sky-server 模块

package com.sky.controller.admin;
/**
 * 员工管理
 */
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")
    @ApiOperation(value = "员工登录")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        //.........

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

        //............

        return Result.success(employeeLoginVO);
    }

}

后续请求中,前端会携带 JWT 令牌,通过 JWT 令牌可以解析出当前登录员工 id:

JwtTokenAdminInterceptor.java

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 {

        //..............

        //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());
            log.info("当前员工id:", empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

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

通过 ThreadLocal 进行传递。

4.3、ThreadLocal

介绍

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

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

注意:客户端发送的每次请求,后端的 Tomcat 服务器都会分配一个单独的线程来处理请求

常用方法

  • public void set(T value):设置当前线程的线程局部变量的值
  • public T get():返回当前线程所对应的线程局部变量的值
  • public void remove():移除当前线程的线程局部变量

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

image22

初始工程中已经封装了 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 employeeDTO
*/
public void save(EmployeeDTO employeeDTO) {
    //.............................

    //设置当前记录创建人id和修改人id
    employee.setCreateUser(BaseContext.getCurrentId());//目前写个假数据,后期修改
    employee.setUpdateUser(BaseContext.getCurrentId());

    employeeMapper.insert(employee);
}

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

image23

查看 employee 表记录

image24

5、代码提交

点击提交:

image25

提交过程中,出现提示:

image26

继续 push:

image27

推送成功:

image28

二、员工分页查询

1、需求分析和设计

1.1、产品原型

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

查询员工原型

image29

业务规则

  • 根据页码展示员工信息
  • 每页展示 10 条数据
  • 分页查询时可以根据需要,输入员工姓名进行查询

1.2、接口设计

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

image30
image31

注意事项

  • 请求参数类型为 Query,不是 json 格式提交,在路径后直接拼接。如 /admin/employee/page?name=zhangsan
  • 返回数据中 records 数组中使用 Employee 实体类对属性进行封装。

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 int page;

    //每页显示记录数
    private int pageSize;

}

2.2、封装PageResult

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

在 sky-common 模块

package com.sky.result;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/**
 * 封装分页查询结果
 */
@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.3、Controller 层

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

/**
* 员工分页查询
* @param employeePageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
    log.info("员工分页查询,参数为:{}", employeePageQueryDTO);
    PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);//后续定义
    return Result.success(pageResult);
}

2.4、Service 层接口

在 EmployeeService 接口中声明 pageQuery 方法:

/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

2.5、Service 层实现类

在 EmployeeServiceImpl 中实现 pageQuery 方法:

/**
* 分页查询
*
* @param employeePageQueryDTO
* @return
*/
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
    // select * from employee limit 0,10
    //开始分页查询
    PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());

    Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);//后续定义
    long total = page.getTotal();
    List<Employee> records = page.getResult();

    return new PageResult(total, records);
}

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

故在 pom.xml 文中添加依赖(初始工程已添加)

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

2.6、Mapper 层

在 EmployeeMapper 中声明 pageQuery 方法:

/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

在 src/main/resources/mapper/EmployeeMapper.xml 中编写 SQL:

<mapper namespace="com.sky.mapper.EmployeeMapper">
    <select id="pageQuery" resultType="com.sky.entity.Employee">
        select * form employee
        <where>
            <if test="name != null and name != ''">
                and name like concat('%',#{name},'%')
            </if>
        </where>
        order by create_time desc
    </select>
</mapper>

3、功能测试

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

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

3.1、接口文档测试

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

image32

响应结果

image33

3.2、前后端联调测试

点击员工管理

image34

输入员工姓名为 zhangsan

image35

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

image36

4、代码完善

问题描述:操作时间字段显示有问题。

image37

解决方式

1). 方式一

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

image38

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

2). 方式二(推荐 )

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

/**
* 扩展Spring MVC框架的消息转化器
* @param converters
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    log.info("扩展消息转换器...");
    //创建一个消息转换器对象
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    //需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
    converter.setObjectMapper(new JacksonObjectMapper());
    //将自己的消息转化器加入容器中
    converters.add(0,converter);
}

添加后,再次测试

image39

时间格式定义,sky-common 模块中

package com.sky.json;

public class JacksonObjectMapper extends ObjectMapper {

    //.......
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    //.......

    }
}

5、代码提交

image40

后续步骤和新增员工代码提交一致,不再赘述。

三、启用禁用员工账号

1、需求分析与设计

1.1、产品原型

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

启禁用员工原型

image41

业务规则

  • 可以对状态为 “启用” 的员工账号进行“禁用”操作
  • 可以对状态为 “禁用” 的员工账号进行“启用”操作
  • 状态为 “禁用” 的员工账号不能登录系统

1.2、接口设计

image42
image43

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

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

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

2、代码开发

2.1、Controller层

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

/**
* 启用禁用员工账号
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status,Long id){
    log.info("启用禁用员工账号:{},{}",status,id);
    employeeService.startOrStop(status,id);//后续步骤定义
    return Result.success();
}

2.2、Service层接口

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

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

2.3、Service层实现类

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

/**
* 启用禁用员工账号
*
* @param status
* @param id
*/
public void startOrStop(Integer status, Long id) {
    // update employee set status=? where id=?

    /*Employee employee = new Employee();
    employee.setStatus(status);
    employee.setId(id);*/

    Employee employee = Employee.builder()
        .status(status)
        .id(id)
        .build();

    employeeMapper.update(employee);
}

2.4、Mapper层

在 EmployeeMapper 接口中声明 update 方法:

/**
* 根据主键动态修改属性
* @param employee
*/
void update(Employee employee);

在 EmployeeMapper.xml 中编写SQL:

<update id="update" parameterType="Employee">
    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.1、接口文档测试

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

image44

开始测试

image45

测试完毕后,再次查询员工账号状态

image46

3.2、前后端联调测试

测试前

image47

点击启用

image48

4、代码提交

image49

后续步骤和上述功能代码提交一致,不再赘述。

四、编辑员工

1、需求分析与设计

1.1、产品原型

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

员工列表原型

image50

修改页面原型

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

image51

1.2、接口设计

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

  • 根据 id 查询员工信息(用于页面回显)
  • 编辑员工信息

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

image52
image53

2). 编辑员工信息

image54
image55

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

2、代码开发

2.1、回显员工信息功能

1). Controller 层

在 EmployeeController 中创建 getById 方法:

/**
 * 根据id查询员工信息
 * @param id
 * @return
 */
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<Employee> getById(@PathVariable Long id){
    Employee employee = employeeService.getById(id);
    return Result.success(employee);
}

2). Service 层接口

在 EmployeeService 接口中声明 getById 方法:

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

3). Service 层实现类

在 EmployeeServiceImpl 中实现 getById 方法:

/**
 * 根据id查询员工
 *
 * @param id
 * @return
 */
public Employee getById(Long id) {
    Employee employee = employeeMapper.getById(id);
    employee.setPassword("****");
    return employee;
}

4). Mapper层

在 EmployeeMapper 接口中声明 getById 方法:

/**
 * 根据id查询员工信息
 * @param id
 * @return
 */
@Select("select * from employee where id = #{id}")
Employee getById(Long id);

可以先通过接口测试确认数据回显是否有问题,如果没有问题再继续开发

2.2、修改员工信息功能

1). Controller 层

在 EmployeeController 中创建 update 方法:

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

2). Service 层接口

在 EmployeeService 接口中声明 update 方法:

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

3). Service 层实现类

在 EmployeeServiceImpl 中实现 update 方法:

/**
 * 编辑员工信息
 *
 * @param employeeDTO
 */
public void update(EmployeeDTO employeeDTO) {
    Employee employee = new Employee();
    BeanUtils.copyProperties(employeeDTO, employee);

    employee.setUpdateTime(LocalDateTime.now());
    employee.setUpdateUser(BaseContext.getCurrentId());

    employeeMapper.update(employee);
}

在实现启用禁用员工账号功能时,已实现 employeeMapper.update(employee),在此不需写 Mapper 层代码。

3、功能测试

3.1、接口文档测试

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

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

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

image56

开始测试

image57

获取到了 id=4 的相关员工信息

2). 编辑员工信息

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

image58

查看 employee 表数据

image59

3.2、前后端联调测试

进入到员工列表查询

image60

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

image61

修改后,点击保存

image62

4、代码提交

image63

后续步骤和上述功能代码提交一致,不再赘述。

五、导入分类模块功能代码

1、需求分析与设计

1.1、产品原型

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

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

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

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

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

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

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

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

分类管理原型

image64

业务规则

  • 分类名称必须是唯一的
  • 分类按照类型可以分为菜品分类和套餐分类
  • 新添加的分类状态默认为 “禁用”

1.2、接口设计

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

  • 新增分类
  • 分类分页查询
  • 根据 id 删除分类
  • 修改分类
  • 启用禁用分类
  • 根据类型查询分类

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

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

1). 新增分类

image65
image66

2). 分类分页查询

image67
image68

3). 根据 id 删除分类

image69

4). 修改分类

image70
image71

5). 启用禁用分类

image72
image73

6). 根据类型查询分类

image74
image75

1.3、表设计

category 表结构

字段名 数据类型 说明 备注
id bigint 主键 自增
name varchar(32) 分类名称 唯一
type int 分类类型 1 菜品分类,2 套餐分类
sort int 排序字段 用于分类数据的排序
status int 状态 1 启用,0 禁用
create_time datetime 创建时间
update_time datetime 最后修改时间
create_user bigint 创建人 id
update_user bigint 最后修改人 id

2、代码导入

导入资料中的分类管理模块功能代码即可

image76

可按照 mapper —> service —> controller 依次导入,这样代码不会显示相应的报错。

进入到 sky-server 模块中

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

在 resources/mapper 目录下创建 CategoryMapper.xml

<?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>

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

CategoryServiceImpl.java

package com.sky.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.BaseContext;
import com.sky.dto.CategoryDTO;
import com.sky.dto.CategoryPageQueryDTO;
import com.sky.entity.Category;
import com.sky.exception.DeletionNotAllowedException;
import com.sky.mapper.CategoryMapper;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.result.PageResult;
import com.sky.service.CategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 分类业务层
 */
@Service
@Slf4j
public class CategoryServiceImpl  implements CategoryService {
    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;

    /**
     * 新增分类
     * @param categoryDTO
     */
    @Override
    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
     */
    @Override
    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
     */
    @Override
    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
     */
    @Override
    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
     */
    @Override
    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
     */
    @Override
    public List<Category> list(Integer type) {
        return categoryMapper.list(type);
    }
}

2.3、Controller层

CategoryController.java

package com.sky.controller.admin;

import com.sky.dto.CategoryDTO;
import com.sky.dto.CategoryPageQueryDTO;
import com.sky.entity.Category;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.CategoryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

/**
 * 分类管理
 */
@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);
    }
}

全部导入完毕后,进行编译

image77

3、功能测试

重启服务,访问 http://localhost:80 ,进入分类管理

分页查询

image78

分类类型

image79

启用禁用

image80

点击禁用

image81

修改

回显

image82

修改后

image83

新增

image84

点击确定,查询列表

image85

删除

image86

删除后,查询分类列表

image87

删除成功

4、代码提交

image88

后续步骤和上述功能代码提交一致,不再赘述。

参考视频:黑马程序员2023最新Java项目实战《苍穹外卖》,最适合新手的SpringBoot+SSM的企业级Java项目实战

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇