员工管理、分类管理
新增员工
需求分析和设计
产品原型
业务规则
- 账号必须是唯一的。
- 手机号为合法的11位手机号码。
- 身份证号为合法的18位身份证号码。
- 密码默认为123456。
接口设计
- 本项目约定:管理端发出的请求,统一使用/admin作为前缀,用户端发出的请求,统一使用/user作为前缀。
数据库设计(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 |
代码开发
根据新增员工接口设计对应的DTO
@Data
public class EmployeeDTO implements Serializable {
private Long id;
private String username;
private String name;
private String phone;
private String sex;
private String idNumber;
}
注意:当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据。
在EmployeeController中创建新增员工方法,接收前端提交的参数
@PostMapping
@ApiOperation("新增员工")
public Result add(@RequestBody EmployeeDTO employeeDTO) {
log.info("新增员工:{}", employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}
在EmployeeService接口中声明新增员工方法
void save(EmployeeDTO employeeDTO);
在EmployeeServiceImpl中实现新增员工方法
@Override
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);
}
在EmployeeMapper中声明insert方法
@Insert("insert into employee (name, username, password, phone, sex, id_number, status, create_time, " +
"update_time, create_user, update_user) VALUES (#{name}, #{username}, #{password}, #{phone}, " +
"#{sex}, #{idNumber}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
void insert(Employee employee);
功能测试
功能测试方式
- 通过接口文档测试。
- 通过前后端联调测试。
注意:由于开发阶段前端和后端是并行开发的,后端完成某个功能后,此时前端对应的功能可能还没有开发完成,导致无法进行前后端联调测试。所以在开发阶段,后端测试主要以接口文档测试为主。
接口文档测试
- 通过Swagger接口文档进行功能测试。
- 调用员工登录接口获得一个合法的JWT令牌,再添加到全局参数中,就可以正常进行测试了。
代码完善
录入的用户名已存在,抛出异常后没有处理
- 在全局异常处理器中进行处理:
//GlobalExceptionHandler.java中
@ExceptionHandler
public Result usernameExistHandler(SQLIntegrityConstraintViolationException ex) {
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);
}
}
新增员工时,创建人id和修改人id设置为了固定值
- 通过ThreadLocal来保存和获取当前登录员工id。
ThreadLocal
- ThreadLocal 并不是一个Thread,而是Thread的局部变量。
- ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法
方法 | 说明 |
---|---|
public void set(T value) | 设置当前线程的线程局部变量的值 |
public T get() | 返回当前线程所对应的线程局部变量的值 |
public void remove() | 移除当前线程的线程局部变量 |
- 注意:客户端发送的每次请求,后端的Tomcat服务器都会分配一个单独的线程来处理请求。
代码完善
- 初始工程中已经封装了 ThreadLocal 操作的工具类:
//BaseContext.java中
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,并放入线程局部变量中:
//JwtTokenAdminInterceptor中
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try{
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
BaseContext.setCurrentId(empId); //在拦截器中解析出当前登录员工id,并放入线程局部变量中
//3、通过,放行
return true;
}catch (Exception ex){
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
- 在Service中获取线程局部变量中的值:
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
//属性拷贝
BeanUtils.copyProperties(employeeDTO, employee);
//账号状态默认为1,正常状态
employee.setStatus(StatusConstant.ENABLE);
//默认密码为123456
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//创建人、创建时间、修改人、修改时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
employee.setCreateUser(BaseContext.getCurrentId()); //从线程局部变量中当前登录员工id并存入新增员工对象
employee.setUpdateUser(BaseContext.getCurrentId()); //从线程局部变量中当前登录员工id并存入新增员工对象
employeeMapper.insert(employee);
}
员工分页查询
需求分析和设计
产品原型
业务规则
- 根据页码展示员工信息。
- 每页展示10条数据。
- 分页查询时可以根据需要,输入员工姓名进行查询。
接口设计
代码开发
根据分页查询接口设计对应的DTO
@Data
public class EmployeePageQueryDTO implements Serializable {
//员工姓名
private String name;
//页码
private int page;
//每页显示记录数
private int pageSize;
}
所有的分页查询,统一都封装成PageResult对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
private long total; //总记录数
private List records; //当前页数据集合
}
- 员工信息分页查询后端返回的对象类型为:Result<PageResult>
根据接口定义创建分页查询方法
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {
log.info("员工分页查询:{}", employeePageQueryDTO);
PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}
在EmployeeService接口中声明pageQuery方法
PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
在 EmployeeServiceImpl 中实现 pageQuery 方法
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
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 的拦截器实现。
在 EmployeeMapper 中声明 pageQuery 方法
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
在 EmployeeMapper.xml 中编写SQL
<select id="pageQuery" 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>
功能测试
- 可以通过接口文档进行测试,最后完成前后端联调测试即可。
代码完善
操作时间字段展示有问题
解决方式一
- 在属性上加入注解,对日期进行格式化:
//employee.java中
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
解决方式二
- 在 WebMvcConfiguration 中扩展Spring MVC的消息转换器,统一对日期类型进行格式化处理:
/**
* 扩展Spring MVC框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//为消息转换器设置一个对象转换器,将java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器加入到容器中,并将索引设置为0
converters.add(0, converter);
}
启用禁用员工账号
产品原型
业务规则
- 可以对状态为“启用”的员工账号进行“禁用”操作。
- 可以对状态为“禁用”的员工账号进行“启用”操作。
- 状态为“禁用”的员工账号不能登录系统。
接口设计
代码开发
根据接口设计中的请求参数形式对应的在 EmployeeController 中创建启用禁用员工账号的方法
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status, Long id) {
log.info("启用禁用员工账号:{},{}", status, id);
employeeService.startOrStop(status, id);
return Result.success();
}
在 EmployeeService 接口中声明启用禁用员工账号的业务方法
void startOrStop(Integer status, Long id);
在 EmployeeServiceImpl 中实现启用禁用员工账号的业务方法
@Override
public void startOrStop(Integer status, Long id) {
Employee employee = Employee.builder()
.status(status)
.id(id)
.updateTime(LocalDateTime.now())
.updateUser(BaseContext.getCurrentId())
.build();
employeeMapper.update(employee);
}
在 EmployeeMapper 接口中声明 update 方法
void update(Employee employee);
在 EmployeeMapper.xml 中编写SQL
<update id="update">
update employee
<set>
<if test="username != null">username = #{username},</if>
<if test="name != null">name = #{name},</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="status != null">status = #{status},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="createUser != null">create_user = #{createUser},</if>
<if test="updateUser != null">update_user = #{updateUser},</if>
</set>
where id = #{id}
功能测试
- 可以通过接口文档进行测试,最后完成前后端联调测试即可。
编辑员工
产品原型
接口设计
- 编辑员工功能涉及到两个接口:根据id查询员工信息、编辑员工信息。
根据id查询员工信息
编辑员工信息
代码开发
在 EmployeeController 中创建 getById 方法
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<Employee> getById(@PathVariable Long id) {
log.info("根据id查询员工信息:{}", id);
Employee employee = employeeService.getById(id);
return Result.success(employee);
}
在 EmployeeService 接口中声明 getById 方法
Employee getById(Long id);
在 EmployeeServiceImpl 中实现 getById 方法
@Override
public Employee getById(Long id) {
Employee employee = employeeMapper.getById(id);
return employee;
}
在 EmployeeMapper 接口中声明 getById 方法
@Select("select * from employee where id = #{id}")
Employee getById(Long id);
- 可以先通过接口测试确认数据回显是否有问题,如果没有问题再继续开发。
在 EmployeeController 中创建 update 方法
@PutMapping
@ApiOperation("编辑员工信息")
public Result update(@RequestBody EmployeeDTO employeeDTO) {
log.info("编辑员工信息:{}", employeeDTO);
employeeService.update(employeeDTO);
return Result.success();
}
在 EmployeeService 接口中声明 update 方法
void update(EmployeeDTO employeeDTO);
在 EmployeeServiceImpl 中实现 update 方法
@Override
public void update(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.update(employee);
}
功能测试
- 可以通过接口文档进行测试,最后完成前后端联调测试即可。
导入分类模块功能代码
需求分析和设计
产品原型
业务规则
- 分类名称必须是唯一的。
- 分类按照类型可以分为菜品分类和套餐分类。
- 新添加的分类状态默认为“禁用”。
接口设计
有以下接口:
- 新增分类
- 分类分页查询
- 根据id删除分类
- 修改分类
- 启用禁用分类
- 根据类型查询分类
数据库设计(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 |
代码导入
- 导入资料中的分类管理模块功能代码即可。
功能测试
- 直接进行前后端联调测试即可。