Java日记

145 阅读2分钟

自定义元数据处理器(公共字段自动填充)

在接口开发(新增、编辑)中常常会遇到一些公共的业务逻辑

  • 创建时间
  • 修改时间
  • 修改人
  • 创建人
/**
     * 新增员工
     * @param employee
     * @return
     */
    @PostMapping
    public R<String> save(HttpServletRequest request, @RequestBody Employee employee) {
        log.info("新增员工", employee.toString());
        // 设置初始密码
        employee.setPassword(DigestUtils.md5DigestAsHex("12345".getBytes()));
        // 自动填充去处理
//        employee.setCreateTime(LocalDateTime.now());
//        employee.setUpdateTime(LocalDateTime.now());
//
//        // 获取登陆用户id
//        Long empId = (Long)request.getSession().getAttribute("employee");
//        employee.setCreateUser(empId);
//        employee.setUpdateUser(empId);

        employeeService.save(employee);

        return R.success("新增员工成功");
    }
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    // 插入数据时拦截操作
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共字段自动填充【insert】");
        log.info(metaObject.toString());
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        // TODO
        metaObject.setValue("createUser", new Long(当前登陆用户ID));
        metaObject.setValue("updateUser", new Long(当前登陆用户ID));
    }
    // 编辑数据时拦截操作
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段自动填充【update】");
        long id = Thread.currentThread().getId();
        log.info("当前线程ID {}", id);
        log.info(metaObject.toString());
    }
}

如何去处理当前登陆用户ID呢

insertFilter无法注入HttpServelet 也就是说无法拿到httpRequest, 所以五法通过session拿到当前登陆的用户ID

基于线程去处理

客户端每次请求服务端,服务端都会创建一个单独的线程处理该请求,而该请求所经过的每一个类都会是相同的线程,比如:过滤器ServletComponentScan、控制器Controll、Service服务器等

ThreadLocal

ThreadLocal并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal去维护变量时,ThreadLocal为每个使用该变量的线程去提供独立的副本,所以每个线程都可以独立改变自己的副本,而不会影响其他线程对应的副本,ThreadLocal为每个线程提供一个独立的存储空间,具备线程隔离的效果,只有在同一个线程内才能获取到对应的值,线程外不能访问

解决方式

可以通过过滤器拦截该请求判断是否登陆时获取到登陆的用户id,然后再ThreadLocal传给元数据处理器

实现步骤

1、编写BaseContext工具类(基于ThreadLocal封装的工具类) 2、在LoginCheckFilterdoFilter方法中调用BaseContext来设置当前登陆的用户ID 3、在MyMetaObjectHandler方法中调用BaseContext获取登陆用户ID

// BaseContext.java
/**
 * 基于ThreadLocal封装的工具类,用于保存当前登陆用户的ID
 */
public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }
    
    public static Long getCurrentId() {
        return threadLocal.get();
    }
}
// LoginCheckFilter.java
//4、判断登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));

            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);

            // 每个请求都会开辟一个单独的线程,在这个请求所经过的类都会是同一个线程ID
//            long id = Thread.currentThread().getId();
//            log.info("当前线程ID {}", id);
            filterChain.doFilter(request,response);
            return;
        }
// MyMetaObjectHandler.java
/**
 * 自定义元数据处理器
 */
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共字段自动填充【insert】");
        log.info(metaObject.toString());
        Long empId = BaseContext.getCurrentId();
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("createUser", new Long(empId));
        metaObject.setValue("updateUser", new Long(empId));
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段自动填充【update】");
        long id = Thread.currentThread().getId();
        log.info("当前线程ID {}", id);
        log.info(metaObject.toString());
    }
}