chenjunda
长期主义

苍穹外卖day02

2025-07-17 javaWeb

苍穹外卖day02

1. 新增员工

1.1 需求分析和设计

1.1.1 产品原型

image-20221111161120975

注意事项:

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

1.1.2 接口设计

image-20221111162912753 image-20221111162930483

本项目约定:

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

1.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,表示状态正常。

image-20221111180159188

1.2 代码开发

1.2.1 设计DTO类

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

前端传递参数列表:

image-20221111164002448

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

image-20221111164453341

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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中创建新增员工方法,接收前端提交的参数。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 新增员工
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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

1
2
3
4
5
/**
* 新增员工
* @param employeeDTO
*/
void save(EmployeeDTO employeeDTO);

1.2.4 Service层实现类

在EmployeeServiceImpl中实现新增员工方法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 新增员工
* @param employeeDTO
*/
@Override
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();

//通过DTO部分导入员工类的属性
BeanUtils.copyProperties(employeeDTO, employee);
//剩下的属性要手动配置

//配置status属性
employee.setStatus(StatusConstant.ENABLE);

//DigestUtils.md5DigestAsHex(...)
//这是 Spring 框架提供的工具类,作用是:
//用 MD5 算法加密字节数组
//并返回一个 32位的十六进制字符串
//比如:
//"123456" 加密后是:e10adc3949ba59abbe56e057f20f883e
//


//employee.setPassword(...)
//把加密后的字符串设置为员工对象的密码。
employee.setPassword(DigestUtils.md5DigestAsHex(employee.getPassword().getBytes()));

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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中添加方法

1
2
3
4
5
6
7
8
/**
* 插入员工数据
* @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可对应。

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

如果不开就要手动AS映射到java

1.3 功能测试

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

功能测试实现方式:

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

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

1.3.1 接口文档测试

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

image-20221111184934153

json数据:

1
2
3
4
5
6
7
8
{
"id": 0,
"idNumber": "111222333444555666",
"name": "xiaozhi",
"phone": "13812344321",
"sex": "1",
"username": "小智"
}

响应码:401 报错

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 校验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用户登录获取令牌

image-20221111185649636

添加令牌:

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

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

image-20221111185901726

Snipaste_2025-07-17_15-57-36

测试成功!!

1.3.2 前后端联调测试

启动nginx,访问 http://localhost:89

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

Snipaste_2025-07-17_16-03-38

测试成功。

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

1.4 代码完善

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

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

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

1.4.1 问题一

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

分析:

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

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

解决:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 处理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添加

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

再次,接口测试:

Snipaste_2025-07-17_16-17-02

1.4.2 问题二

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

分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 新增员工
*
* @param employeeDTO
*/
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
//................
//////////当前设置的id为固定值10//////////
employee.setCreateUser(10L);
employee.setUpdateUser(10L);
//////////////////////////////////////
//.................................

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

解决:

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

image-20221111201922482

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

在sky-server模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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进行传递。

1.4.3 ThreadLocal

介绍:

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

常用方法:

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

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

image-20221111212349365

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

在sky-common模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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模块中,拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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中获取线程局部变量中的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 新增员工
*
* @param employeeDTO
*/
public void save(EmployeeDTO employeeDTO) {
//.............................

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

employeeMapper.insert(employee);
}

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

Snipaste_2025-07-17_16-43-42

1.5 代码提交

2. 员工分页查询

2.1 需求分析和设计

2.1.1 产品原型

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

查询员工原型:

image-20221111215309289

业务规则

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

2.1.2 接口设计

image-20221111220031113image-20221111220041965

注意事项:

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

2.2 代码开发

2.2.1 设计DTO类

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.2 封装PageResult

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

在sky-common模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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中添加分页查询方法。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 员工分页查询
* @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.2.4 Service层接口

在EmployeeService接口中声明pageQuery方法:

1
2
3
4
5
6
/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

2.2.5 Service层实现类

在EmployeeServiceImpl中实现pageQuery方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 分页查询
*
* @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);
}

PageHelper 是 MyBatis 的分页插件,它的底层实现核心原理是:


基于 MyBatis 拦截器机制,在 SQL 执行前自动修改原 SQL,加上分页语句


🔍 具体工作机制如下:

1. PageHelper.startPage(pageNum, pageSize) 被调用时:

  • 它会在当前线程(ThreadLocal)中保存分页参数。
  • 例如保存成:{pageNum: 1, pageSize: 10}

2. 当 MyBatis 执行查询 SQL 时(如 employeeMapper.pageQuery()):

  • PageHelper 注册的拦截器(拦截的是 Executor.query(...) 方法)会被触发。
  • 在这个拦截器中,PageHelper 会:
    • 检测是否调用过 startPage()(判断 ThreadLocal 中是否有分页参数)。
    • 如果有,就自动 修改 SQL 语句,拼接分页语法。

🧠 例子:

原始 SQL:

1
2
3
4
5
sql


复制编辑
SELECT * FROM employee

分页后实际执行的 SQL(MySQL):

1
2
3
4
5
sql


复制编辑
SELECT * FROM employee LIMIT 0, 10

如果你传的是 page=2, pageSize=10,它会生成:

1
2
3
4
5
sql


复制编辑
SELECT * FROM employee LIMIT 10, 10

✅ 总结一句话:

PageHelper 是通过 拦截 MyBatis 查询语句并动态拼接 LIMIT 语句 实现分页的,本质是 在执行 SQL 前修改原始 SQL 来实现数据库级分页。

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

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

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper}</version>
</dependency>

2.2.6 Mapper层

在 EmployeeMapper 中声明 pageQuery 方法:

1
2
3
4
5
6
/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

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

1
2
3
4
5
6
7
8
9
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from employee
<where>
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
</where>
order by create_time desc
</select>

这段其实返回的是 List<Employee>,但用 PageHelper 包了一层后,变成了 Page<Employee>

2.3 功能测试

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

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

2.3.1 接口文档测试

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

image-20221112101946022

image-20221112102441810

image-20221112102540938

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

image-20221112102745437

2.4 代码完善

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

解决方式:

1). 方式一

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

image-20221112103501581

2). 方式二(推荐 )

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

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 扩展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);
}

添加后,再次测试

image-20221112104305608

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

1
2
3
4
5
6
7
8
9
10
package com.sky.json;

public class JacksonObjectMapper extends ObjectMapper {

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

}
}

2.5 代码提交

3. 启用禁用员工账号

3.1 需求分析与设计

3.1.1 产品原型

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

启禁用员工原型:

image-20221112112359233

业务规则:

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

3.1.2 接口设计

image-20221112112728333image-20221112112739680

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

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

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

3.2 代码开发

3.2.1 Controller层

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

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 启用禁用员工账号
* @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();
}

3.2.2 Service层接口

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

1
2
3
4
5
6
/**
* 启用禁用员工账号
* @param status
* @param id
*/
void startOrStop(Integer status, Long id);

3.2.3 Service层实现类

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 启用禁用员工账号
*
* @param status
* @param id
*/
public void startOrStop(Integer status, Long id) {
Employee employee = Employee.builder()
.status(status)
.id(id)
.build();

employeeMapper.update(employee);
}

3.2.4 Mapper层

在 EmployeeMapper 接口中声明 update 方法:

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

在 EmployeeMapper.xml 中编写SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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 功能测试

3.3.1 接口文档测试

image-20221112143316357

3.3.2 前后端联调测试

测试前:

image-20221112143552246

点击启用:

image-20221112143655318

3.4 代码提交

4. 编辑员工

4.1 需求分析与设计

4.1.1 产品原型

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

image-20221112144731759

修改页面原型

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

image-20221112144842825

4.1.2 接口设计

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

  • 根据id查询员工信息
  • 编辑员工信息

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

image-20221112145607939image-20221112145619775

image-20221112145643769image-20221112145659035

4.2 代码开发

4.2.1 回显员工信息功能

1). Controller层

在 EmployeeController 中创建 getById 方法:

1
2
3
4
5
6
7
8
9
10
11
/**
* 根据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 方法:

1
2
3
4
5
6
/**
* 根据id查询员工
* @param id
* @return
*/
Employee getById(Long id);

3). Service层实现类

在 EmployeeServiceImpl 中实现 getById 方法:

1
2
3
4
5
6
7
8
9
10
11
/**
* 根据id查询员工
*
* @param id
* @return
*/
public Employee getById(Long id) {
Employee employee = employeeMapper.getById(id);
employee.setPassword("****");
return employee;
}

4). Mapper层

在 EmployeeMapper 接口中声明 getById 方法:

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

4.2.2 修改员工信息功能

1). Controller层

在 EmployeeController 中创建 update 方法:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 编辑员工信息
* @param employeeDTO
* @return
*/
@PutMapping
@ApiOperation("编辑员工信息")
public Result update(@RequestBody EmployeeDTO employeeDTO){
log.info("编辑员工信息:{}", employeeDTO);
employeeService.update(employeeDTO);
return Result.success();
}

2). Service层接口

在 EmployeeService 接口中声明 update 方法:

1
2
3
4
5
/**
* 编辑员工信息
* @param employeeDTO
*/
void update(EmployeeDTO employeeDTO);

3). Service层实现类

在 EmployeeServiceImpl 中实现 update 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 编辑员工信息
*
* @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层代码。

4.3 功能测试

4.3.1 接口文档测试

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

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

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

开始测试

image-20221112155206712

image-20221112155430652

image-20221112155559298

4.4 代码提交

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

5.1 需求分析与设计

5.1.1 产品原型

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

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

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

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

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

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

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

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

Author: chenjunda

Link: http://example.com/2025/07/17/%E8%8B%8D%E7%A9%B9%E5%A4%96%E5%8D%96day02/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
diary2025-7-24
NextPost >
diary2025_7_16
CATALOG
  1. 1. 苍穹外卖day02
    1. 1.1. 1. 新增员工
      1. 1.1.1. 1.1 需求分析和设计
        1. 1.1.1.1. 1.1.1 产品原型
        2. 1.1.1.2. 1.1.2 接口设计
        3. 1.1.1.3. 1.1.3 表设计
      2. 1.1.2. 1.2 代码开发
        1. 1.1.2.1. 1.2.1 设计DTO类
        2. 1.1.2.2. 1.2.2 Controller层
        3. 1.1.2.3. 1.2.3 Service层接口
        4. 1.1.2.4. 1.2.4 Service层实现类
        5. 1.1.2.5. 1.2.5 Mapper层
      3. 1.1.3. 1.3 功能测试
        1. 1.1.3.1. 1.3.1 接口文档测试
        2. 1.1.3.2. 1.3.2 前后端联调测试
      4. 1.1.4. 1.4 代码完善
        1. 1.1.4.1. 1.4.1 问题一
        2. 1.1.4.2. 1.4.2 问题二
        3. 1.1.4.3. 1.4.3 ThreadLocal
    2. 1.2. 1.5 代码提交
    3. 1.3. 2. 员工分页查询
      1. 1.3.1. 2.1 需求分析和设计
        1. 1.3.1.1. 2.1.1 产品原型
        2. 1.3.1.2. 2.1.2 接口设计
      2. 1.3.2. 2.2 代码开发
        1. 1.3.2.1. 2.2.1 设计DTO类
        2. 1.3.2.2. 2.2.2 封装PageResult
        3. 1.3.2.3. 2.2.3 Controller层
        4. 1.3.2.4. 2.2.4 Service层接口
        5. 1.3.2.5. 2.2.5 Service层实现类
      3. 1.3.3. ✅ 基于 MyBatis 拦截器机制,在 SQL 执行前自动修改原 SQL,加上分页语句
      4. 1.3.4. 🔍 具体工作机制如下:
        1. 1.3.4.1. 1. PageHelper.startPage(pageNum, pageSize) 被调用时:
        2. 1.3.4.2. 2. 当 MyBatis 执行查询 SQL 时(如 employeeMapper.pageQuery()):
      5. 1.3.5. 🧠 例子:
      6. 1.3.6. ✅ 总结一句话:
        1. 1.3.6.1. 2.2.6 Mapper层
      7. 1.3.7. 2.3 功能测试
        1. 1.3.7.1. 2.3.1 接口文档测试
      8. 1.3.8. 2.4 代码完善
      9. 1.3.9. 2.5 代码提交
    4. 1.4. 3. 启用禁用员工账号
      1. 1.4.1. 3.1 需求分析与设计
        1. 1.4.1.1. 3.1.1 产品原型
        2. 1.4.1.2. 3.1.2 接口设计
      2. 1.4.2. 3.2 代码开发
        1. 1.4.2.1. 3.2.1 Controller层
        2. 1.4.2.2. 3.2.2 Service层接口
        3. 1.4.2.3. 3.2.3 Service层实现类
        4. 1.4.2.4. 3.2.4 Mapper层
      3. 1.4.3. 3.3 功能测试
        1. 1.4.3.1. 3.3.1 接口文档测试
        2. 1.4.3.2. 3.3.2 前后端联调测试
      4. 1.4.4. 3.4 代码提交
    5. 1.5. 4. 编辑员工
      1. 1.5.1. 4.1 需求分析与设计
        1. 1.5.1.1. 4.1.1 产品原型
        2. 1.5.1.2. 4.1.2 接口设计
      2. 1.5.2. 4.2 代码开发
        1. 1.5.2.1. 4.2.1 回显员工信息功能
        2. 1.5.2.2. 4.2.2 修改员工信息功能
      3. 1.5.3. 4.3 功能测试
        1. 1.5.3.1. 4.3.1 接口文档测试
      4. 1.5.4. 4.4 代码提交
    6. 1.6. 5. 导入分类模块功能代码
      1. 1.6.1. 5.1 需求分析与设计
        1. 1.6.1.1. 5.1.1 产品原型