一、必备类
1.自定义注解
FieldIdentifyAnnotation
import java.lang.annotation.*;
/**
* @author DBC
* 字段标识注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FieldIdentifyAnnotation {
/**
* 唯一标识字段,只能有一个字段是true
*/
boolean id() default false;
/**
* 对象名,只能有一个字段是true
*/
boolean isObjectName() default false;
/**
* 字段名,必填
*/
String filedName() default "";
/**
* 字段类型
*/
OperatorLogFiledTypeEnum type() default OperatorLogFiledTypeEnum.NORMAL;
/**
* 变更字段显示优先级
*/
int priority() default 0;
/**
* 数据字典
*/
Class<?> dictionary() default FieldIdentifyAnnotation.class;
} OperatorLogAnnotation
import com.example.test05.demo.enums.LogTypeEnum;
import com.example.test05.demo.strategy.IOperateLogStrategy;
import java.lang.annotation.*;
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 操作日志的切点
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperatorLogAnnotation {
/**
* 生成操作日志内容策略类
* @return
*/
Class<? extends IOperateLogStrategy> iOperatorLogStrategy();
/**
* 表对应实体,支持多表
* @return 表对应实体
*/
Class<?>[] tableEntity();
/**
* 历史表对应实体
*/
Class<?> histTableEntity();
/**
* 日志类型
* @return 日志类型枚举
*/
LogTypeEnum logType();
/**
* 前端请求对象,注解方法返回类型不支持Object,故要借助SPEL来获取前端传来的参数
* 若是删除或查询操作,则是"",其他操作要自己指定
* @return spring表达式
*/
String requestObjSpel() default SpelInstance.BLANK;
/**
* 操作对象ID
* 若是新增操作,则是"",若是查询操作,则是无,其他操作要自己指定
* @return spring表达式
*/
String operatorObjIdSpel() default SpelInstance.BLANK;
class SpelInstance {
private SpelInstance(){}
private static final String BLANK = "";
private static final String NO_CONTENT = "'无'";
}
} 2.注解的关键实现(关键代码)
OperatorLogAspect
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.test05.demo.annotation.OperatorLogAnnotation;
import com.example.test05.demo.exception.BizException;
import com.example.test05.demo.mapper.OperatorLogMapper;
import com.example.test05.demo.model.OperatorLog;
import com.example.test05.demo.strategy.IOperateLogStrategy;
import com.example.test05.demo.unit.OperatorLogUtil;
import com.example.test05.demo.unit.TokenUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 生成操作日志的切面
*/
@Component
@Aspect
@RequiredArgsConstructor
public class OperatorLogAspect {
private final OperatorLogMapper operatorLogMapper;
private final ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
/**
* 切点
*
* @param operatorLogAnnotation
*/
@Pointcut("@annotation(operatorLogAnnotation)")
public void operatorLogPointCut(OperatorLogAnnotation operatorLogAnnotation) {
}
/**
* 环绕切面,添加操作日志
*/
@Around(value = "operatorLogPointCut(operatorLogAnnotation)", argNames = "joinPoint,operatorLogAnnotation")
public Object operatorLogAround(ProceedingJoinPoint joinPoint, OperatorLogAnnotation operatorLogAnnotation) throws Throwable{
OperatorLog operatorLog = new OperatorLog();
// 设置服务器开始处理时间
operatorLog.setStartTime(LocalDateTime.now());
// 设置策略类型
operatorLog.setType(operatorLogAnnotation.logType());
// 设置对象类型
Class<?>[] tableEntity = operatorLogAnnotation.tableEntity();
// 设置历史表名
Class<?> histTableEntity = operatorLogAnnotation.histTableEntity();
TableName tableNameAnnotation = histTableEntity.getDeclaredAnnotation(TableName.class);
String tableName = tableNameAnnotation.value() + "_hist";
operatorLog.setOperatorObjType(OperatorLogUtil.getObjectTypeEnum(tableEntity[0]));
// 获取切面方法中的参数内容
EvaluationContext context = getEvaluationContext(joinPoint);
// 获取操作对象ID
String operatorObjId = getOperatorObjIdBySpel(operatorLogAnnotation.operatorObjIdSpel(), context);
// 获取策略类
IOperateLogStrategy iOperateLogStrategy;
try {
iOperateLogStrategy = operatorLogAnnotation.iOperatorLogStrategy().newInstance();
} catch (IllegalAccessException | InstantiationException e) {
throw new BizException(0,"新建策略实现类失败");
}
// 设置操作日志内容
Object requestObj = getRequestObjBySpel(operatorLogAnnotation.requestObjSpel(), context);
String operatorContent = iOperateLogStrategy.getOperatorContent(requestObj, tableEntity, operatorObjId);
if (StringUtils.isBlank(operatorContent)) {
return null;
}
operatorLog.setContent(operatorContent);
// 设置当前用户ID
operatorLog.setUserId(TokenUtil.getCurrentUserId());
// 执行方法
Object result;
try {
result = joinPoint.proceed();
// 设置操作对象ID
setOperatorObjId(operatorLog, operatorObjId, result);
// 设置操作成功
operatorLog.setHasFinish(true);
} catch (Throwable throwable) {
// 设置操作失败
operatorLog.setHasFinish(false);
// 设置失败原因
operatorLog.setFailReason(throwable.getMessage());
throw throwable;
} finally {
// 设置服务器处理完成时间
operatorLog.setEndTime(LocalDateTime.now());
// TODO 可以采用RabbitMQ 保存操作日志
SpringUtil.getBean(this.getClass()).saveOperatorLog(tableName, operatorLog);
}
return result;
}
@Async
public void saveOperatorLog(String tableName, OperatorLog operatorLog) {
operatorLogMapper.insertDynamicTable(tableName,operatorLog);
}
/**
* 设置操作对象ID,分为新增和非新增两种情况
* @param operatorLog
* @param operatorObjId
* @param result
*/
private void setOperatorObjId(OperatorLog operatorLog, String operatorObjId, Object result) {
// 若operatorObjId为空,新增操作,从返回结果获取
if (StringUtils.isBlank(operatorObjId)) {
if (result instanceof String || result instanceof Integer) {
operatorLog.setOperatorObjId(result.toString());
} else {
throw new BizException(0,"新增操作要返回新增对象ID");
}
// 若operatorObjId非空
} else {
operatorLog.setOperatorObjId(operatorObjId);
}
}
/**
* 通过Spel获取前端请求对象
* @param requestObjSpel spring表达式
* @param context 切面方法参数值
* @return
*/
private Object getRequestObjBySpel(String requestObjSpel, EvaluationContext context) {
if (StringUtils.isBlank(requestObjSpel)) {
return null;
}
ExpressionParser parser = new SpelExpressionParser();
return parser.parseExpression(requestObjSpel).getValue(context, Object.class);
}
/**
* 通过Spel获取操作对象ID
* @param operatorObjId spring表达式
* @param context 切面方法参数值
* @return
*/
private String getOperatorObjIdBySpel(String operatorObjId, EvaluationContext context) {
if (StringUtils.isBlank(operatorObjId)) {
return null;
}
ExpressionParser parser = new SpelExpressionParser();
return parser.parseExpression(operatorObjId).getValue(context, String.class);
}
/**
* 获取切面方法中的参数内容
* @param joinPoint
* @return
*/
private EvaluationContext getEvaluationContext(ProceedingJoinPoint joinPoint) {
// 获取切面方法参数
Object[] args = joinPoint.getArgs();
// 获取切面方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 填充方法参数
EvaluationContext context = new StandardEvaluationContext();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0; i < args.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
return context;
}
}
3.核心策略类(重点)
策略接口
IOperateLogStrategy
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 获取日志内容信息策略类
*/
public interface IOperateLogStrategy {
/**
* 生成操作日志内容
* @param requestObj 前端请求参数
* @param tableEntity 表实体类数组
* @param operatorObjId 操作对象ID
* @return 操作日志内容
*/
String getOperatorContent(Object requestObj, Class<?>[] tableEntity, String operatorObjId);
} 【新增】策略类:AddOperatorLogStrategy
import com.example.test05.demo.constant.ObjectAddLogUtil;
import com.example.test05.demo.exception.BizException;
import com.example.test05.demo.unit.OperatorLogUtil;
/**
* @Author: DBC
* @Date: 2023/09/25
* @Description: 新增生成变更内容模板
*/
public class AddOperatorLogStrategy implements IOperateLogStrategy{
@Override
public String getOperatorContent(Object requestObj, Class<?>[] tableEntity, String operatorObjId) {
ObjectAddLogUtil objectAddLogUtil = new ObjectAddLogUtil();
String content;
try {
content = objectAddLogUtil.generatorContent(requestObj, OperatorLogUtil.getObjectTypeEnum(tableEntity[0]).getName());
} catch (Exception e) {
throw new BizException(0,"生成新增操作日志失败");
}
return content;
}
} 【删除】策略类:DeleteOperatorLogStrategy
import com.example.test05.demo.constant.OperatorLogContentUtil;
import com.example.test05.demo.unit.OperatorLogUtil;
/**
* @Author: DBC
* @Date: 2023/09/25
* @Description: 删除生成变更内容模板
*/
public class DeleteOperatorLogStrategy implements IOperateLogStrategy{
@Override
public String getOperatorContent(Object requestObj, Class<?>[] tableEntity, String operatorObjId) {
String objName = OperatorLogUtil.getObjName(operatorObjId, tableEntity[0]);
return OperatorLogContentUtil.deleteFormat(OperatorLogUtil.getObjectTypeEnum(tableEntity[0]).getName(), objName);
}
} 【更新】策略类:UpdateOperatorLogStrategy
import com.example.test05.demo.constant.ObjectUpdateLogUtil;
import com.example.test05.demo.exception.BizException;
import com.example.test05.demo.unit.OperatorLogUtil;
import java.util.List;
/**
* @Author: DBC
* @Date: 2023/09/25
* @Description: 更新生成变更内容模板
*/
public class UpdateOperatorLogStrategy implements IOperateLogStrategy{
@Override
public String getOperatorContent(Object requestObj, Class<?>[] tableEntity, String operatorObjId) {
ObjectUpdateLogUtil objectUpdateLogUtil = new ObjectUpdateLogUtil();
List<String> result;
try {
Object oldObject = OperatorLogUtil.getObj(requestObj.getClass(), operatorObjId, tableEntity[0]);
// 生成变更日志
result = objectUpdateLogUtil.generatorChangeLog(oldObject, oldObject.getClass(), requestObj, requestObj.getClass(),
OperatorLogUtil.getObjectTypeEnum(tableEntity[0]).getName());
} catch (Exception e) {
throw new BizException(0,"生成更新对象操作日志失败");
}
if (result.size() == 0) {
return null;
}
return result.get(0);
}
}
4.枚举类
枚举基类接口
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 枚举基类接口
*/
public interface IEnumBase {
/**
* 通过整型值转换为枚举
* @param code 枚举中的整形
* @return 枚举类
*/
IEnumBase convertIEnumBase(Integer code);
}
日志类型
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.example.test05.demo.exception.BizException;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 日志类型
*/
@Getter
public enum LogTypeEnum implements IEnumBase{
USER_MANAGE(0, "用户管理日志"),
USER_OPERATOR(1, "用户操作日志");
@EnumValue
@JsonValue
private final Integer code;
private final String name;
LogTypeEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
@Override
public IEnumBase convertIEnumBase(Integer code) {
for (LogTypeEnum logTypeEnum : LogTypeEnum.values()) {
if (logTypeEnum.getCode().equals(code)) {
return logTypeEnum;
}
}
throw new BizException(0,"该整型值没有对应枚举:" + code);
}
}
操作对象类型
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.example.test05.demo.constant.TableNameConstant;
import com.example.test05.demo.exception.BizException;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 操作对象类型
*/
@Getter
public enum ObjectTypeEnum implements IEnumBase{
USER(0, "用户", TableNameConstant.USER),
OPERATOR_LOG(1, "操作日志", TableNameConstant.OPERATOR_LOG),
ROLE(2, "角色", TableNameConstant.ROLE),
STUDENT(3, "学生", TableNameConstant.STUDENT),
PROJECT(4, "项目", TableNameConstant.PROJECT),
PROJECT_STUDENT(5, "结对", TableNameConstant.PROJECT_STUDENT)
,;
@EnumValue
@JsonValue
private final Integer code;
private final String name;
private final String tableName;
ObjectTypeEnum(Integer code, String name, String tableName) {
this.code = code;
this.name = name;
this.tableName = tableName;
}
@Override
public IEnumBase convertIEnumBase(Integer code) {
for (ObjectTypeEnum objectTypeEnum : ObjectTypeEnum.values()) {
if (objectTypeEnum.getCode().equals(code)) {
return objectTypeEnum;
}
}
throw new BizException(0,"该整型值没有对应枚举:" + code);
}
public static ObjectTypeEnum getByTableName(String tableName) {
for (ObjectTypeEnum objectTypeEnum : ObjectTypeEnum.values()) {
if (objectTypeEnum.getTableName().equals(tableName)) {
return objectTypeEnum;
}
}
throw new BizException(0,"该表名没有对应枚举:" + tableName);
}
} 字段类型
/**
* @author DBC
* 字段类型
*/
public enum OperatorLogFiledTypeEnum {
// 正常字段
NORMAL,
// 字典字段
ENUM,
// 独立子类字段
QUOTE_SUB_OBJECT,
// 子类字段是父类字段的一部分
SUB_OBJECT,
// 简单列表字段,例如:List<String>
SIMPLE_LIST,
// 复杂列表字段,例如:List<Object>
COMPLEX_LIST;
} 5.一些工具类
枚举工具类
import com.example.test05.demo.enums.IEnumBase;
import com.example.test05.demo.exception.BizException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @Author: DBC
* @Date: 2023/09/25
* @Description: 枚举工具类
*/
public class EnumUtils {
public static IEnumBase getIEnumBase(Class<?> clazz, Integer code) {
Object[] enumConstants = clazz.getEnumConstants();
try {
Method method = clazz.getDeclaredMethod("convertIEnumBase", Integer.class);
return (IEnumBase) method.invoke(enumConstants[0], code);
} catch (NoSuchMethodException e) {
throw new BizException(0,"该枚举没有ConvertIEnumBase方法");
} catch (IllegalAccessException | InvocationTargetException e) {
throw new BizException(0,"调用反射方法失败");
}
}
} 操作日志查询工具类
import com.example.test05.demo.compoment.SpringBeanContext;
import com.example.test05.demo.enums.ObjectTypeEnum;
import com.example.test05.demo.exception.BizException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author: DBC
* @Date: 2023/09/25
* @Description: 操作日志查询工具类
*/
public class OperatorLogQueryUtil {
private OperatorLogQueryUtil(){}
private static JdbcTemplate jdbcTemplate;
/**
* 获取Jdbc静态类
*/
private static JdbcTemplate getJdbcTemplate() {
if (null == jdbcTemplate) {
jdbcTemplate = SpringBeanContext.getSpringBean(JdbcTemplate.class);
}
return jdbcTemplate;
}
/**
* 通过Id查询获取数据
* @param operatorObjIds
* @param tableEntity
* @return
*/
public static List<Map<String, Object>> listObj(List<String> operatorObjIds, Class<?> tableEntity) {
if (CollectionUtils.isEmpty(operatorObjIds)) {
throw new BizException(0,"查询旧数据,主键列表Id不能为空");
}
// 获取表名
String tableName = OperatorLogUtil.getTableName(tableEntity);
// 获取主键字段名
String tableId = OperatorLogUtil.getTableId(tableEntity);
String querySql = String.format("select * from %s where %s in (%s);", tableName, tableId, String.join("," , operatorObjIds));
return getJdbcTemplate().queryForList(querySql);
}
/**
* 通过Id查询获取数据
* @param operatorObjId
* @param tableEntity
* @return
*/
public static Map<String, Object> getObj(String operatorObjId, Class<?> tableEntity) {
if (StringUtils.isBlank(operatorObjId)) {
throw new BizException(0,"查询数据,主键列表Id不能为空");
}
// 获取表名
String tableName = OperatorLogUtil.getTableName(tableEntity);
// 获取主键字段名
String tableId = OperatorLogUtil.getTableId(tableEntity);
String querySql = String.format("select * from %s where %s = %s;", tableName, tableId, operatorObjId);
return getJdbcTemplate().queryForMap(querySql);
}
public static Map<String, String> listObjIdAndNameMap(List<String> objIds, ObjectTypeEnum objectTypeEnum) {
Map<String, String> result = new HashMap<>();
// if (CollectionUtils.isEmpty(objIds)) {
// return result;
// }
// String sql = String.format("select %s, %s from %s where %s in (%s);", GlobalConstant.TABLE_ID_COLUMN_NAME, GlobalConstant.TABLE_NAME_COLUMN_NAME,
// objectTypeEnum.getTableName(), GlobalConstant.TABLE_ID_COLUMN_NAME, String.join(",", objIds));
// try {
// List<Map<String, Object>> queryList = getJdbcTemplate().queryForList(sql);
// for (Map<String, Object> queryMap : queryList) {
// result.put(queryMap.get(GlobalConstant.TABLE_ID_COLUMN_NAME).toString(), queryMap.get(GlobalConstant.TABLE_NAME_COLUMN_NAME).toString());
// }
// } catch (Exception e) {
// return result;
// }
return result;
}
}
操作日志工具类
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.test05.demo.constant.GlobalConstant;
import com.example.test05.demo.constant.OperatorLogRecord;
import com.example.test05.demo.enums.ObjectTypeEnum;
import com.example.test05.demo.exception.BizException;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 操作日志工具类
*/
public class OperatorLogUtil {
public static String getTableId(Class<?> tableEntity){
Field[] fields = tableEntity.getDeclaredFields();
for (Field field : fields) {
TableId tableField = field.getDeclaredAnnotation(TableId.class);
if (tableField != null) {
return tableField.value();
}
}
throw new BizException(0,"该类[" + tableEntity.getName() + "]" + "没有标注TableId注解");
}
/**
* 通过表对应实体获取表名
* @param tableEntity
* @return
*/
public static String getTableName(Class<?> tableEntity) {
TableName tableNameAnnotation = tableEntity.getDeclaredAnnotation(TableName.class);
if (tableNameAnnotation == null) {
throw new BizException(0,"该类[" + tableEntity.getName() + "]" + "没有标注TableName注解");
}
return tableNameAnnotation.value();
}
/**
* 通过表对应实体获取对象类型枚举
* @param tableEntity
* @return
*/
public static ObjectTypeEnum getObjectTypeEnum(Class<?> tableEntity) {
String tableName = OperatorLogUtil.getTableName(tableEntity);
return ObjectTypeEnum.getByTableName(tableName);
}
/**
* 查询对象列表名称
* @param requestObjs
* @param tableClass
* @return
*/
public static String listObjName(List<String> requestObjs, Class<?> tableClass) {
// 查询数据
List<Map<String, Object>> queryList = OperatorLogQueryUtil.listObj(requestObjs, tableClass);
List<String> result = new ArrayList<>();
for (Map<String, Object> queryMap : queryList) {
String name = (String) queryMap.get(GlobalConstant.TABLE_NAME_COLUMN_NAME);
if (name == null) {
throw new BizException(0,"查询对象名列表,该表没有name字段");
}
result.add(name);
}
return String.join(GlobalConstant.COMMA, result);
}
/**
* 查询对象名称
* @param operatorObjId
* @param tableClass
* @return
*/
public static String getObjName(String operatorObjId, Class<?> tableClass) {
// 查询数据
Map<String, Object> queryMap = OperatorLogQueryUtil.getObj(operatorObjId, tableClass);
String name = (String) queryMap.get(GlobalConstant.TABLE_NAME_COLUMN_NAME);
if (name == null) {
throw new BizException(0,"查询对象名列表,该表没有name字段");
}
return name;
}
/**
* 根据字段优先级排序,并转为String
* @param records
* @return
*/
public static String sortOperatorLogRecord(List<OperatorLogRecord> records) {
if (CollectionUtils.isEmpty(records)){
return "";
}
records.sort(Comparator.comparingInt(OperatorLogRecord::getPriority));
return records.stream().map(OperatorLogRecord::getChangeLogContent).collect(Collectors.joining());
}
public static Object getObj(Class<?> objClazz, String operatorObjId, Class<?> tableClazz) throws IllegalAccessException, InstantiationException {
Object result = objClazz.newInstance();
Field[] resultFields = objClazz.getDeclaredFields();
List<Field> tableFields = new ArrayList<>(Arrays.asList(tableClazz.getDeclaredFields()));
// 查询旧数据
Map<String, Object> queryMap = OperatorLogQueryUtil.getObj(operatorObjId, tableClazz);
// 设置值
for (Field resultField : resultFields) {
Optional<Field> first = tableFields.stream().filter(i -> i.getName().equals(resultField.getName())).findFirst();
if (first.isPresent()) {
Field tableField = first.get();
resultField.setAccessible(true);
resultField.set(result, getFiledValue(tableField, queryMap));
}
}
return result;
}
/**
* 获取字段值
* @param tableField
* @param queryMap
* @return
*/
private static Object getFiledValue(Field tableField, Map<String, Object> queryMap) {
TableField tableFieldAnnotation = tableField.getDeclaredAnnotation(TableField.class);
if (tableFieldAnnotation == null) {
return null;
}
Object result = queryMap.get(tableFieldAnnotation.value());
// 特殊处理枚举
if (tableField.getType().isEnum()) {
EnumUtils.getIEnumBase(tableField.getType(), (Integer) result);
}
return result;
}
}
6.静态获取Bean
静态获取Bean
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 静态获取Bean
*/
@Component
public class SpringBeanContext implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBeanContext.applicationContext = applicationContext;
}
/**
* 静态获取Bean方法
*/
public static <T> T getSpringBean(Class<T> tClass) {
return applicationContext.getBean(tClass);
}
}
7.必要的类
基本常量
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 基本常量
*/
public class GlobalConstant {
public static final String TIMEZONE = "Asia/Shanghai";
public static final String PATTERN_DATE_TIME = "yyyy-MM-dd HH:mm:ss";
/**
* 表名称命名
*/
public static final String TABLE_NAME_COLUMN_NAME = "name";
/**
* 逗号
*/
public static final String COMMA = ", ";
} 新增对象内容生成器
import com.example.test05.demo.unit.OperatorLogUtil;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Author: DBC
* @Date: 2023/09/25
* @Description: 新增对象内容生成器
*/
public class ObjectAddLogUtil {
public String generatorContent(Object object, String objectType) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 获取操作数据元数据
OperatorLogMetaDataBuilder builder = new OperatorLogMetaDataBuilder();
OperatorLogMetaData metaData = builder.getChangeModel(object, object.getClass());
// 生成新增字段
List<OperatorLogRecord> filedContents = listFiledContent(metaData);
// 排序,并转为String
String content = OperatorLogUtil.sortOperatorLogRecord(filedContents);
return OperatorLogContentUtil.addObjFormat(metaData.getName(), objectType, content);
}
/**
* 生成字段变更内容
* @param metaData
* @return
*/
private List<OperatorLogRecord> listFiledContent(OperatorLogMetaData metaData) {
List<OperatorLogRecord> result = new ArrayList<>();
Map<String, String> fieldMap = metaData.getFieldMap();
Map<String, Integer> filedNameAndPriorityMap = metaData.getFiledNameAndPriorityMap();
for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
// 新增成员
String fieldContent = OperatorLogContentUtil.addFiledFormat(entry.getKey(), entry.getValue());
OperatorLogRecord record = new OperatorLogRecord(filedNameAndPriorityMap.get(entry.getKey()), fieldContent);
result.add(record);
}
return result;
}
} 更新对象内容生成器
import com.example.test05.demo.exception.BizException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author DBC
* @Description: 更新对象内容生成器
*/
public class ObjectUpdateLogUtil {
/**
* 生成变更记录
* @return 变更记录
*/
public List<String> generatorChangeLog(Object oldObject,Class<?> oldClazz,Object newObject,Class<?> newClazz,String objectType) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
if (Objects.isNull(oldObject) || Objects.isNull(newObject)){
throw new BizException(0,"对比对象不能为null");
}
// 封装对象
OperatorLogMetaDataBuilder builder = new OperatorLogMetaDataBuilder();
OperatorLogMetaData oldOperatorLogMetaData = builder.getChangeModel(oldObject, oldClazz);
OperatorLogMetaData newOperatorLogMetaData = builder.getChangeModel(newObject, newClazz);
return generatorChangeLog(oldOperatorLogMetaData, newOperatorLogMetaData,objectType);
}
/**
* 生成变更记录
* @return 变更记录
*/
public List<String> generatorChangeLog(OperatorLogMetaData oldOperatorLogMetaData, OperatorLogMetaData newOperatorLogMetaData, String objectType) {
List<String> result = new ArrayList<>();
// 父类字段变更和引用子对象
String filedChangeLog = getFiledChangeLog(oldOperatorLogMetaData.getFieldMap(), newOperatorLogMetaData.getFieldMap(),
oldOperatorLogMetaData.getSubObjectMap(), newOperatorLogMetaData.getSubObjectMap(), oldOperatorLogMetaData.getFiledNameAndPriorityMap(), oldOperatorLogMetaData.getName(), objectType);
if (isNoBlankString(filedChangeLog)){
result.add(filedChangeLog);
}
// 复杂列表变更
result.addAll(getComplexListChangeLogs(oldOperatorLogMetaData.getListMap(), newOperatorLogMetaData.getListMap()));
return result;
}
/**
* 获取父类字段变更和引用子对象字段变更
*/
private String getFiledChangeLog(Map<String,String> oldFieldMap, Map<String,String> newFieldMap,
Map<String, OperatorLogMetaData> oldQuoteSubObject, Map<String, OperatorLogMetaData> newQuoteSubObject,
Map<String,Integer> filedNameAndPriorityMap, String objectName, String objectType){
List<OperatorLogRecord> result = new ArrayList<>();
// 父类字段变更
if (Objects.nonNull(oldFieldMap) && Objects.nonNull(newFieldMap)){
for (Map.Entry<String,String> entry : oldFieldMap.entrySet()){
String newValue = newFieldMap.get(entry.getKey());
if (Objects.nonNull(newValue) && !newValue.equals(entry.getValue())){
result.add(new OperatorLogRecord(filedNameAndPriorityMap.get(entry.getKey()),OperatorLogContentUtil.updateFiledFormat(entry.getValue(), newValue, entry.getKey())));
}
}
}
// 引用字段变更
if (Objects.nonNull(oldQuoteSubObject) && Objects.nonNull(newQuoteSubObject)){
for (Map.Entry<String, OperatorLogMetaData> entry : oldQuoteSubObject.entrySet()){
OperatorLogMetaData newValue = newQuoteSubObject.get(entry.getKey());
if (Objects.nonNull(newValue) && !newValue.getId().equals(entry.getValue().getId())){
result.add(new OperatorLogRecord(filedNameAndPriorityMap.get(entry.getKey()),OperatorLogContentUtil.updateFiledFormat(entry.getValue().getName(), newValue.getName(), entry.getKey())));
}
}
}
if (!result.isEmpty()){
// 根据优先级排序
result.sort(Comparator.comparingInt(OperatorLogRecord::getPriority));
return OperatorLogContentUtil.updateFormat(objectType, objectName) + result.stream().map(OperatorLogRecord::getChangeLogContent).collect(Collectors.joining());
}
return null;
}
/**
* 获取复杂列表变更
*/
private List<String> getComplexListChangeLogs(Map<String,List<OperatorLogMetaData>> oldListMap, Map<String,List<OperatorLogMetaData>> newListMap){
List<String> result = new ArrayList<>();
if (Objects.isNull(oldListMap) || Objects.isNull(newListMap)){
return result;
}
for (Map.Entry<String,List<OperatorLogMetaData>> entry : oldListMap.entrySet()){
List<OperatorLogMetaData> newOperatorLogMetaData = newListMap.get(entry.getKey());
if (Objects.nonNull(newOperatorLogMetaData)){
result.addAll(getComplexListChangeLog(entry.getValue(), newOperatorLogMetaData,entry.getKey()));
}
}
return result;
}
/**
* 获取复杂列表变更
*/
private List<String> getComplexListChangeLog(List<OperatorLogMetaData> oldOperatorLogMetaDatas, List<OperatorLogMetaData> newOperatorLogMetaDatas, String objectType){
List<String> result = new ArrayList<>();
if (Objects.isNull(oldOperatorLogMetaDatas) || Objects.isNull(newOperatorLogMetaDatas)){
return result;
}
// 新增
List<String> addObjectNames = newOperatorLogMetaDatas.stream().filter(i -> oldOperatorLogMetaDatas.stream()
.noneMatch(j -> j.getId().equals(i.getId()))).map(OperatorLogMetaData::getName).collect(Collectors.toList());
if (isNoBlankList(addObjectNames)){
result.add(OperatorLogContentUtil.addBatchFieldFormat(addObjectNames, objectType));
}
// 删除
List<String> deleteObjectNames = oldOperatorLogMetaDatas.stream().filter(i -> newOperatorLogMetaDatas.stream()
.noneMatch(j -> j.getId().equals(i.getId()))).map(OperatorLogMetaData::getName).collect(Collectors.toList());
if (isNoBlankList(deleteObjectNames)){
result.add(OperatorLogContentUtil.deleteBatchFieldFormat(deleteObjectNames, objectType));
}
// 更新
for (OperatorLogMetaData oldOperatorLogMetaData : oldOperatorLogMetaDatas){
for (OperatorLogMetaData newOperatorLogMetaData : newOperatorLogMetaDatas){
if (oldOperatorLogMetaData.getId().equals(newOperatorLogMetaData.getId())){
result.addAll(generatorChangeLog(oldOperatorLogMetaData, newOperatorLogMetaData,objectType));
}
}
}
return result;
}
/**
* 判断字符串是否为null或空字符
*/
public boolean isNoBlankString(String str){
return Objects.nonNull(str) && str.length() > 0;
}
/**
* 判断列表是否为null或大小为0
*/
public boolean isNoBlankList(List<?> list){
return Objects.nonNull(list) && !list.isEmpty();
}
} 操作内容格式
import java.util.List;
/**
* @Author: DBC
* @Date: 2023/09/25
* @Description: 操作内容格式
*/
public class OperatorLogContentUtil {
/**
* 批量新增子对象格式
*/
public static String addBatchSubObjFormat(String addSubObjNames, String addObjType, String objName, String objType) {
return String.format("%s【%s】新增:%s【%s】", objType, objName, addObjType, addSubObjNames);
}
/**
* 列表字段新增格式
*/
public static String addBatchFieldFormat(List<String> addObjectName, String objectType){
return String.format("新增%s:%s", objectType, String.join(", ", addObjectName));
}
/**
* 列表字段删除格式
*/
public static String deleteBatchFieldFormat(List<String> addObjectName, String objectType){
return String.format("删除%s:%s", objectType, String.join(", ", addObjectName));
}
/**
* 新增时成员格式
* @param filedName
* @param filedValue
* @return
*/
public static String addFiledFormat(String filedName, String filedValue) {
return String.format("%s:%s;", filedName, filedValue);
}
/**
* 添加对象格式
* @param objName
* @param objectType
* @param content
* @return
*/
public static String addObjFormat(String objName, String objectType, String content) {
if (objName !=null){
return String.format("新增%s【%s】: %s", objectType, objName, content);
}else {
return String.format("新增%s:%s", objectType, content);
}
}
/**
* 更新字段格式
*/
public static String updateFiledFormat(String oldValue, String newValue, String fieldName){
return String.format("%s: %s --> %s", fieldName, oldValue , newValue + ";");
}
/**
* 更新格式
*/
public static String updateFormat(String objectType,String objectName){
return String.format("更新%s【%s】:", objectType ,objectName);
}
public static String deleteFormat(String objType, String objName) {
return String.format("删除%s【%s】", objType, objName);
}
} 操作日志内容元模型
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author DBC
* 操作日志内容元模型
*/
@Getter
@Setter
public class OperatorLogMetaData {
private String id;
// 对象名
private String name;
// 除复杂列表和独立子对象外的所有字段 key:字段名 value:字段值
private Map<String,String> fieldMap = new HashMap<>();
// 引用子对象 key:字段名 value:子对象
private Map<String, OperatorLogMetaData> subObjectMap = new HashMap<>();
//复杂列表字段 key:字段名 value:复杂列表
private Map<String,List<OperatorLogMetaData>> listMap = new HashMap<>();
// 优先级
private Map<String,Integer> filedNameAndPriorityMap = new HashMap<>();
} 构建操作日志内容元数据
import com.example.test05.demo.annotation.FieldIdentifyAnnotation;
import com.example.test05.demo.exception.BizException;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* @Author: DBC
* @Date: 2023/09/25
* @Description: 构建操作日志内容元数据
*/
public class OperatorLogMetaDataBuilder {
/**
* 封装数据
* @param object 目标对象
* @param clazz 目标类
* @return 封装类
*/
public OperatorLogMetaData getChangeModel(Object object, Class<?> clazz) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
OperatorLogMetaData result = new OperatorLogMetaData();
String name = null;
String id = null;
Map<String,String> fieldMap = new HashMap<>(16);
Map<String, OperatorLogMetaData> subObjectMap = new HashMap<>(16);
Map<String, List<OperatorLogMetaData>> list = new HashMap<>(16);
Map<String,Integer> filedNameAndPriorityMap = new HashMap<>(16);
// 遍历该类的所有变量
for (Field field : clazz.getDeclaredFields()){
field.setAccessible(true);
// 判断该变量有无Comparator注解
FieldIdentifyAnnotation comparator = field.getAnnotation(FieldIdentifyAnnotation.class);
if (Objects.nonNull(comparator)) {
// 变量的值
Object filedValue = field.get(object);
// 判断是否是唯一ID
if (comparator.id()) {
id = String.valueOf(filedValue);
} else {
// 字段名
String filedName = comparator.filedName();
if (comparator.isObjectName()) {
name = String.valueOf(filedValue);
}
// 显示优先级
filedNameAndPriorityMap.put(filedName, comparator.priority());
// 判断变量类型
switch (comparator.type()) {
case NORMAL:
fieldMap.put(filedName, String.valueOf(filedValue));
break;
case ENUM:
fieldMap.put(filedName, getDictionary((Integer) filedValue, comparator.dictionary()));
break;
case SUB_OBJECT:
fieldMap.putAll(getSubObject(filedNameAndPriorityMap,filedValue, field.getType()));
break;
case QUOTE_SUB_OBJECT:
subObjectMap.put(filedName, getChangeModel(filedValue, field.getType()));
break;
case SIMPLE_LIST:
fieldMap.put(filedName, getSimpleList((List<?>) filedValue));
break;
case COMPLEX_LIST:
list.put(filedName, getComplexList((List<?>) filedValue, field.getGenericType()));
break;
default:
throw new BizException(0,"没有这种类型的变量");
}
}
}
}
result.setId(id);
result.setName(name);
result.setFieldMap(fieldMap);
result.setSubObjectMap(subObjectMap);
result.setListMap(list);
result.setFiledNameAndPriorityMap(filedNameAndPriorityMap);
return result;
}
/**
* 获取字典值
*/
public String getDictionary(int coed, Class<?> dictionaryEnum) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Object[] enumConstants = dictionaryEnum.getEnumConstants();
if (Objects.isNull(enumConstants) || enumConstants.length == 0){
throw new BizException(0,"该类不是枚举类");
} else {
Method getValue = dictionaryEnum.getMethod("getValue", int.class);
return (String) getValue.invoke(enumConstants[0], coed);
}
}
/**
* 把简单列表变成按字典排序的String
*/
public String getSimpleList(List<?> simpleList){
List<String> result = new ArrayList<>();
for (Object object : simpleList){
result.add(String.valueOf(object));
}
return result.stream().sorted().collect(Collectors.joining(","));
}
/**
* 获取子对象是父类一部分的值
*/
public Map<String,String> getSubObject(Map<String,Integer> filedNameAndPriorityMap,Object object, Class<?> clazz) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Map<String,String> fieldMap = new HashMap<>(16);
// 遍历该类的所有变量
for (Field field : clazz.getDeclaredFields()){
field.setAccessible(true);
// 判断该变量有无Comparator注解
FieldIdentifyAnnotation comparator = field.getAnnotation(FieldIdentifyAnnotation.class);
if (Objects.nonNull(comparator)){
// 变量的值
Object filedValue = field.get(object);
// 字段名
String filedName = comparator.filedName();
filedNameAndPriorityMap.put(filedName, comparator.priority());
// 判断变量类型
switch (comparator.type()){
case NORMAL:
fieldMap.put(filedName, String.valueOf(filedValue));
break;
case ENUM:
fieldMap.put(filedName, getDictionary((Integer) filedValue, comparator.dictionary()));
break;
case SUB_OBJECT:
fieldMap.putAll(getSubObject(filedNameAndPriorityMap,filedValue,field.getType()));
break;
case SIMPLE_LIST:
fieldMap.put(filedName, getSimpleList((List<?>) filedValue));
break;
default:
throw new BizException(0,"没有这种类型的变量");
}
}
}
return fieldMap;
}
/**
* 获取复杂列表的值
*/
public List<OperatorLogMetaData> getComplexList(List<?> complexList, Type genericsType) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 得到泛型里的class类型对象
ParameterizedType pt = (ParameterizedType) genericsType;
Class<?> actualTypeArgument = (Class<?>)pt.getActualTypeArguments()[0];
List<OperatorLogMetaData> result = new ArrayList<>();
for (Object object : complexList){
result.add(getChangeModel(object, actualTypeArgument));
}
return result;
}
}
变更内容记录
/**
* @author DBC
* 变更内容记录
*/
public class OperatorLogRecord {
private Integer priority;
private String changeLogContent;
public OperatorLogRecord(Integer priority, String changeLogContent) {
this.priority = priority;
this.changeLogContent = changeLogContent;
}
public Integer getPriority() {
return priority;
}
public void setChangeLogContent(String changeLogContent) {
this.changeLogContent = changeLogContent;
}
public String getChangeLogContent() {
return changeLogContent;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
}
表名常量
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 表名常量
*/
public class TableNameConstant {
public static final String USER = "user";
public static final String OPERATOR_LOG = "operator_log";
public static final String STUDENT = "student";
public static final String ROLE = "role";
public static final String PROJECT = "project";
public static final String PROJECT_STUDENT = "st_pro";
}
8.操作日志Mapper
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.test05.demo.model.OperatorLog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @Author: DBC
* @Date: 2023/11/11
* @Description: 操作日志Mapper
*/
@Mapper
public interface OperatorLogMapper {
Integer insertDynamicTable(@Param("tableName")String tableName, @Param("operatorLog") OperatorLog operatorLog);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.test05.demo.mapper.OperatorLogMapper">
<insert id="insertDynamicTable">
INSERT INTO
${tableName}
(
id, `type`, operator_obj_id, operator_obj_type, content,
user_id, start_time, end_time, has_finish, fail_reason
)
VALUES
(
#{operatorLog.id}, #{operatorLog.type}, #{operatorLog.operatorObjId},
#{operatorLog.operatorObjType}, #{operatorLog.content}, #{operatorLog.userId},
#{operatorLog.startTime}, #{operatorLog.endTime}, #{operatorLog.hasFinish},
#{operatorLog.failReason}
)
</insert>
</mapper> 本文作者为DBC,转载请注明。