二刷苍穹外卖 day02
新增员工
DTO
将前端传递的参数列表通过对应的实体类接收
当前端提交的数据和实体类中对应的属性差别较大时,使用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;}
记得要implements Serializable,实现接口后,对象就具备了可序列化的能力
controller
/** * 新增员工 */
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO)
{ log.info("新增员工:{}",employeeDTO); employeeService.save(employeeDTO); return Result.success();
}
Result定义了后端的返回结果格式
Result
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; } }
Result是一个通用的后端统一返回结果封装类,用于向前端返回统一格式的响应数据
泛型参数表示返回的数据类型,可以是任何对象
属性:
code 相应状态码,1表示成功,0或其他表示失败
msg:描述错误信息,成功则为空
data:封装返回给前端的具体数据,类型为泛型T
方法:
success()
只返回code=1
success(T object):静态方法,返回包含具体数据的成功响应(code=1,data=object)
error(String msg):返回错误信息和失败状态码
public static <T> Result<T>
是一个Java泛型方法的声明,前一个表明了泛型方法的类型参数声明部分
Result表示该方法返回一个Result类型的对象
static表示这是一个类方法,可通过类名调用
@Service
@Override
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.setCreateUser(10L);employeeMapper.insert(employee);
}
注意一下BeanUtils.copyProperties()方法,以后用的很多
用于将一个 JavaBean 对象(源对象)的属性值复制到另一个 JavaBean 对象(目标对象)中。这里它将 EmployeeDTO
对象 employeeDTO
的属性值复制到 Employee
对象 employee
中。通常,源对象和目标对象需有相同或兼容的属性名及类型,这样才能正确复制属性值,比如 EmployeeDTO
中的 name
属性若与 Employee
中的 name
属性类型匹配,就会将 employeeDTO
的 name
值复制到 employee
的 name
属性中 。
mapper
@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中已经开启了驼峰命名,所以二者可以对应
mybatis:configuration:#开启驼峰命名map-underscore-to-camel-case: true
问题一 重名
使用全局异常处理器来解决问题
@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); }
}
@ExceptionHandler
是一个注解,该方法用于处理特定类型的异常。比如在上述代码中,标记的方法 exceptionHandler
专门处理 SQLIntegrityConstraintViolationException
类型的异常。当程序执行过程中抛出此类型异常时,就会执行被 @ExceptionHandler
标记的方法,根据异常信息进行相应处理,比如提取异常信息中的用户名,返回特定的错误提示给前端,告知用户用户名已存在等。
问题二:
新增员工时,我们需要获得当前登录员工的id
使用JWT 令牌并相应给前端
当前端指令请求时,它所携带的JWT令牌可以解析出对应员工的登录id;
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); //3、通过,放行 return true; } catch (Exception ex) { //4、不通过,响应401状态码 response.setStatus(401); return false; }
}
在拦截器中已经解析出了员工的id
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token); Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
第一行调用JwtUtil工具类的parseJWT方法,使用配置文件中的管理员密钥对token令牌进行解析,将解析后的对象(包括id)存储在claim对象中
通过JwtClaimsContant.EMP_ID这个常量作为键,获取对应的用户ID,得到字符串之后再转换为Long类型
ThreadLocal
ThreadLocal是为每一个线程提供的一个单独的存储空间,具有线程隔离的效果,其他线程无法访问
常用方法
public void set(T value)设置
public T get()获取
public void remove()
解决流程
在初始工程中已经封装了对应类
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();
}
在一个多线程的 Web 应用中,不同的请求线程可能需要各自独立的用户 ID 标识。可以在某个请求线程开始处理业务时,调用setCurrentId
方法设置该线程的用户 ID,在处理业务过程中通过getCurrentId
方法随时获取该线程的用户 ID,处理完成后调用removeCurrentId
方法清理资源。
在拦截器中存储empid
try {//.................Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:", empId);/将用户id存储到ThreadLocalBaseContext.setCurrentId(empId);//3、通过,放行return true;
在service中获取局部变量的值
public void save(EmployeeDTO employeeDTO) {//.............................//设置当前记录创建人id和修改人idemployee.setCreateUser(BaseContext.getCurrentId());employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.insert(employee);}
员工分页查询
注意:
请求参数类型为Query,在路径后拼接/admin/employee/page?name=zhangsan
返回数据中records数组使用Employee实体类对属性进行了封装
Controller
@GetMapping("/page")@ApiOperation("员工分页查询")public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){log.info("员工分页查询,参数为:{}", employeePageQueryDTO);PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);//后续定义return Result.success(pageResult);}
方法返回值类型为Result<PageResult>
,Result
是自定义的用于封装响应结果的类,PageResult
则可能是包含分页数据的类。
PageResult是一个专用的数据载体
long total 总记录数
List records 当前页实际查询到的数据集合
Result
是泛型的具体类型实例,data字段的类型是PageResult
service
@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);
}
mappe层
<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>
查询名字中间包含name的,不需要limit,分页由pagehelper插件进行处理
操作时间字段问题
在WebMvcConfiguration中扩展SpringMVC消息转换器
Spring Boot 默认使用 MappingJackson2HttpMessageConverter
处理 JSON 序列化 / 反序列化,但 Jackson 对日期类型的默认序列化规则会导致格式异常:
- 若实体类中是
Date
、LocalDateTime
等日期类型,Jackson 默认可能将其序列化为 时间戳 或 拆分的数组(如[年, 月, 日, 时, 分, 秒]
,与前端期望的格式(如2022524112024
)不匹配。 - 将自定义的
MappingJackson2HttpMessageConverter
放到转换器列表的第一位,确保优先使用(覆盖默认的 Jackson 转换器)。
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { log.info("扩展消息转换器..."); //创建一个消息转换器对象 MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); //需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据 converter.setObjectMapper(new JacksonObjectMapper()); //将自己的消息转化器加入容器内 converters.add(0, converter);
}
启用禁用员工账号
Controller
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status,@RequestParam("id") Long id // 显式声明id来自请求参数
)
{ log.info("启用禁用员工账号"); employeeService.startOrStop(status,id); return Result.success();
}
注意讲义中当前方法中 Long id
参数未显式声明参数来源。在 Spring MVC 中,未标注@PathVariable
/@RequestBody
等注解的参数默认会尝试从请求参数(@RequestParam
)获取,但如果请求中未传递id
参数,会直接抛出MissingServletRequestParameterException
(400 错误)。建议修改
编辑员工
回显员工信息
controller
/** * 根据id查询员工信息 */
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<Employee> getById(@PathVariable Long id) { Employee employee = employeeService.getById(id); return Result.success(employee);
}
service层
@Override
public Employee getById(Long id) { Employee employee = employeeMapper.getById(id); employee.setPassword("****"); return employee;
}
密码主动修改很细节
修改信息
service
注意这里不能用builder(),因为builder只能是创建对象的时候使用
public void update(EmployeeDTO employeeDTO) { Employee employee = new Employee(); BeanUtils.copyProperties(employeeDTO, employee); employee.setUpdateTime(LocalDateTime.now()); employee.setUpdateUser(BaseContext.getCurrentId()); employeeMapper.update(employee); }
在上一个功能中已经实现了可泛用的mapper,这里不需要再写了
导入代码
CategoryMapper.xml文件中注意到一个点
select 语句中使用where if test时需要"and",表示&
在update语句中set里不需要写表示| ,有就满足
<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>
总结
1.DTO:
用来接受前端提交的数据以及不同层之间传递
当与实体类差别较大时使用
2.Result
通用后端统一返回结果封装类,向前端返回统一格式的响应数据
3.public static Result
是一个Java泛型方法的声明,前一个表明了泛型方法的类型参数声明部分
Result表示该方法返回一个Result类型的对象
static表示这是一个类方法,可通过类名调用
4.BeanUtils.copyProperties()
一般将DTO赋值给对应的对象
5.重名
ExceptionHandler注解,当程序抛出对应异常时,会执行被@ExceptionHandler标记的方法
6.JWT令牌
通过JwTUtil.parseJWT可以解析令牌
7.ThreadLocal
仅本线程可用存储空间
set get remove
8.分页查询
PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
第一个参数employeePageQueryDTO.getPage()
获取到的是要查询的页码,第二个参数employeePageQueryDTO.getPageSize()
获取到的是每页显示的记录数
方法返回值类型为Result
PageResult是专门的数据载体,包括List records,即当前页查询到的数据集合
records
9.操作时间字段
更换自定义的转换器覆盖原有
10.启用禁用员工账号
未表明数据来源会默认从请求参数(@RequestParam)获取,即带有查询参数(Query Parameters)的 URL 链接?xx=xx
11.xml文件 if条件
在select时要保证全满足,即&,所以if 后要加"and"
在update时满足一个修改一个,即 |,所以If 后不用加