一、定义切面对象
import java.lang.annotation.*; /** * 字段变更日志注解 * * @author zhuzhen */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogFieldChange { /** * 业务类型,枚举针对不同的业务定义不同的对象,以及需要判断发生数据变更的字段名 */ FieldChangeBizTypeEnum bizType(); /** * 业务主键字段名,eg:#product.id */ String bizIdField(); /** * 业务编码字段名 */ String bizNoField() default ""; /** * 租户编码 */ String tenantCode(); /** * 操作人 */ String operator(); }
@Getter public enum FieldChangeBizTypeEnum { // 结算标志业务下需要记录发生变更的字段 SM_REPAIR_SETTLEMENT_STANDARD("settlement_standard", "结算标准", Arrays.asList("productLine", "productType", "projectCode", "coolingCapacityMax", "coolingCapacityMin", "baseRepairCost", "extraRepairCost", "repairStandardItemCode", "repairType", "completionTime")), ; private final String code; private final String desc;
// 需要判断是否发生变更的字段 private final List<String> fieldList; FieldChangeBizTypeEnum(String code, String desc, List<String> fieldList) { this.code = code; this.desc = desc; this.fieldList = fieldList; } }
二、AOP切面拦截处理EL表达式解析
类:ExpressionEvaluator定义。可无脑使用
import org.springframework.aop.support.AopUtils; import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.context.expression.CachedExpressionEvaluator; import org.springframework.context.expression.MethodBasedEvaluationContext; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ExpressionEvaluator<T> extends CachedExpressionEvaluator { private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64); private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64); public EvaluationContext createEvaluationContext(Object o, Class<?> targetClass, Method method, Object[] args) { Method targetMethod = getTargetMethod(targetClass, method); ExpressionRootObject root = new ExpressionRootObject(o, args); return new MethodBasedEvaluationContext(root, targetMethod, args, this.parameterNameDiscoverer); } public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evaluationContext, Class<T> clazz) { return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evaluationContext, clazz); } private Method getTargetMethod(Class<?> targetClass, Method method) { AnnotatedElementKey annotatedElementKey = new AnnotatedElementKey(method, targetClass); Method targetMethod = this.targetMethodCache.get(annotatedElementKey); if (targetMethod == null) { targetMethod = AopUtils.getMostSpecificMethod(method, targetClass); this.targetMethodCache.put(annotatedElementKey, targetMethod); } return targetMethod; } }
类:ExpressionRootObject定义,无脑使用即可
public class ExpressionRootObject { private final Object object; private final Object[] args; public ExpressionRootObject(Object object, Object[] args) { this.object = object; this.args = args; } public Object getObject() { return object; } public Object[] getArgs() { return args; } }
三、Aspect定义
import cn.hutool.core.util.IdUtil; import com.auxgroup.adp.common.data.toolkit.LocalTenantContextHolder; import com.auxgroup.adp.commons.utils.AuxJsonUtil; import com.auxgroup.adp.commons.utils.AuxStringUtils; import com.auxgroup.gcss.api.business.component.LocalTenantCode; import com.auxgroup.gcss.api.business.component.utilt.ZoneIdUtil; import com.auxgroup.gcss.api.business.dto.system.SysFieldChangeLogDto; import com.auxgroup.gcss.api.business.intf.system.ISysFieldChangeLogService; import com.auxgroup.gcss.server.business.component.aop.annotation.LogFieldChange; import com.auxgroup.gcss.server.business.constant.BusinessConstant; import org.apache.commons.beanutils.BeanUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.expression.EvaluationContext; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.lang.reflect.Method; import java.util.Date; import java.util.List; /** * 字段变更日志记录切面 * * @author zhuzhen */ @Aspect @Component public class LogFieldChangeAspect { private final static Logger LOGGER = LoggerFactory.getLogger(LogFieldChangeAspect.class); public static final ExpressionEvaluator<String> EVALUATOR = new ExpressionEvaluator<>(); @Resource private ISysFieldChangeLogService sysFieldChangeLogService; /** * 配置切入点 */ @Pointcut("@annotation(com.auxgroup.gcss.server.business.component.aop.annotation.LogFieldChange)") public void pointCut() { // Do nothing } @AfterReturning(pointcut = "pointCut()", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) { try { LogFieldChange logFieldChange = getAnnotationLog(joinPoint); if (logFieldChange == null) { return; } Object[] args = joinPoint.getArgs(); if (args == null || args.length < 1) { return; } LOGGER.info("===========字典变更记录变更信息,args:{}", AuxJsonUtil.toString(args)); List<String> fieldList = logFieldChange.bizType().getFieldList(); String bizIdField = logFieldChange.bizIdField(); String bizId = this.evalParam(joinPoint, bizIdField); String bizType = logFieldChange.bizType().getCode(); String tenantCode = this.evalParam(joinPoint, logFieldChange.tenantCode()); if (AuxStringUtils.isBlank(bizId) || AuxStringUtils.isBlank(tenantCode)) { LOGGER.error("===================字段变更记录变更信息失败,bizId为空======bizType:{}", bizType); return; } String bizNo = this.evalParam(joinPoint, logFieldChange.bizNoField()); String operator = this.evalParam(joinPoint, logFieldChange.operator()); operator = AuxStringUtils.isNotBlank(operator) ? operator : BusinessConstant.SYSTEM; // 一个字段一个字段排查是否变更 for (String field : fieldList) { String fieldValue = BeanUtils.getProperty(args[0], field); LOGGER.info("=========变更信息日志记录:bizType:{},bizId:{},field:{},fieldValue:{}", bizType, bizId, field, fieldValue); try { saveAddFieldChangeLog(tenantCode, bizType, bizId, bizNo, field, fieldValue, operator, ZoneIdUtil.getCurrentTime()); } catch (Exception e) { LOGGER.error("===================字段变更记录变更信息异常:", e); } } } catch (Exception e) { LOGGER.info("获取变更字段并保存变更日志失败,异常信息:", e); } } private void saveAddFieldChangeLog(String tenantCode, String bizType, String bizId, String bizNo, String field, String fieldValue, String createBy, Date currentTime) { SysFieldChangeLogDto data = sysFieldChangeLogService.getNewFieldChangeLog(tenantCode, bizType, bizId, field).getData(); if (data == null) { SysFieldChangeLogDto logDto = new SysFieldChangeLogDto(); logDto.setLogId(IdUtil.fastSimpleUUID()); logDto.setTenantCode(tenantCode); logDto.setBizType(bizType); logDto.setBizId(bizId); logDto.setBizNo(bizNo); logDto.setLogType(1); logDto.setChangeField(field); logDto.setChangeBefore(null); logDto.setChangeAfter(fieldValue); logDto.setCreateTime(currentTime); logDto.setUpdateTime(currentTime); logDto.setCreateBy(createBy); sysFieldChangeLogService.insertByDto(logDto); } else { String changeAfter = data.getChangeAfter(); if (AuxStringUtils.equals(fieldValue, changeAfter)) { return; } data.setId(null); data.setLogId(IdUtil.fastSimpleUUID()); data.setBizNo(bizNo); data.setLogType(2); data.setChangeBefore(changeAfter); data.setChangeAfter(fieldValue); data.setCreateTime(currentTime); data.setUpdateTime(currentTime); data.setCreateBy(createBy); data.setVersion(data.getVersion() + 1); sysFieldChangeLogService.insertByDto(data); } } /** * 获取存在的注解 */ private LogFieldChange getAnnotationLog(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(LogFieldChange.class); } return null; } private String evalParam(JoinPoint joinPoint, String elExpressKey) { if (AuxStringUtils.isBlank(elExpressKey)) { return null; } MethodSignature ms = (MethodSignature) joinPoint.getSignature(); Method method = ms.getMethod(); Object[] args = joinPoint.getArgs(); Object target = joinPoint.getTarget(); Class<?> targetClass = target.getClass(); EvaluationContext context = EVALUATOR.createEvaluationContext(target, targetClass, method, args); AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass); return EVALUATOR.condition(elExpressKey, elementKey, context, String.class); } }
四、使用
@LogFieldChange(bizType = FieldChangeBizTypeEnum.SM_REPAIR_SETTLEMENT_STANDARD, bizIdField = "#dto.standardId", bizNoField = "#dto.standardNo", tenantCode = "#dto.tenantCode", operator = "#dto.createBy") @Override public AuxResponse<Boolean> insert(RepairSettlementStandardDto dto) { // do anyting }
@LogFieldChange(bizType = FieldChangeBizTypeEnum.SM_REPAIR_SETTLEMENT_STANDARD, bizIdField = "#dto.standardId",
bizNoField = "#dto.standardNo", tenantCode = "#dto.tenantCode", operator = "#dto.createBy")
@Override
public AuxResponse<Boolean> update(RepairSettlementStandardDto dto) {
// do anyting
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Blazor Hybrid适配到HarmonyOS系统
· 万字调研——AI生成内容检测
· 解决跨域问题的这6种方案,真香!
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库