From 51ebb7f7bc0386482f975b5bfb00574197bdfeb4 Mon Sep 17 00:00:00 2001 From: lbw Date: Tue, 28 Feb 2023 13:14:13 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20Introducing=20new=20features.=20co?= =?UTF-8?q?mmon-audit=20=E5=AD=97=E6=AE=B5=E5=8F=98=E6=9B=B4=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pigx-common/pigx-common-audit/pom.xml | 38 +++++++++ .../common/audit/AuditAutoConfiguration.java | 52 +++++++++++++ .../pigx/common/audit/annotation/Audit.java | 47 +++++++++++ .../pigx/common/audit/aop/AuditAspect.java | 36 +++++++++ .../audit/handle/DefaultAuditLogHandle.java | 77 +++++++++++++++++++ .../common/audit/handle/IAuditLogHandle.java | 21 +++++ .../common/audit/handle/ICompareHandle.java | 22 ++++++ .../audit/handle/JavesCompareHandle.java | 37 +++++++++ .../common/audit/support/DataAuditor.java | 27 +++++++ .../pigx/common/audit/support/SpelParser.java | 39 ++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + 11 files changed, 397 insertions(+) create mode 100644 pigx-common/pigx-common-audit/pom.xml create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/AuditAutoConfiguration.java create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/annotation/Audit.java create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/aop/AuditAspect.java create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/DefaultAuditLogHandle.java create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/IAuditLogHandle.java create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/ICompareHandle.java create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/JavesCompareHandle.java create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/DataAuditor.java create mode 100644 pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/SpelParser.java create mode 100644 pigx-common/pigx-common-audit/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/pigx-common/pigx-common-audit/pom.xml b/pigx-common/pigx-common-audit/pom.xml new file mode 100644 index 000000000..27f258374 --- /dev/null +++ b/pigx-common/pigx-common-audit/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + com.pig4cloud + pigx-common + 5.0.0-SNAPSHOT + + + pigx-common-audit + jar + + pigx 数据审计相关工具类 + + + + + org.javers + javers-core + + + + org.springframework.boot + spring-boot-starter-aop + + + + com.pig4cloud + pigx-upms-api + + + + org.springframework.security + spring-security-core + + + 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 new file mode 100644 index 000000000..8d1e4f5db --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/AuditAutoConfiguration.java @@ -0,0 +1,52 @@ +package com.pig4cloud.pigx.common.audit; + +import com.pig4cloud.pigx.admin.api.feign.RemoteAuditLogService; +import com.pig4cloud.pigx.admin.api.feign.RemoteLogService; +import com.pig4cloud.pigx.common.audit.aop.AuditAspect; +import com.pig4cloud.pigx.common.audit.handle.DefaultAuditLogHandle; +import com.pig4cloud.pigx.common.audit.handle.ICompareHandle; +import com.pig4cloud.pigx.common.audit.handle.IAuditLogHandle; +import com.pig4cloud.pigx.common.audit.handle.JavesCompareHandle; +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.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.scheduling.annotation.EnableAsync; + +import java.util.Optional; + +/** + * 审计自动配置类 + * + * @author lengleng + * @date 2023/2/26 + */ +@EnableAsync +@AutoConfiguration +@Import({ AuditAspect.class, SpelParser.class }) +public class AuditAutoConfiguration { + + /** + * 默认注入 javers 的比较器实现 + * @param auditNameHandleOptional 注入审计用户来源 + * @return ICompareHandle + */ + @Bean + @ConditionalOnMissingBean + public ICompareHandle compareHandle(Optional auditNameHandleOptional) { + return new JavesCompareHandle(auditNameHandleOptional); + } + + /** + * 默认的审计日志存储策略 + * @return DefaultAuditLogHandle + */ + @Bean + @ConditionalOnMissingBean + public IAuditLogHandle auditLogHandle(RemoteAuditLogService logService, KeyStrResolver tenantKeyStrResolver) { + return new DefaultAuditLogHandle(logService, tenantKeyStrResolver); + } + +} 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 new file mode 100644 index 000000000..be09a2c7b --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/annotation/Audit.java @@ -0,0 +1,47 @@ +package com.pig4cloud.pigx.common.audit.annotation; + +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author lengleng + * + * 记需要进行审计的方法 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Audit { + + /** + * 此审计的名称 + * @return 审计名称 + */ + String name(); + + @AliasFor("spel") + String value() default ""; + + /** + * 默认查询的表达式 + * @return string + */ + @AliasFor("value") + String spel() default ""; + + /** + * 查询原有结果的表达式,如果为空取 spel() + * @return string + */ + String oldVal() default ""; + + /** + * 查询编辑后的表达式,如果为空取 spel() + * @return string + */ + String newVal() default ""; + +} 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 new file mode 100644 index 000000000..e47b1f447 --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/aop/AuditAspect.java @@ -0,0 +1,36 @@ +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 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 + * @date 2023/2/26 + * + * 拦截被@Audit注解标记的方法,并记录前后变化值 + */ + +@Aspect +@RequiredArgsConstructor +public class AuditAspect { + + private final ICompareHandle compareHandle; + + @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 result = joinPoint.proceed(); + + compareHandle.compare(oldVal, joinPoint, audit); + return result; + } + +} diff --git a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/DefaultAuditLogHandle.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/DefaultAuditLogHandle.java new file mode 100644 index 000000000..730a9a311 --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/DefaultAuditLogHandle.java @@ -0,0 +1,77 @@ +package com.pig4cloud.pigx.common.audit.handle; + +import com.pig4cloud.pigx.admin.api.entity.SysAuditLog; +import com.pig4cloud.pigx.admin.api.feign.RemoteAuditLogService; +import com.pig4cloud.pigx.common.audit.annotation.Audit; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.core.util.KeyStrResolver; +import com.pig4cloud.pigx.common.core.util.SpringContextHolder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.javers.core.Changes; +import org.javers.core.diff.Change; +import org.javers.core.diff.changetype.ValueChange; +import org.springframework.scheduling.annotation.Async; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * 默认的审计日志存储 + * + * @author lengleng + * @date 2023/2/27 + */ +@Slf4j +@RequiredArgsConstructor +public class DefaultAuditLogHandle implements IAuditLogHandle { + + private final RemoteAuditLogService remoteLogService; + + private final KeyStrResolver tenantKeyStrResolver; + + @Override + public void handle(Audit audit, Changes changes) { + // 如果变更项为空则不进行审计 + if (changes.isEmpty()) { + return; + } + + // 获取当前操作人 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + // 如果获取不到授权信息、或者没有身份信息的接口 直接跳过处理 + if (Objects.isNull(authentication) || !authentication.isAuthenticated()) { + return; + } + + List auditLogList = new ArrayList<>(); + for (Change change : changes) { + ValueChange valueChange = (ValueChange) change; + + SysAuditLog auditLog = new SysAuditLog(); + auditLog.setAuditName(audit.name()); + auditLog.setAuditField(valueChange.getPropertyName()); // 修改的字段名称 + auditLog.setBeforeVal(valueChange.getLeft().toString()); // 更改前的值 + auditLog.setAfterVal(valueChange.getRight().toString()); // 更改后的值 + auditLog.setCreateBy(authentication.getName()); // 操作人 + auditLog.setCreateTime(LocalDateTime.now()); // 操作时间 + auditLog.setTenantId(Long.parseLong(tenantKeyStrResolver.key())); // 设置操作所属租户 + + auditLogList.add(auditLog); + } + + // 异步保存日志,提升性能 + IAuditLogHandle auditLogHandle = SpringContextHolder.getBean(IAuditLogHandle.class); + auditLogHandle.asyncSend(auditLogList); + } + + @Async + public void asyncSend(List auditLogList) { + remoteLogService.saveLog(auditLogList, SecurityConstants.FROM_IN); + } + +} diff --git a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/IAuditLogHandle.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/IAuditLogHandle.java new file mode 100644 index 000000000..10790f474 --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/IAuditLogHandle.java @@ -0,0 +1,21 @@ +package com.pig4cloud.pigx.common.audit.handle; + +import com.pig4cloud.pigx.admin.api.entity.SysAuditLog; +import com.pig4cloud.pigx.common.audit.annotation.Audit; +import org.javers.core.Changes; + +import java.util.List; + +/** + * @author lengleng + * @date 2023/2/26 + * + * 审计日志处理器 + */ +public interface IAuditLogHandle { + + void handle(Audit audit, Changes changes); + + void asyncSend(List auditLogList); + +} diff --git a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/ICompareHandle.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/ICompareHandle.java new file mode 100644 index 000000000..6d176841f --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/ICompareHandle.java @@ -0,0 +1,22 @@ +package com.pig4cloud.pigx.common.audit.handle; + +import com.pig4cloud.pigx.common.audit.annotation.Audit; +import org.aspectj.lang.ProceedingJoinPoint; +import org.javers.core.Changes; + +/** + * 比较器抽象类 + * + * @author lengleng + * @date 2023/2/26 + */ +public interface ICompareHandle { + + /** + * 比较两个对象是否变更,及其变更后如何审计 + * @param oldVal 原有值 + * @param newVal 变更后值 + */ + Changes compare(Object oldVal, ProceedingJoinPoint joinPoint, Audit 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 new file mode 100644 index 000000000..5b00280e1 --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/handle/JavesCompareHandle.java @@ -0,0 +1,37 @@ +package com.pig4cloud.pigx.common.audit.handle; + +import com.pig4cloud.pigx.common.audit.annotation.Audit; +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; + +/** + * javers 比较实现 + * + * @author lengleng + * @date 2023/2/26 + */ +@RequiredArgsConstructor +public class JavesCompareHandle implements ICompareHandle { + + private final Optional auditLogHandleOptional; + + /** + * 比较两个对象是否变更,及其变更后如何审计 + * @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()); + 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/DataAuditor.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/DataAuditor.java new file mode 100644 index 000000000..294c99942 --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/DataAuditor.java @@ -0,0 +1,27 @@ +package com.pig4cloud.pigx.common.audit.support; + +import lombok.experimental.UtilityClass; +import org.javers.core.Changes; +import org.javers.core.Javers; +import org.javers.core.JaversBuilder; +import org.javers.core.diff.Diff; + +import static org.javers.core.diff.ListCompareAlgorithm.LEVENSHTEIN_DISTANCE; + +/** + * javers 审计工具 + * + * @author lengleng + * @date 2023/2/26 + */ +@UtilityClass +public class DataAuditor { + + private final Javers javers = JaversBuilder.javers().withListCompareAlgorithm(LEVENSHTEIN_DISTANCE).build(); + + public Changes compare(Object newObj, Object oldObj) { + Diff compare = javers.compare(newObj, oldObj); + return compare.getChanges(); + } + +} diff --git a/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/SpelParser.java b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/SpelParser.java new file mode 100644 index 000000000..88aaeb4a9 --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/java/com/pig4cloud/pigx/common/audit/support/SpelParser.java @@ -0,0 +1,39 @@ +package com.pig4cloud.pigx.common.audit.support; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * 表达式处理器 + * + * @author lengleng + * @date 2023/2/26 + */ +public class SpelParser implements ApplicationContextAware { + + private final static SpelExpressionParser parser = new SpelExpressionParser(); + + private static ApplicationContext applicationContext; + + public static Object parser(ProceedingJoinPoint joinPoint, String spel) { + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setBeanResolver(new BeanFactoryResolver(applicationContext)); + for (int i = 0; i < joinPoint.getArgs().length; i++) { + String paramName = ((MethodSignature) joinPoint.getSignature()).getParameterNames()[i]; + context.setVariable(paramName, joinPoint.getArgs()[i]); + } + return parser.parseExpression(spel).getValue(context); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpelParser.applicationContext = applicationContext; + } + +} diff --git a/pigx-common/pigx-common-audit/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/pigx-common/pigx-common-audit/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..d295c990b --- /dev/null +++ b/pigx-common/pigx-common-audit/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.pig4cloud.pigx.common.audit.AuditAutoConfiguration