# day02 项目内容新增员工 员工分页查询 启用禁用员工账号 编辑员工 导入分类模块功能代码 # 新增员工# 需求分析和设计# 产品原型一般在做需求分析时,往往都是对照着产品原型进行分析,因为产品原型比较直观,便于我们理解业务。
后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。
当填写完表单信息,点击 "保存" 按钮后,会提交该表单的数据到服务端,在服务端中需要接受数据,然后将数据保存至数据库中。
注意事项:
账号必须是唯一的 手机号为合法的 11 位手机号码 身份证号为合法的 18 位身份证号码 密码默认为 123456 # 接口设计找到资料 --> 项目接口文档 --> 苍穹外卖 - 管理端接口.html
明确新增员工接口的请求路径、请求方式、请求参数、返回数据 。
本项目约定:
管理端 发出的请求,统一使用 /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
其中,employee 表中的 status 字段已经设置了默认值 1,表示状态正常。
# 代码开发# 设计 DTO 类根据新增员工接口设计对应的 DTO
前端传递参数列表:
思考:
是否可以使用对应的实体类来接收呢?
以下是 sky-pojo 下 entity 中的 employee 实体类,我们可以看出实体类中的属性要比前端传过来的属性多。
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 @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Employee implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String username; private String name; private String password; private String phone; private String sex; private String idNumber; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; private Long createUser; private Long updateUser; }
[!NOTE]
当前端提交的数据和实体类中对应的属性差别比较大时,建议使用 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; }
# Controller 层EmployeeController 中创建新增员工方法
进入到 sky-server 模块中,在 com.sky.controller.admin 包下,在 EmployeeController 中创建新增员工方法,接收前端提交的参数。
1 2 3 4 5 6 7 8 9 10 11 12 @PostMapping @ApiOperation("新增员工") public Result save (@RequestBody EmployeeDTO employeeDTO) { log.info("新增员工:{}" ,employeeDTO); employeeService.save(employeeDTO); return Result.success(); }
[!CAUTION]
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;@Data public class Result <T> implements Serializable { private Integer code; 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; } }
# Service 层接口在 EmployeeService 接口中声明新增员工方法
进入到 sky-server 模块中,com.sky.server.EmployeeService
1 2 3 4 5 void save (EmployeeDTO employeeDTO) ;
# 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 public void save (EmployeeDTO employeeDTO) { Employee employee = new Employee (); BeanUtils.copyProperties(employeeDTO, employee); employee.setStatus(StatusConstant.ENABLE); employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes())); employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); employee.setCreateUser(10L ); 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 ; }
# Mapper 层在 EmployeeMapper 中声明 insert 方法
com.sky.EmployeeMapper 中添加方法
1 2 3 4 5 6 7 8 @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
# 功能测试代码已经发开发完毕,对新增员工功能进行测试。
功能测试实现方式:
接下来我们使用上述两种方式分别测试。
# 接口文档测试启动服务:
访问 http://localhost:8080/doc.html,进入新增员工接口
json 数据:
1 2 3 4 5 6 7 8 { "id" : 0 , "idNumber" : "452601198307190014" , "name" : "大纱布" , "phone" : "15223422134" , "sex" : "1" , "username" : "小智" }
如果 401 报错,是我们的令牌过期了,这是因为我们在 application.yml 文件里设置了过期时间为两个小时
通过断点调试:
进入到 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 public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true ; } String token = request.getHeader(jwtProperties.getAdminTokenName()); 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); return true ; } catch (Exception ex) { response.setStatus(401 ); return false ; } }
报错原因:
由于 JWT 令牌校验失败,导致 EmployeeController 的 save 方法没有被调用
解决方法:
调用员工登录接口获得一个合法的 JWT 令牌
使用 admin 用户登录获取令牌
添加令牌:
将合法的 JWT 令牌添加到全局参数中
文档管理 --> 全局参数设置 --> 添加参数
接口测试:
添加完全局参数后,一定要关闭之前的新增员工调试窗口,重新打开,然后你会发现在请求头部有红色数字 1 的提示,证明 token 已经添加到请求头部
响应码 200,测试成功
查看数据库,成功插入新员工
# 前后端联调测试启动 nginx, 访问 http://localhost
登录 --> 员工管理 --> 添加员工
保存后,查看 employee 表
测试成功。
[!CAUTION]
由于开发阶段前端和后端是并行开发的,后端完成某个功能后,此时前端对应的功能可能还没有开发完成,导致无法进行前后端联调测试。所以在开发阶段,后端测试主要以接口文档测试为主。
# 代码完善目前,程序存在的问题主要有两个:
录入的用户名已存,抛出的异常后没有处理 新增员工时,创建人 id 和修改人 id 设置为固定值 接下来,我们对上述两个问题依次进行分析和解决。
# 问题一描述:
录入的用户名已存,抛出的异常后没有处理
分析:
新增 username=dashabu 的用户,若 employee 表中之前已存在。
后台报错信息:
查看 employee 表结构后,我们发现 username 为 unique
解决:
通过全局异常处理器来处理。
进入到 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 @ExceptionHandler public Result exceptionHandler (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); } }
进入到 sky-common 模块,在 MessageConstant.java 添加
1 public static final String ALREADY_EXISTS = "已存在" ;
再次,接口测试:
# 问题二描述:
新增员工时,创建人 id 和修改人 id 设置为固定值
分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void save (EmployeeDTO employeeDTO) { Employee employee = new Employee (); employee.setCreateUser(10L ); employee.setUpdateUser(10L ); employeeMapper.insert(employee); }
解决:
通过某种方式动态获取当前登录员工的 id。
员工登录成功后会生成 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; @PostMapping("/login") @ApiOperation(value = "员工登录") public Result<EmployeeLoginVO> login (@RequestBody EmployeeLoginDTO employeeLoginDTO) { 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;@Component @Slf4j public class JwtTokenAdminInterceptor implements HandlerInterceptor { @Autowired private JwtProperties jwtProperties; public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader(jwtProperties.getAdminTokenName()); 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); return true ; } catch (Exception ex) { response.setStatus(401 ); return false ; } } }
思考:
解析出登录员工 id 后,如何传递给 Service 的 save 方法?
通过 ThreadLocal 进行传递。
# ThreadLocal介绍:
ThreadLocal 并不是一个 Thread,而是 Thread 的局部变量。 ThreadLocal 为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
常用方法:
public void set (T value) 设置当前线程的线程局部变量的值 public T get () 返回当前线程所对应的线程局部变量的值 public void remove () 移除当前线程的线程局部变量 对 ThreadLocal 有了一定认识后,接下来继续解决问题二
[!CAUTION]
客户端发送的每次请求,后端的 Tomcat 服务器都会分配一个单独的线程来处理请求
初始工程中已经封装了 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;@Component @Slf4j public class JwtTokenAdminInterceptor implements HandlerInterceptor { @Autowired private JwtProperties jwtProperties; public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token); Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString()); log.info("当前员工id:" , empId); BaseContext.setCurrentId(empId); return true ; } catch (Exception ex) { } } }
在 Service 中获取线程局部变量中的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void save (EmployeeDTO employeeDTO) { employee.setCreateUser(BaseContext.getCurrentId()); employee.setUpdateUser(BaseContext.getCurrentId()); employeeMapper.insert(employee); }
测试:使用 admin (id=1) 用户登录后添加一条记录
查看 employee 表记录
# 员工分页查询# 需求分析和设计# 产品原型系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。而在我们的分页查询页面中,除了分页条件以外,还有一个查询条件 "员工姓名"。
业务规则 :
根据页码展示员工信息 每页展示 10 条数据 分页查询时可以根据需要,输入员工姓名进行查询 # 接口设计找到资料 --> 项目接口文档 --> 苍穹外卖 - 管理端接口.html
注意事项:
请求参数类型为 Query,不是 json 格式提交,在路径后直接拼接。/admin/employee/page?name=zhangsan 返回数据中 records 数组中使用 Employee 实体类对属性进行封装。 # 代码开发# 设计 DTO 类根据请求参数进行封装,在 sky-pojo 模块中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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; }
后面所有的分页查询,统一都封装为 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 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>
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;@Data public class Result <T> implements Serializable { private Integer code; 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; } }
# Controller 层在 sky-server 模块中,com.sky.controller.admin.EmployeeController 中添加分页查询方法。
1 2 3 4 5 6 7 8 9 10 11 12 @GetMapping("/page") @ApiOperation("员工分页查询") public Result<PageResult> page (EmployeePageQueryDTO employeePageQueryDTO) { log.info("员工分页查询,参数为:{}" , employeePageQueryDTO); PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO); return Result.success(pageResult); }
# Service 层接口在 EmployeeService 接口中声明 pageQuery 方法:
1 2 3 4 5 6 PageResult pageQuery (EmployeePageQueryDTO employeePageQueryDTO) ;
# Service 层实现类在 EmployeeServiceImpl 中实现 pageQuery 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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); }
[!IMPORTANT]
此处使用 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 >
# Mapper 层在 EmployeeMapper 中声明 pageQuery 方法:
1 2 3 4 5 6 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 >
# 功能测试测试步骤和之前类似,可以通过接口文档进行测试,也可以进行前后端联调测试。
但是在测试过程中我们发现
时间格式不对
# 代码完善问题描述:操作时间字段显示有问题。
解决方式:
1). 方式一
在属性上加上注解,对日期进行格式化
但这种方式,需要在每个时间属性上都要加上该注解,使用较麻烦,不能全局处理。
2). 方式二(推荐 )
在 WebMvcConfiguration 中扩展 SpringMVC 的消息转换器,统一对日期类型进行格式处理
1 2 3 4 5 6 7 8 9 10 11 12 13 protected void extendMessageConverters (List<HttpMessageConverter<?>> converters) { log.info("扩展消息转换器..." ); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter (); converter.setObjectMapper(new JacksonObjectMapper ()); converters.add(0 ,converter); }
添加后,再次测试
时间格式定义,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" ; } }
# 启用禁用员工账号# 需求分析与设计# 产品原型
业务规则:
可以对状态为 “启用” 的员工账号进行 “禁用” 操作 可以对状态为 “禁用” 的员工账号进行 “启用” 操作 状态为 “禁用” 的员工账号不能登录系统 # 接口设计
1). 路径参数携带状态值。
2). 同时,把 id 传递过去,明确对哪个用户进行操作。
3). 返回数据 code 状态是必须,其它是非必须。
# 代码开发# Controller 层在 sky-server 模块中,根据接口设计中的请求参数形式对应的在 EmployeeController 中创建启用禁用员工账号的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 @PostMapping("/status/{status}") @ApiOperation("启用禁用员工账号") public Result startOrStop (@PathVariable Integer status,Long id) { log.info("启用禁用员工账号:{},{}" ,status,id); employeeService.startOrStop(status,id); return Result.success(); }
# Service 层接口在 EmployeeService 接口中声明启用禁用员工账号的业务方法:
1 2 3 4 5 6 void startOrStop (Integer status, Long id) ;
# Service 层实现类在 EmployeeServiceImpl 中实现启用禁用员工账号的业务方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void startOrStop (Integer status, Long id) { Employee employee = Employee.builder() .status(status) .id(id) .build(); employeeMapper.update(employee); }
# Mapper 层在 EmployeeMapper 接口中声明 update 方法:
1 2 3 4 5 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 >
# 功能测试
# 编辑员工# 需求分析与设计# 产品原型在员工管理列表页面点击 "编辑" 按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击 "保存" 按钮完成编辑操作。
员工列表原型:
修改页面原型 :
注:点击修改时,数据应该正常回显到修改页面。
# 接口设计根据上述原型图分析,编辑员工功能涉及到两个接口:
1). 根据 id 查询员工信息
2). 编辑员工信息
注:因为是修改功能,请求方式可设置为 PUT。
# 代码开发# 回显员工信息功能# Controller 层在 EmployeeController 中创建 getById 方法:
1 2 3 4 5 6 7 8 9 10 11 @GetMapping("/{id}") @ApiOperation("根据id查询员工信息") public Result<Employee> getById (@PathVariable Long id) { Employee employee = employeeService.getById(id); return Result.success(employee); }
# Service 层接口在 EmployeeService 接口中声明 getById 方法:
1 2 3 4 5 6 Employee getById (Long id) ;
# Service 层实现类在 EmployeeServiceImpl 中实现 getById 方法:
1 2 3 4 5 6 7 8 9 10 11 public Employee getById (Long id) { Employee employee = employeeMapper.getById(id); employee.setPassword("****" ); return employee; }
# Mapper 层在 EmployeeMapper 接口中声明 getById 方法:
1 2 3 4 5 6 7 @Select("select * from employee where id = #{id}") Employee getById (Long id) ;
# 修改员工信息功能# Controller 层在 EmployeeController 中创建 update 方法:
1 2 3 4 5 6 7 8 9 10 11 12 @PutMapping @ApiOperation("编辑员工信息") public Result update (@RequestBody EmployeeDTO employeeDTO) { log.info("编辑员工信息:{}" , employeeDTO); employeeService.update(employeeDTO); return Result.success(); }
# Service 层接口在 EmployeeService 接口中声明 update 方法:
1 2 3 4 5 void update (EmployeeDTO employeeDTO) ;
# Service 层实现类在 EmployeeServiceImpl 中实现 update 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 层代码。
# 功能测试
# 导入分类模块功能代码# 需求分析与设计# 产品原型后台系统中可以管理分类信息,分类包括两种类型,分别是 菜品分类 和 套餐分类 。
先来分析菜品分类 相关功能。
新增菜品分类:
当我们在后台系统中添加菜品时需要选择一个菜品分类,在移动端也会按照菜品分类来展示对应的菜品。
菜品分类分页查询:
系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
根据 id 删除菜品分类:
在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。
修改菜品分类:
在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作。
启用禁用菜品分类 :
在分类管理列表页面,可以对某个分类进行启用或者禁用操作。
分类类型查询:
当点击分类类型下拉框时,从数据库中查询所有的菜品分类数据进行展示。
分类管理原型:
业务规则:
分类名称必须是唯一的 分类按照类型可以分为菜品分类和套餐分类 新添加的分类状态默认为 “禁用” # 接口设计根据上述原型图分析,菜品分类模块共涉及 6 个接口。
新增分类 分类分页查询 根据 id 删除分类 修改分类 启用禁用分类 根据类型查询分类 接下来,详细地分析每个接口。
找到资料 --> 项目接口文档 --> 苍穹外卖 - 管理端接口.html
1). 新增分类
2). 分类分页查询
3). 根据 id 删除分类
4). 修改分类
5). 启用禁用分类
6). 根据类型查询分类
# 表设计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
# 代码导入导入资料中的分类管理模块功能代码即可
可按照 mapper-->service-->controller 依次导入,这样代码不会显示相应的报错。
进入到 sky-server 模块中
# Mapper 层DishMapper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.sky.mapper;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Select;@Mapper public interface DishMapper { @Select("select count(id) from dish where category_id = #{categoryId}") Integer countByCategoryId (Long categoryId) ; }
SetmealMapper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.sky.mapper;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Select;@Mapper public interface SetmealMapper { @Select("select count(id) from setmeal where category_id = #{categoryId}") Integer countByCategoryId (Long id) ; }
CategoryMapper.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 44 45 46 47 48 49 50 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 { @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) ; Page<Category> pageQuery (CategoryPageQueryDTO categoryPageQueryDTO) ; @Delete("delete from category where id = #{id}") void deleteById (Long id) ; void update (Category category) ; List<Category> list (Integer type) ; }
CategoryMapper.xml , 进入到 resources/mapper 目录下
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 44 45 46 47 48 49 50 51 52 53 <?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 >
# Service 层CategoryService.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 44 45 46 47 48 49 50 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 { void save (CategoryDTO categoryDTO) ; PageResult pageQuery (CategoryPageQueryDTO categoryPageQueryDTO) ; void deleteById (Long id) ; void update (CategoryDTO categoryDTO) ; void startOrStop (Integer status, Long id) ; List<Category> list (Integer type) ; }
EmployeeServiceImpl.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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 package com.sky.service.impl;import com.github.pagehelper.Page;import com.github.pagehelper.PageHelper;import com.sky.constant.MessageConstant;import com.sky.constant.PasswordConstant;import com.sky.constant.StatusConstant;import com.sky.context.BaseContext;import com.sky.dto.EmployeeDTO;import com.sky.dto.EmployeeLoginDTO;import com.sky.dto.EmployeePageQueryDTO;import com.sky.entity.Employee;import com.sky.exception.AccountLockedException;import com.sky.exception.AccountNotFoundException;import com.sky.exception.PasswordErrorException;import com.sky.mapper.EmployeeMapper;import com.sky.result.PageResult;import com.sky.service.EmployeeService;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.util.DigestUtils;import java.time.LocalDateTime;import java.util.List;@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeMapper employeeMapper; public Employee login (EmployeeLoginDTO employeeLoginDTO) { String username = employeeLoginDTO.getUsername(); String password = employeeLoginDTO.getPassword(); Employee employee = employeeMapper.getByUsername(username); if (employee == null ) { throw new AccountNotFoundException (MessageConstant.ACCOUNT_NOT_FOUND); } password = DigestUtils.md5DigestAsHex(password.getBytes()); if (!password.equals(employee.getPassword())) { throw new PasswordErrorException (MessageConstant.PASSWORD_ERROR); } if (employee.getStatus() == StatusConstant.DISABLE) { throw new AccountLockedException (MessageConstant.ACCOUNT_LOCKED); } return employee; } public void save (EmployeeDTO employeeDTO) { Employee employee = new Employee (); BeanUtils.copyProperties(employeeDTO, employee); employee.setStatus(StatusConstant.ENABLE); employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes())); employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); employee.setCreateUser(BaseContext.getCurrentId()); employee.setUpdateUser(BaseContext.getCurrentId()); employeeMapper.insert(employee); } 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); } public void startOrStop (Integer status, Long id) { Employee employee = Employee.builder() .status(status) .id(id) .build(); employeeMapper.update(employee); } public Employee getById (Long id) { Employee employee = employeeMapper.getById(id); employee.setPassword("****" ); return employee; } public void update (EmployeeDTO employeeDTO) { Employee employee = new Employee (); BeanUtils.copyProperties(employeeDTO, employee); employee.setUpdateTime(LocalDateTime.now()); employee.setUpdateUser(BaseContext.getCurrentId()); employeeMapper.update(employee); } }
# Controller 层CategoryController.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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 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; @PostMapping @ApiOperation("新增分类") public Result<String> save (@RequestBody CategoryDTO categoryDTO) { log.info("新增分类:{}" , categoryDTO); categoryService.save(categoryDTO); return Result.success(); } @GetMapping("/page") @ApiOperation("分类分页查询") public Result<PageResult> page (CategoryPageQueryDTO categoryPageQueryDTO) { log.info("分页查询:{}" , categoryPageQueryDTO); PageResult pageResult = categoryService.pageQuery(categoryPageQueryDTO); return Result.success(pageResult); } @DeleteMapping @ApiOperation("删除分类") public Result<String> deleteById (Long id) { log.info("删除分类:{}" , id); categoryService.deleteById(id); return Result.success(); } @PutMapping @ApiOperation("修改分类") public Result<String> update (@RequestBody CategoryDTO categoryDTO) { categoryService.update(categoryDTO); return Result.success(); } @PostMapping("/status/{status}") @ApiOperation("启用禁用分类") public Result<String> startOrStop (@PathVariable("status") Integer status, Long id) { categoryService.startOrStop(status,id); return Result.success(); } @GetMapping("/list") @ApiOperation("根据类型查询分类") public Result<List<Category>> list (Integer type) { List<Category> list = categoryService.list(type); return Result.success(list); } }
[!IMPORTANT]
全部导入完毕后,进行编译,在 maven 里一定要进行整个项目的编译测试
# 功能测试重启服务,访问 http://localhost:80, 进入分类管理
这里测试过,就不展示了