From 1d9bfd866fb7d167c3982c01fb1bd532ef7da9b7 Mon Sep 17 00:00:00 2001 From: PIGCLOUD Date: Mon, 18 May 2026 14:19:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(pigx-common-audit):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=AE=A1=E8=AE=A1=E6=A8=A1=E5=9D=97=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E4=B8=BB=E9=94=AE=E8=87=AA=E5=8A=A8=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=AF=B9=E8=B1=A1=E8=BF=9B=E8=A1=8C=E6=AF=94=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/audit/AuditAutoConfiguration.java | 20 +++- .../pigx/common/audit/annotation/Audit.java | 18 ++++ .../pigx/common/audit/aop/AuditAspect.java | 8 +- .../audit/handle/JavesCompareHandle.java | 8 +- .../audit/support/AuditValueResolver.java | 97 +++++++++++++++++++ 5 files changed, 141 insertions(+), 10 deletions(-) create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/AuditValueResolver.java diff --git a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/AuditAutoConfiguration.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/AuditAutoConfiguration.java index f2a9378c5..5d8192792 100644 --- a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/AuditAutoConfiguration.java +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/AuditAutoConfiguration.java @@ -6,10 +6,12 @@ import com.pig4cloud.pigx.common.audit.handle.DefaultAuditLogHandle; import com.pig4cloud.pigx.common.audit.handle.IAuditLogHandle; import com.pig4cloud.pigx.common.audit.handle.ICompareHandle; import com.pig4cloud.pigx.common.audit.handle.JavesCompareHandle; +import com.pig4cloud.pigx.common.audit.support.AuditValueResolver; import com.pig4cloud.pigx.common.audit.support.SpelParser; import com.pig4cloud.pigx.common.core.util.KeyStrResolver; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.scheduling.annotation.EnableAsync; @@ -34,8 +36,22 @@ public class AuditAutoConfiguration { */ @Bean @ConditionalOnMissingBean - public ICompareHandle compareHandle(Optional auditNameHandleOptional) { - return new JavesCompareHandle(auditNameHandleOptional); + public ICompareHandle compareHandle(Optional auditNameHandleOptional, + AuditValueResolver auditValueResolver) { + return new JavesCompareHandle(auditNameHandleOptional, auditValueResolver); + } + + /** + * 审计值解析器。 + * + * @param applicationContext Spring 上下文 + * @return AuditValueResolver + * @since 6.0 + */ + @Bean + @ConditionalOnMissingBean + public AuditValueResolver auditValueResolver(ApplicationContext applicationContext) { + return new AuditValueResolver(applicationContext); } /** diff --git a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/annotation/Audit.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/annotation/Audit.java index be09a2c7b..1cc22685d 100644 --- a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/annotation/Audit.java +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/annotation/Audit.java @@ -44,4 +44,22 @@ public @interface Audit { */ String newVal() default ""; + /** + * 主键表达式(SpEL)。配置后会通过 MyBatis-Plus Mapper selectById 查询切面执行前后的实体快照, + * 解析结果必须实现 {@link java.io.Serializable}。 + * + * @return 主键 SpEL 表达式 + * @since 6.0 + */ + String id() default ""; + + /** + * 显式指定 MyBatis-Plus Mapper 类型,必须是被 Spring 管理的 BaseMapper Bean。 + * 默认 {@code Void.class} 表示从当前 Service 的 {@code getBaseMapper()} 自动获取。 + * + * @return Mapper 类型 + * @since 6.0 + */ + Class mapper() default Void.class; + } diff --git a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/aop/AuditAspect.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/aop/AuditAspect.java index e47b1f447..95ba9b724 100644 --- a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/aop/AuditAspect.java +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/aop/AuditAspect.java @@ -2,12 +2,11 @@ package com.pig4cloud.pigx.common.audit.aop; import com.pig4cloud.pigx.common.audit.annotation.Audit; import com.pig4cloud.pigx.common.audit.handle.ICompareHandle; -import com.pig4cloud.pigx.common.audit.support.SpelParser; +import com.pig4cloud.pigx.common.audit.support.AuditValueResolver; import lombok.RequiredArgsConstructor; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.springframework.util.StringUtils; /** * @author lengleng @@ -22,11 +21,12 @@ public class AuditAspect { private final ICompareHandle compareHandle; + private final AuditValueResolver auditValueResolver; + @Around("@annotation(audit)") public Object auditLog(ProceedingJoinPoint joinPoint, Audit audit) throws Throwable { // 获取变更之前的结果 - Object oldVal = SpelParser.parser(joinPoint, - StringUtils.hasText(audit.oldVal()) ? audit.oldVal() : audit.spel()); + Object oldVal = auditValueResolver.resolveOldVal(joinPoint, audit); Object result = joinPoint.proceed(); compareHandle.compare(oldVal, joinPoint, audit); diff --git a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/JavesCompareHandle.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/JavesCompareHandle.java index 5b00280e1..11feda3a1 100644 --- a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/JavesCompareHandle.java +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/JavesCompareHandle.java @@ -1,12 +1,11 @@ package com.pig4cloud.pigx.common.audit.handle; import com.pig4cloud.pigx.common.audit.annotation.Audit; +import com.pig4cloud.pigx.common.audit.support.AuditValueResolver; import com.pig4cloud.pigx.common.audit.support.DataAuditor; -import com.pig4cloud.pigx.common.audit.support.SpelParser; import lombok.RequiredArgsConstructor; import org.aspectj.lang.ProceedingJoinPoint; import org.javers.core.Changes; -import org.springframework.util.StringUtils; import java.util.Optional; @@ -21,14 +20,15 @@ public class JavesCompareHandle implements ICompareHandle { private final Optional auditLogHandleOptional; + private final AuditValueResolver auditValueResolver; + /** * 比较两个对象是否变更,及其变更后如何审计 * @param oldVal 原有值 */ @Override public Changes compare(Object oldVal, ProceedingJoinPoint joinPoint, Audit audit) { - Object newVal = SpelParser.parser(joinPoint, - StringUtils.hasText(audit.newVal()) ? audit.newVal() : audit.spel()); + Object newVal = auditValueResolver.resolveNewVal(joinPoint, audit); Changes compare = DataAuditor.compare(oldVal, newVal); auditLogHandleOptional.ifPresent(handle -> handle.handle(audit, compare)); return compare; diff --git a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/AuditValueResolver.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/AuditValueResolver.java new file mode 100644 index 000000000..b61226d5a --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/AuditValueResolver.java @@ -0,0 +1,97 @@ +package com.pig4cloud.pigx.common.audit.support; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.pig4cloud.pigx.common.audit.annotation.Audit; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * 审计快照解析器。 + * + * @author lengleng + * @since 6.0 + */ +@RequiredArgsConstructor +public class AuditValueResolver { + + private final ApplicationContext applicationContext; + + /** + * 解析修改前快照。 + * + * @param joinPoint 连接点 + * @param audit 审计注解 + * @return 修改前快照 + */ + public Object resolveOldVal(ProceedingJoinPoint joinPoint, Audit audit) { + if (StringUtils.hasText(audit.id())) { + return selectById(joinPoint, audit); + } + return SpelParser.parser(joinPoint, StringUtils.hasText(audit.oldVal()) ? audit.oldVal() : audit.spel()); + } + + /** + * 解析修改后快照。 + * + * @param joinPoint 连接点 + * @param audit 审计注解 + * @return 修改后快照 + */ + public Object resolveNewVal(ProceedingJoinPoint joinPoint, Audit audit) { + if (StringUtils.hasText(audit.id())) { + return selectById(joinPoint, audit); + } + return SpelParser.parser(joinPoint, StringUtils.hasText(audit.newVal()) ? audit.newVal() : audit.spel()); + } + + private Object selectById(ProceedingJoinPoint joinPoint, Audit audit) { + Object id = SpelParser.parser(joinPoint, audit.id()); + if (Objects.isNull(id)) { + return null; + } + if (!(id instanceof Serializable serializableId)) { + throw new IllegalArgumentException("@Audit id 表达式解析结果必须实现 Serializable"); + } + return resolveMapper(joinPoint, audit).selectById(serializableId); + } + + private BaseMapper resolveMapper(ProceedingJoinPoint joinPoint, Audit audit) { + Object mapper; + if (audit.mapper() == Void.class) { + mapper = getBaseMapper(joinPoint); + } else { + try { + mapper = applicationContext.getBean(audit.mapper()); + } catch (NoSuchBeanDefinitionException ex) { + throw new IllegalStateException( + "未找到 @Audit mapper Bean: " + audit.mapper().getName() + ",请确认其已注册到 MyBatis-Plus", ex); + } + } + if (mapper instanceof BaseMapper baseMapper) { + return baseMapper; + } + throw new IllegalStateException("@Audit mapper 必须是 MyBatis-Plus BaseMapper 类型"); + } + + private Object getBaseMapper(ProceedingJoinPoint joinPoint) { + Object target = Objects.nonNull(joinPoint.getTarget()) ? joinPoint.getTarget() : joinPoint.getThis(); + if (Objects.isNull(target)) { + throw new IllegalStateException("@Audit 未指定 mapper,且无法获取当前 Service 实例"); + } + Method getBaseMapper = ReflectionUtils.findMethod(target.getClass(), "getBaseMapper"); + if (Objects.isNull(getBaseMapper)) { + throw new IllegalStateException("@Audit 未指定 mapper,当前 Service 未提供 getBaseMapper()"); + } + ReflectionUtils.makeAccessible(getBaseMapper); + return ReflectionUtils.invokeMethod(getBaseMapper, target); + } + +}