一、问题描述
createBy
、createTime
、updateBy
等字段是我们创建表的时候经常要用到的几个字段,但是我们不可能每一次在增删改查的时候都手动去修改或者添加这几个字段的属性值,我们可以在系统层面统一处理,如何实现呢?
二、实现方法
要实现上述需求的一个办法就是使用mabatis拦截器进行统一处理,步骤如下:
-
创建基础类
BaseEntity.java
/** * Entity基类 */ @Data public class BaseEntity implements Serializable { private static final long serialVersionUID = 1L; /** 搜索值 */ @JsonIgnore private String searchValue; /** 创建者 */ private String createBy; /** 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; /** 更新者 */ private String updateBy; /** 更新时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; }
-
创建需要的实体,并继承基础类
BaseEntity.java
@Data public class CorText extends BaseEntity { private static final long serialVersionUID = 1L; /** * 主键 */ private String id; /** * 用户ID */ @Excel(name = "用户ID") private String userId; // xxxx }
-
创建mabatis拦截器
package com.ncjr.framework.interceptor.mybatis; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ReflectUtil; import com.ncjr.common.core.domain.model.LoginUser; import com.ncjr.common.utils.DateUtils; import com.ncjr.common.utils.SecurityUtils; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.binding.MapperMethod.ParamMap; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.*; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.*; /** * mybatis拦截器,自动注入创建人、创建时间、修改人、修改时间 * * @Author wxz * @Date 2023-5-5 */ @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) @Component @Slf4j public class AutoInjectBaseFieldInterceptor implements Interceptor { /** * 用户字段,如果有其他命名的字段,就添加进来 */ private List<String> createUserFieldList() { List<String> createUserList = new ArrayList<>(); createUserList.add("createUser"); createUserList.add("createBy"); // createUserList.add("deleteBy"); // createUserList.add("deleteUser"); return createUserList; } private List<String> updateUserFieldList() { List<String> createUserList = new ArrayList<>(); createUserList.add("updateUser"); createUserList.add("updateBy"); return createUserList; } private List<String> idFieldList() { return Collections.singletonList("id"); } private List<String> createTimeFieldList() { List<String> createTimeList = new ArrayList<>(); createTimeList.add("createTime"); createTimeList.add("createDate"); return createTimeList; } private List<String> updateTimeFieldList() { List<String> createTimeList = new ArrayList<>(); createTimeList.add("updateTime"); createTimeList.add("updateDate"); return createTimeList; } @Override public Object intercept(Invocation invocation) throws Throwable { if (invocation.getArgs().length == 1) { return invocation.proceed(); } MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); Object parameter = invocation.getArgs()[1]; if (parameter == null) { return invocation.proceed(); } if (SqlCommandType.INSERT == sqlCommandType) { Field[] fields = ReflectUtil.getFields(parameter.getClass()); for (Field field : fields) { try { String fieldName = field.getName(); //获取属性名前,先开启反射可访问,否则,无法获取到字段名,因为字段名是私有属性 field.setAccessible(true); Object localCreateBy = field.get(parameter); // 注入id if (this.idFieldList().contains(fieldName)) { if (localCreateBy == null || "".equals(localCreateBy)) { field.set(parameter, IdUtil.fastSimpleUUID()); } } //注入创建时间 if (this.createTimeFieldList().contains(fieldName)) { if (localCreateBy == null || "".equals(localCreateBy)) { field.set(parameter, DateUtils.getNowDate()); } } //注入创建人 if (this.createUserFieldList().contains(fieldName)) { if (localCreateBy == null || "".equals(localCreateBy)) { setUserName(field, parameter); } } field.setAccessible(false); } catch (Exception ignored) { } } } if (SqlCommandType.UPDATE == sqlCommandType) { Field[] fields = null; if (parameter instanceof ParamMap) { ParamMap<?> p = (ParamMap<?>) parameter; if (p.containsKey("et")) { parameter = p.get("et"); } else { parameter = p.get("param1"); } if (parameter == null) { return invocation.proceed(); } } fields = ReflectUtil.getFields(parameter.getClass()); for (Field field : fields) { try { String fieldName = field.getName(); field.setAccessible(true); Object localUpdateBy = field.get(parameter); if (this.updateUserFieldList().contains(fieldName)) { if (localUpdateBy == null || "".equals(localUpdateBy)) { setUserName(field, parameter); } } if (this.updateTimeFieldList().contains(fieldName)) { if (localUpdateBy == null || "".equals(localUpdateBy)) { field.set(parameter, DateUtils.getNowDate()); } } field.setAccessible(false); } catch (Exception e) { e.printStackTrace(); } } } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } private void setUserName(Field field, Object parameter) { LoginUser sysUser = null; //获取登录用户信息 try { sysUser = SecurityUtils.getLoginUser(); if (sysUser != null) { field.setAccessible(true); field.set(parameter, sysUser.getUser().getUserId() + ""); field.setAccessible(false); } } catch (Exception ignored) { } } }
-
依赖工具
SecurityUtils.java
import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** * 安全服务工具类 * * @author ruoyi */ public class SecurityUtils { /** * 用户ID **/ public static Long getUserId() { try { return getLoginUser().getUserId(); } catch (Exception e) { throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED); } } /** * 获取部门ID **/ public static Long getDeptId() { try { return getLoginUser().getDeptId(); } catch (Exception e) { throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED); } } /** * 获取用户账户 **/ public static String getUsername() { try { return getLoginUser().getUsername(); } catch (Exception e) { throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); } } /** * 获取用户 **/ public static LoginUser getLoginUser() { try { return (LoginUser) getAuthentication().getPrincipal(); } catch (Exception e) { throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); } } /** * 获取Authentication */ public static Authentication getAuthentication() { return SecurityContextHolder.getContext().getAuthentication(); } /** * 生成BCryptPasswordEncoder密码 * * @param password 密码 * @return 加密字符串 */ public static String encryptPassword(String password) { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.encode(password); } /** * 判断密码是否相同 * * @param rawPassword 真实密码 * @param encodedPassword 加密后字符 * @return 结果 */ public static boolean matchesPassword(String rawPassword, String encodedPassword) { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.matches(rawPassword, encodedPassword); } /** * 是否为管理员 * * @param userId 用户ID * @return 结果 */ public static boolean isAdmin(Long userId) { return userId != null && 1L == userId; } }
-
创建
mybatis-config.xml
配置文件,使上面的拦截器生效<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--mybatis拦截器--> <plugins> <plugin interceptor="com.xxx.mybatis.MybatisInterceptor"/> </plugins> </configuration>
其中,
mybatis-config.xml
结构路径如下:
经过以上步骤,就可以实现使用mabatis拦截器
统一处理创建人/创建时间/更新人/更新时间等字段了;
问题
如果项目里面使用了 com.github.pagehelper.PageInterceptor
,有可能会导致上面的自定义拦截器失效,如果遇见这种情况,请补充下面代码:
package com.ncjr.framework.config;
import com.ncjr.framework.interceptor.mybatis.AutoInjectBaseFieldInterceptor;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 注册 MyBatis 自定义拦截器,mybatis使用责任链模式,可能会导致自定义插件失效
* @author wxz
*/
@Component
@RequiredArgsConstructor
public class RegisterCustomerInterceptor implements ApplicationListener<ContextRefreshedEvent> {
/**
* 需要注册的自定义插件
*/
private final AutoInjectBaseFieldInterceptor switchDataSourceInterceptor;
private final List<SqlSessionFactory> sqlSessionFactories;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
for (SqlSessionFactory factory : sqlSessionFactories) {
//由于mybatis拦截器使用责任链模式,有可能会导致自定义拦截器失效,因此下面方法可以将自定义拦截器重新注入到sql中
factory.getConfiguration().addInterceptor(switchDataSourceInterceptor);
}
}
}