Java笔记-28、功能实现-属性拷贝、ThreadLocal动态获取id、全局异常处理

新增员工

对象间的属性拷贝

在实现新增员工的过程中,由前端传来的数据与数据库存储数据差别较大,建立了EmployeeDTO类和Employee类。为方便处理对象间的属性拷贝,spring framework提供了:

BeanUtils.copyProperties(Object source, Object target) throws BeansException

该方法通过反射机制实现属性的复制。它遵循以下步骤:

  1. 获取源对象和目标对象的属性:通过反射获取两个对象的属性名。
  2. 匹配属性:将源对象的属性与目标对象的属性进行匹配,通常基于属性名。
  3. 复制值:将源对象中属性的值赋值给目标对象相应的属性。

注意事项:

  1. 属性名匹配:源对象和目标对象的属性名必须一致。如果属性名不匹配,则该属性不会被复制。
  2. 类型匹配:属性类型必须兼容。例如,如果源对象的属性是 int 类型,目标对象的属性应该是 Integer 类型,否则可能会导致转换错误。
  3. 异常处理:该方法会抛出 BeansException 异常,因此需要进行适当的异常处理。
@Override
public void save(EmployeeDTO employeeDTO) {
    Employee employee = new Employee();
    // 1. 属性拷贝
    BeanUtils.copyProperties(employeeDTO, employee);

    // 2. 设置employee中有而employeeDTO没有的属性的值
    // 2.1 设置账号状态
    employee.setStatus(StatusConstant.ENABLE);
    // 2.2 设置密码,默认为123456
    employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
    // 2.3 设置创建时间和修改时间
    employee.setCreateTime(LocalDateTime.now());
    employee.setUpdateTime(LocalDateTime.now());
    // 2.4 设置创建和更新人员id TODO 先写假数据,获取id后再补全
    employee.setCreateUser(10L);
    employee.setUpdateUser(10L);

    // 3. 调用mapper的插入方法
    employeeMapper.insert(employee);
}

调试的注意事项

在后端调试时,由于加入了jwt令牌机制,测试单个请求时,会由于JWT令牌校验失败,导致save方法没被调用。

解决办法:使用员工登录接口获取一个合法的jwt令牌,并在swagger文档管理-->全局参数设置-->添加参数。

代码优化

全局异常处理器处理重复用户名异常

数据库建表时username已经设为unique,如果前端请求带来的数据中username在数据库中不唯一,后端会报错,exception类型为SQLIntegrityConstraintViolationException

但是前端没有任何提示,用户不知道哪里错了,导致添加失败。

需要在全局异常处理器中捕捉异常并返回提示。

在sky-server模块,com.sky.hander包的GlobalExceptionHandler.java中添加方法。

动态获取当前创建人和修改人的id

当前用户登录时,前端提交用户名和密码后,后端会生成JWT Token返回给前端。

之后前端每次请求后端接口,都会在请求头中携带token。

后端在每次请求前都会用注册的自定义拦截器拦截请求,验证jwt token,通过了就会执行业务逻辑,返回数据。不通过就会返回错误信息。

因此,在自定义拦截器中解析出员工id后,如何传递给service的save方法,以达到动态获取当前登录用户的id呢?

通过ThreadLocal进行传递。

ThreadLocal

ThreadLocal 并不是一个Thread,而是Thread的局部变量。

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

常用方法:

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

DBdUkBySWhK78T4LAt3G0qMjsG35Mk-UgK3p5k-OnkE=

在sky-common模块封装工具类,用于操作ThreadLocal存储id:

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,并放入线程局部变量中:

Long employeeId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
BaseContext.setCurrentId(employeeId);

在Service中获取线程局部变量中的值:

// 2.4 设置创建和更新人员id
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
posted @ 2025-04-24 22:14  subeipo  阅读(38)  评论(0)    收藏  举报