一、必备类
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,转载请注明。