前言
这些功能都没有展示对应的测试结果,可自行通过接口文档进行测试,也可以进行前后端联调测试,附代码链接:take-out
1新增员工
1.1 需求分析和设计
产品原型
接口设计
【注】code:操作成功返回1,否则返回0,可通过 msg 来设置失败的提示信息,一般表现为前端的弹窗。
数据库设计
1.2 代码开发
根据新增员工接口设计对应的DTO
【拓展】DTO和实体类
DTO(Data Transfer Object):用于数据传输,只包含需要传输和展示的数据
实体类(Entity):用于持久化和业务逻辑的处理,包含数据库中所有字段以及对于的业务逻辑处理方法
EmployeeController.java
/**
* 新增员工
* @param employeeDTO
* @return
*/
//统一用 result 来封装返回结果
//@RequestBody 表明接受的是 json 格式的数据
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO) {
log.info("新增员工:{}",employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}
【拓展】
@GetMapping:将HTTP get 请求映射到特定处理程序的方法注解,
是@RequestMapping(metho = RequestMethod.GET)的缩写
@PostMapping:将HTTP post 请求映射到特定处理程序的方法注解
是@RequestMapping(metho = RequestMethod.POST)的缩写
EmployeeMapper.java
/**
* 插入员工数据
* @param employee
*/
@Insert("insert into employ(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);
EmployeeServiceImpl.java
/**
* 新增员工
* @param employeeDTO
*/
public void save(EmployeeDTO employeeDTO) {
//传入的是DTO,为了方便封装前端提交的数据,但传给持久层,还是建议使用实体类,所以这里需要一个对象的转换,将DTO转换为我们的实体类
Employee employee = new Employee();
//由于DTO里的和Employee里面的属性名一样,所以可以通过对象的属性拷贝来赋值
BeanUtils.copyProperties(employeeDTO, employee);
//状态和初始密码都不希望直接锁死,所以通过常量的方式进行引用
//设置账号的状态,默认正常状态 1表示正常 0表示锁定
employee.setStatus(StatusConstant.ENABLE);
//设置密码,默认密码123456(需要进行MD5码转换,还需要变为Byte数组)
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
//设置当前记录的创建时间和修改时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//设置当前记录的创建人和修改人
//TODO 后期需要改为当前登录用户的ID
employee.setCreateUser(10L);
employee.setUpdateUser(10L);
employeeMapper.insert(employee);
}
1.3 代码完善
程序存在的问题:
- 录入的用户名已存在,抛出异常后没有处理
- 新增员工时,创建人 id 和修改人 id 设置为了固定值
针对第二个问题,需要通过某种方式(生成令牌)动态获取当前登录员工的 id:
解决:
① 员工登录成功后生成JWT令牌并响应给前端
EmployeeController.java:
//登录成功后,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
② 后续请求中,前端会携带JWT令牌,通过JWT令牌解析出当前员工登录id
JwtTokenAdminInterceptor.java:
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token); //log.info() 打印日志
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
【注】通过上述两个步骤可以解析出登录员工的 id,如何将这个 id 传递给 Service 的 save 方法呢?需要借助 ThreadLocal ,它提供了一个存储空间,可以通过这个共享的空间实现消息传递!
在实际过程中,一般将 ThreadLocal 包装为一个工具类 :
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();
}
}
所以完善以后的整体代码如下:
EmployeeServiceImpl.java
//设置当前记录的创建人和修改人
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
JwtTokenAdminInterceptor.java
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是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());
log.info("当前员工id:", empId);
BaseContext.setCurrentId(empId); // 把 id 存到线程的独立空间 ThreadLocal 里面去了
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
1.2 员工分页查询
1.2.1 需求分析和设计
产品原型
接口设计
1.2.2 代码开发
根据分页查询接口设计对应的 DTO
在我们后面所有的分页查询开发中,统一都封装成 PageResult 对象 :
员工信息分页查询后端返回的对象类型为:Result<PageResult>
1.2.3 代码完善
存在问题:最后修改时间所对应的数据格式存在问题
解决方式:
方式一:在属性上加上注解,对日期进行格式化(只能处理单个实体)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
方式二:在 WebMvcConfiguration 中扩展 Spring MVC 的消息转换器,统一对日期类型进行格式化处理
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建一个消息转换器对象
MappingJackson2CborHttpMessageConverter converter = new MappingJackson2CborHttpMessageConverter();
//为消息转换器设置一个对象转换器,对象转换器可以将Java 对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器加入容器中,并排在第一位
converters.add(0,converter);
}
1.3 启用禁用员工账号
1.3.1 需求分析和设计
产品原型
业务规则
状态为启用,可操作为禁用;状态为禁用,可操作为启用。启用状态可登录系统,反之不可。
接口设计
1.3.2 代码开发
EmployeeServiceImpl.java
/**
* 启用禁用员工账号
* @param status
* @param id
*/
@Override
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);
}
EmployeeController.java
/**
* 启用禁用员工账号
* @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();
}
1.4 编辑员工
1.4.1 需求分析和设计
产品原型
编辑员工功能涉及到的两个接口:
① 根据 id 查询员工信息
② 编辑员工信息
1.4.2 代码开发
EmployeeServiceImpl.java
/**
* 根据 id 查询员工
* @param id
* @return
*/
@Override
public Employee getById(Long id) {
Employee employee = employeeMapper.getById(id);
employee.setPassword("****");
return null;
}
/**
* 编辑员工信息
* @param employeeDTO
*/
@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);
}
EmployeeController.java
/**
* 根据 id 查询员工信息
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据 id 查询员工信息")
public Result<Employee> getById(@PathVariable Long id) {
Employee employee = employeeService.getById(id);
return Result.success(employee);
}
/**
* 编辑员工信息
* @param employeeDTO
* @return
*/
@PutMapping
@ApiOperation("编辑员工信息")
//@RequestBody:表明提交过来的是 json 数据
public Result update(@RequestBody EmployeeDTO employeeDTO) {
log.info("编辑员工信息:{}",employeeDTO);
employeeService.update(employeeDTO);
return Result.success();
}
2 导入分类模块功能代码
2.1 需求分析和设计
产品原型
业务规则
- 分类名称必须唯一
- 分类按照类型可以分为菜品分类和套餐分类
- 新添加的分类状态默认为“禁用”
数据库设计表(category)
2.2 代码导入
这部分实现方式和员工管理的内容相似,所以直接提供代码,将其分别导入对应包的下面即可