SpringBoot入门学习笔记-15-MyBatis-Plus拦截器与操作人、操作时间等字段自动全局填充

在MyBatis-Plus中,每个service的save\update方法时,都会有一些通用字段需要处理。比如插入新记录时,需要记录操作人和操作时间,更新记录时,需要记录更新人和更新时间。那么,我们就不需要在每个service里面进行定义和处理了,直接利用MyBatis-Plus拦截器和他的自动填充策略实现。

springboot中,每一次controller请求,都会产生一个新线程。这样我们就可以把需要保存的用户信息和这个线程关联起来。

1、查看当前线程ID

    @RequestMapping(value = "/type1", method = RequestMethod.GET)
    public String get1() {
       
        return "当前线程:"+ Thread.currentThread().getId();
    }

当访问这个controller时,就可以看到变化,每次访问都会返回一个新的线程ID。

2、ThreadLocal类用来提供线程内部的局部变量

常用方法:

方法声明 描述
ThreadLocal() 创建ThreadLocal对象
public void set( T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove() 移除当前线程绑定的局部变量

3、保存用户信息实现思路

 用户在访问时,通过拦截器或过滤器,将用户信息保存到UserThreadLocal变量。

然后,需要使用的地方,通过get方法读取即可。

4、建立UserThreadLocal工具类

package com.luo.comm.utils.ThreadLocal;

import com.luo.comm.entity.User;

/**
 * UserThreadLocal用来保存当前线程的信息,比如当前请求的用户信息。
 * SpringBoot 每一次请求,都会产生一个新的线程。可以将需要线程共享的信息通过ThreadLocal进行存取。
 * 可以在拦截器或过滤器或GlobalExceptionHandler进行读取。
 * 本项目使用了拦截器,非开放路径都需要经由-AuthInterceptor处理,所以在AuthInterceptor中将用户信息保存。在其他地方可以使用。
 * 注意:开放接口未经过AuthInterceptor处理,是没有保存用户信息的,需要自行处理。
 * */

public class ThreadlUser {
    /**
     * 构造函数私有
     */
    private ThreadlUser() {
    }

    private static final ThreadLocal USER_INFO_THREAD_LOCAL =
            new ThreadLocal<>();

    /**
     * 清除用户信息
     */
    public static void clear() {
        USER_INFO_THREAD_LOCAL.remove();
    }

    /**
     * 存储用户信息
     */
    public static void write(User user) {
        USER_INFO_THREAD_LOCAL.set(user);
    }

    /**
     * 获取当前用户信息
     */
    public static User read() {
        return USER_INFO_THREAD_LOCAL.get();
    }
}

定义了write\read的通用方法,后面就可以用来保存和读取用户信息了。 

5、拦截器实现保存用户信息。

在preHandle中,将请求头的user_name\role_id等自己需要的用户信息保存。本示例保存到User实体类中了,也可以新建一个vo对象用来保存。

package com.luo.comm.config.interceptor;


import com.luo.comm.entity.User;
import com.luo.comm.utils.ThreadLocal.ThreadlUser;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/*
 *  这是登陆拦截器,用于拦截所有请求,除特定接口外,都需要登陆,否则不允许访问。
 *  登陆的正常来说是在网关实现token的验证,然后将token中的用户信息写入请求头,这样本服务就可以获取了。
 *  如果是单机项目测试,直接在postman写入请求头即可。
 *  更新说明:reqAllow 从类文件定义放到preHandle方法中了。
 * */


public class AuthInterceptor implements HandlerInterceptor {



    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         boolean reqAllow = false;
        // Controller方法处理之前执行
        // setHeader 只能在preHandle中处理,在其他地方处理无效
        // 约定所有请求走网关。网关处理后,需要将用户信息写入请求头。包括user_name,role_id
        String userName = request.getHeader("user_name");
        String roleId = request.getHeader("role_id");

        if (null !=userName && null !=roleId) {
            // 如果无网关,需在在这里增加token的校验。通过才放行。如果走的放的是网关,可以信任这里只要有就表示是合法,可以放许。
            // 这里示例,进行简化,全部放行。
            reqAllow = true;
            // 如果有用户信息,表示是正确的请求
            User user = new User();
            user.setUserName(userName);
            user.setRoleId(roleId);
            ThreadlUser.write(user);
        }

        // 如果 ALLOW 为真,放行。否则拒绝返回401。
        if (reqAllow) {
            // 如果为真,将相关用户信息写入

            return true;
        } else {
            response.setStatus(401);  //通过response设置返回信息,比如status设置状态
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write("401");
            return false;  //true 放行,false拒绝
        }

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // Controller方法处理完之后.
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //  DispatcherServlet进行视图的渲染之后

    }

}

6、读取用户信息示例。

我们以controller中读取为例。

    @PostMapping("/read")
    public ResultsObj read() {
        User loginUser = ThreadlUser.read();
        return new ResultsObj(loginUser);
    }

当我们在请求头中将用户信息传入以后,就可以通过ThreadUser.read来获取了。 

SpringBoot入门学习笔记-15-MyBatis-Plus拦截器与操作人、操作时间等字段自动全局填充_第1张图片

7、MybatisPlus的自动填充

核心就做两件事,定义一个配置类 + 定义一个自动填充类

1)定义一个配置类,加载拦截器

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}

2)定义一个自动填充类

package com.luo.comm.config.mybatisPlus;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.luo.comm.entity.User;
import com.luo.comm.utils.ThreadLocal.ThreadlUser;
import com.luo.comm.vo.BusinessException;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 自动填充字段值的配置
 * 插入时,保存创建时间和创建人,以及更新时间和更新人。
 * 更新时,保存更新时间和更新人。
 * 其中,创建人,更新人都是从ThreadlUser获取的当前线程用户信息
 */

@Component
public class AutoFillFieldValueConfig implements MetaObjectHandler {

    private static final String createdBy = "createdBy";
    private static final String updatedBy = "updatedBy";
    private static final String createdAt = "createdAt";
    private static final String updatedAt = "updatedAt";


    @Override
    public void insertFill(MetaObject metaObject) {

        User loginUser = getLoginUser();
        this.strictInsertFill(metaObject, createdAt, Date.class, new Date());
        this.strictInsertFill(metaObject, createdBy, String.class, loginUser.getUserName());
        this.strictInsertFill(metaObject, updatedAt, Date.class, new Date());
        this.strictInsertFill(metaObject, updatedBy, String.class, loginUser.getUserName());

    }

    @Override
    public void updateFill(MetaObject metaObject) {
        User loginUser = getLoginUser();
        this.strictInsertFill(metaObject, updatedAt, Date.class, new Date());
        this.strictInsertFill(metaObject, updatedBy, String.class, loginUser.getUserName());

    }


    private User getLoginUser (){
        /** 获取当前线程的用户信息,检查是否登陆 */
        User loginUser = ThreadlUser.read();
        if(StringUtils.isEmpty(loginUser.getUserName()) || StringUtils.isEmpty(loginUser.getRoleId())){
            throw BusinessException.error ("用户未登陆或不存在");
        }
        return loginUser;
    }


}

3)使用。在需要生效的实体类上,设置Fill行为。

FieldFill.INSERT 表示插入时生效,UPDATE表示更新时生效。INSERT_UPDATE表示两个都生效

  /** 创建者*/
    @JsonInclude(JsonInclude.Include.NON_NULL) // 当为空时不转换JSON输出。这样前端就不会返回null.
    @TableField(fill = FieldFill.INSERT)
    private String createdBy;

    /** 创建时间*/
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @JsonInclude(JsonInclude.Include.NON_NULL) // 当为空时不转换JSON输出。这样前端就不会返回null.
    @TableField(fill = FieldFill.INSERT)
    private Date createdAt;

    /** 更新者*/
    @JsonInclude(JsonInclude.Include.NON_NULL) // 当为空时不转换JSON输出。这样前端就不会返回null.
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updatedBy;

    /** 更新时间*/
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @JsonInclude(JsonInclude.Include.NON_NULL) // 当为空时不转换JSON输出。这样前端就不会返回null.
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @Version
    private Date updatedAt;

8、进行验证,在controller中进行测试。

SpringBoot入门学习笔记-15-MyBatis-Plus拦截器与操作人、操作时间等字段自动全局填充_第2张图片

SpringBoot入门学习笔记-15-MyBatis-Plus拦截器与操作人、操作时间等字段自动全局填充_第3张图片

 9、总结。

将用户信息保存到UserThreadLocal变量,可以极大的方便进行用户身份识别。

实现MetaObjectHandler类,可以实现全局的插入和更新时进行公用字段的自动填充。像一些创建人、更新人、操作时间是每个实体类都需要记录的,这样就可以配在这个自动填充类里,就不需要单独为每个服务进行处理了。

 当然,需要配合使用,首先用户信息获取,需要controller之前保存,一般在拦截器或过滤器里保存。用户信息可以写在请求头或通过路由问号传参都可以的。自行约定即可。

你可能感兴趣的:(SpringBoot,入门,学习笔记,spring,boot,学习,笔记)