最近準(zhǔn)備做修改留痕的業(yè)務(wù)模塊呀舔,準(zhǔn)備使用注解+mybaits攔截器的方式來實(shí)現(xiàn)场斑,這里展示一下代碼和實(shí)現(xiàn)思路糠睡,目前做的這個版本挽鞠,限定只能攔截到根據(jù)ID來更新數(shù)據(jù)的方法,比較簡單狈孔。
第一步編寫相關(guān)實(shí)體類
- UserMo 用來測試需要記錄的實(shí)體類
@Data
@TableName("t_user")
public class UerMo implements Serializable {
@RecordField(name = "ID")
private Long id;
@RecordField(name = "姓名")
private String xm;
@RecordField(name = "姓名", type= "file")
private String avatar;
}
2.RecordModel 用來存放被記錄的對象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RecordModel {
/**
* 數(shù)據(jù)ID
*/
private String dataId;
/**
* 類
*/
private Class classType;
/**
* 老數(shù)據(jù)
*/
private Object oldData;
}
- markMo 留痕記錄表
@Data
@TableName("t_mark")
public class markMo {
// id
private Long id;
// 舊值
private String oldValue;
// 新值
private String newValue;
}
第步編寫注解類
我們實(shí)現(xiàn)一個注解信认,并對這個注解做一個切面
- RecordField 用來標(biāo)記需要被記錄的字段
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordField {
public String name();
public String type() default "";
}
- MarkUpdate.java 用來標(biāo)記需要記錄的方法
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MarkUpdate {
}
- MarkUpdateAspect 用來對被MarkUpdate注解的方法做一個切面
@Aspect
@Order(99)
@Component
@AllArgsConstructor
public class MarkUpdateAspect {
@Autowired
MarkHandlerDispatch markHandlerDispatch;
private static final Map<Long, List<RecordModel>> TEM_MAP = new ConcurrentHashMap<>();
/**
* 判斷是否有該注解
*
* @param threadName
* @return
*/
public static boolean hasThread(Long threadName) {
return TEM_MAP.containsKey(threadName);
}
/**
* 放入前置數(shù)據(jù)
*
* @param threadName
* @param RecordModel
*/
public static void put(Long threadName, RecordModel recordModel) {
if (TEM_MAP.containsKey(threadName)) {
TEM_MAP.get(threadName).add(recordModel);
}
}
@SneakyThrows
@Before("@annotation(markUpdate)")
public void before(JoinPoint joinPoint, MarkUpdate markUpdate) {
// 獲取線程名,使用線程名作為同一次操作記錄
Long threadName = Thread.currentThread().getId();
TEM_MAP.remove(threadName);
TEM_MAP.put(threadName, new LinkedList<>());
}
@SneakyThrows
@AfterReturning("@annotation(markUpdate)")
public void after(JoinPoint joinPoint, MarkUpdate markUpdate) {
// 獲取線程名均抽,使用線程名作為同一次操作記錄
Long threadName = Thread.currentThread().getId();
if (TEM_MAP.get(threadName) == null) {
return;
}
for (RecordModel recordModel: TEM_MAP.get(threadName)) {
markHandlerDispatch.record(recordModel);
}
// 移除當(dāng)前線程
TEM_MAP.remove(threadName);
}
}
第三步實(shí)現(xiàn)mybatis攔截器
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class})})
public class MarkUpdateInterceptor extends AbstractSqlParserHandler implements Interceptor {
@Autowired
MarkHandlerDispatch markHandlerDispatch;
@Override
@SneakyThrows
public Object intercept(Invocation invocation) throws Throwable {
String sqlCommandType = getSqlCommandType(invocation);
if (sqlCommandType.equals("UPDATE")) {
Long threadName = Thread.currentThread().getId();
Class classType = getClassType(invocation);
if (classType == null) {
return invocation.proceed();
}
// 判斷該類型是否需要被記錄
if (!markHandlerDispatch.isCanMark(classType)) {
return invocation.proceed();
}
// 獲取更新ID
String id = getUpdateId(invocation);
if (StringUtils.isEmpty(id)) {
return invocation.proceed();
}
// 獲取老數(shù)據(jù)
Object oldData = markHandlerDispatch.getData(classType, id);
Object proceedObj = invocation.proceed();
// 生成記錄對象 并放入線程Map中等待處理
RecordModel recordModel = new RecordModel (id, classType, oldData);
if (MarkUpdateAspect.hasThread(threadName)) {
MarkUpdateAspect.put(threadName, recordModel);
}
return proceedObj;
}
return invocation.proceed();
}
/**
* 獲取語句的類型
*
* @param invocation
* @return
*/
public String getSqlCommandType(Invocation invocation) {
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
this.sqlParser(metaObject);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
String sqlCommandType = mappedStatement.getSqlCommandType().toString();
return sqlCommandType;
}
/**
* 獲取更新的類
*
* @param invocation
* @return
*/
public Class getClassType(Invocation invocation) {
try {
String sql = getOriginSql(invocation);
Collection<String> tables = new TableNameParser(sql).tables();
if (CollectionUtils.isEmpty(tables)) {
return null;
}
String tableName = tables.iterator().next();
TableInfo tableInfo = TableInfoHelper.getTableInfos().stream().filter(item -> {
return item.getTableName().equals(tableName);
}).findFirst().orElse(new TableInfo(null));
Class<?> entityType = tableInfo.getEntityType();
return entityType;
} catch (Exception e) {
return null;
}
}
/**
* 獲取SQL語句
*
* @param invocation
* @return
*/
public String getOriginSql(Invocation invocation) {
Statement statement = getStatement(invocation);
String originalSql = statement.toString();
return originalSql;
}
/**
* 獲取Statement
*
* @param invocation
* @return
*/
public Statement getStatement(Invocation invocation) {
Object firstArg = invocation.getArgs()[0];
Statement statement = null;
if (Proxy.isProxyClass(firstArg.getClass())) {
statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
} else {
statement = (Statement) firstArg;
}
MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
try {
statement = (Statement) stmtMetaObj.getValue("stmt.statement");
} catch (Exception e) {
// do nothing
}
if (stmtMetaObj.hasGetter("delegate")) {
//Hikari
try {
statement = (Statement) stmtMetaObj.getValue("delegate");
} catch (Exception ignored) {
}
}
return statement;
}
/**
* 獲取更新數(shù)據(jù)的ID
*
* @param invocation
* @return
*/
public String getUpdateId(Invocation invocation) {
try {
Map<String, String> conditionMap = new HashMap<>();
String sql = getOriginSql(invocation);
Integer index = sql.lastIndexOf("WHERE");
String allCondition = sql.substring(index, sql.length()).replaceAll("WHERE", "").replaceAll("\\(", "").replaceAll("\\)", "");
String[] conditionArr = allCondition.split("AND|OR");
Arrays.stream(conditionArr).forEach(item -> {
if (item.lastIndexOf("=") > 0) {
List<String> condition = Arrays.stream(item.split("=")).collect(Collectors.toList());
conditionMap.put(condition.get(0).trim().toLowerCase(), condition.get(1).trim());
}
});
return conditionMap.get("id");
} catch (Exception e) {
return null;
}
}
}
第四步實(shí)現(xiàn)記錄處理類
MarkHandlerDispatch 用來記錄數(shù)據(jù)到表中
@Slf4j
@Component
public class MarkHandlerDispatch {
@Lazy
@Autowired
UserMapper userMapper;
@Lazy
@Autowired
MarkMapper markMapper;
@Lazy
@Autowired
TrueValueConventHandlerDispatch trueValueConventHandlerDispatch ;
private static List<Class> needHandlerClass = new ArrayList<>();
static {
// 放入需要被標(biāo)記的實(shí)體類
needHandlerClass.add(UserMo.class);
}
/**
* 獲取實(shí)體數(shù)據(jù)
*
*/
public Object getData(Class type, String id) {
if (type == UserMo.class) {
return userMapper.selectById(Long.valueOf(id));
}
return null;
}
/**
* 獲取實(shí)體數(shù)據(jù)
* 這里需要異步處理嫁赏,否則還是獲取的老數(shù)據(jù)
*/
@Async
public void record(RecordModel recordModel) {
// 獲取最新的數(shù)據(jù)
Object updateData = this.getData(recordModel.getClassType(), recordModel.getDataId());
if (recordModel.getClassType() == UserMo.class) {
UserMo userMo = (UserMo) updateData;
record(recordModel.getOldData(), updateData, "用戶信息");
}
}
/**
* 記錄
*/
public void record(Object oldData, Object updateData, Class type) {
try {
Map<String, String> oldDataMap = trueValueConventHandlerDispatch.dispatchConvert(type, oldData);
Map<String, String> updateDataMap = trueValueConventHandlerDispatch.dispatchConvert(type, updateData);
List<MarkMo> markList = getNeedRecordMap(type, oldDataMap, updateDataMap);
markMapper.saveBatch(markList);
} catch (Exception e) {
log.error("記錄失敗", e);
}
}
/**
* 通過比較,獲取所有需要記錄的列表數(shù)據(jù)
*
* @param oldDataMap
* @param updateDataMap
* @return
*/
public List<MarkMo> getNeedRecordMap(String type, Map<String, String> oldDataMap, Map<String, String> updateDataMap) {
List<MarkMo> markList = new ArrayList<>();
Set<String> keySet = new HashSet<>();
keySet.addAll(oldDataMap.keySet());
keySet.addAll(updateDataMap.keySet());
for (String key : keySet) {
String oldValue = oldDataMap.get(key);
String newValue = updateDataMap.get(key);
if (oldValue != null && oldValue.equals(newValue)) {
continue;
}
if (newValue != null && newValue.equals(oldValue)) {
continue;
}
MarkMo markMo = new MarkMo();
markMo.setOldValue(oldValue)
markMo.setNewValue(newValue)
markMo.setType(type)
markList.add(markMo);
}
return markList;
}
/**
* 判斷是否需要記錄
*
* @param type
* @return
*/
public Boolean isCanMark(Class type) {
Long count = needHandlerClass.stream().filter(item -> {
return item == type;
}).count();
return count > 0;
}
}
第5步 編寫真實(shí)值轉(zhuǎn)換類
@Component
@Slf4j
public class TrueValueConventHandlerDispatch {
// 需要被記錄的基本數(shù)據(jù)類型
private Class[] baseTypeList = {Long.class, String.class, Integer.class};
public Map<String, String> dispatchConvert(Class type, Object data) {
if (type == UserMo.class) {
UserMo userMo = (UserMo) data;
Map<String, String> result = baseConvert(userMo);
// 這里還可以特殊定制需要記錄的值
}
return result;
}
/**
* 默認(rèn)將對象轉(zhuǎn)換為key - value 真實(shí)表示的值
* @param o
* @return
*/
protected Map<String, String> baseConvert(Object o) {
Map<String, String> result = new HashMap<>();
try {
for (Field declaredField : o.getClass().getDeclaredFields()) {
declaredField.setAccessible(true);
// 獲取到RecordField注解標(biāo)識的列
RecordField recordField = declaredField.getAnnotation(RecordField.class);
if (recordField == null) {
continue;
}
Object value = declaredField.get(o);
if (value == null) {
continue;
}
// 如果是基本類型
if (isBaseType(declaredField.getType())) {
// 如果是別的類型油挥,則采用特殊方式記錄
if (StringUtils.isNotEmpty(recordField.type())) {
result.put(recordField.name(), "(" + recordField.type() + ")" + value);
} else {
result.put(recordField.name(), String.valueOf(value));
}
}
// 如果是特殊類型潦蝇,比如時間日期類型
if (declaredField.getType() == Date.class) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
result.put(recordField.name(), simpleDateFormat.format(value));
}
}
} catch (Exception e) {
log.error("記錄轉(zhuǎn)換失敗", e);
throw new BizException("轉(zhuǎn)換記錄失敗");
}
return result;
}
/**
* 判斷是否是基本類型
* @param clazz
* @return
*/
private Boolean isBaseType(Class clazz) {
return Arrays.stream(baseTypeList).anyMatch(item -> {
return clazz == item;
});
}
}
第6步,注冊mybatis攔截器
@Bean
@Profile({"prod", "dev", "local"})
@ConditionalOnMissingBean
@ConditionalOnBean(AbstractMarkHandle.class)
public MarkUpdateInterceptor markUpdateInterceptor() {
return new MarkUpdateInterceptor();
}
第7步深寥,在需要留痕的地方加上注解
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@MarkUpdate
@Override
public void udpate(UserMo userMo) {
userMapper.updateById(userMo);
}
}