在MyBatis-Plus中,每个service的save\update方法时,都会有一些通用字段需要处理。比如插入新记录时,需要记录操作人和操作时间,更新记录时,需要记录更新人和更新时间。那么,我们就不需要在每个service里面进行定义和处理了,直接利用MyBatis-Plus拦截器和他的自动填充策略实现。
@RequestMapping(value = "/type1", method = RequestMethod.GET)
public String get1() {
return "当前线程:"+ Thread.currentThread().getId();
}
当访问这个controller时,就可以看到变化,每次访问都会返回一个新的线程ID。
常用方法:
方法声明 | 描述 |
---|---|
ThreadLocal() | 创建ThreadLocal对象 |
public void set( T value) | 设置当前线程绑定的局部变量 |
public T get() | 获取当前线程绑定的局部变量 |
public void remove() | 移除当前线程绑定的局部变量 |
用户在访问时,通过拦截器或过滤器,将用户信息保存到UserThreadLocal变量。
然后,需要使用的地方,通过get方法读取即可。
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的通用方法,后面就可以用来保存和读取用户信息了。
在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进行视图的渲染之后
}
}
我们以controller中读取为例。
@PostMapping("/read")
public ResultsObj read() {
User loginUser = ThreadlUser.read();
return new ResultsObj(loginUser);
}
当我们在请求头中将用户信息传入以后,就可以通过ThreadUser.read来获取了。
核心就做两件事,定义一个配置类 + 定义一个自动填充类
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
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;
将用户信息保存到UserThreadLocal变量,可以极大的方便进行用户身份识别。
实现MetaObjectHandler类,可以实现全局的插入和更新时进行公用字段的自动填充。像一些创建人、更新人、操作时间是每个实体类都需要记录的,这样就可以配在这个自动填充类里,就不需要单独为每个服务进行处理了。
当然,需要配合使用,首先用户信息获取,需要controller之前保存,一般在拦截器或过滤器里保存。用户信息可以写在请求头或通过路由问号传参都可以的。自行约定即可。