暂存提交

This commit is contained in:
2026-07-30 18:01:36 +08:00
commit 2f4e159356
368 changed files with 20370 additions and 0 deletions
@@ -0,0 +1,88 @@
package com.baiyue.common.audit;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.common.web.CorrelationContext;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.Instant;
import java.util.Arrays;
/**
* 业务审计切面,拦截审计注解方法并采集操作者、租户、请求、耗时、结果及脱敏前后快照。
*/
@Aspect
public class BusinessAuditAspect {
private static final Logger log = LoggerFactory.getLogger(BusinessAuditAspect.class);
private final AuditSink sink;
private final SensitiveDataSanitizer sanitizer;
private final String serviceName;
private final ExpressionParser parser = new SpelExpressionParser();
public BusinessAuditAspect(AuditSink sink, SensitiveDataSanitizer sanitizer, String serviceName) {
this.sink = sink; this.sanitizer = sanitizer; this.serviceName = serviceName;
}
@Around("@annotation(audit)")
public Object around(ProceedingJoinPoint pjp, BusinessAudit audit) throws Throwable {
long start = System.nanoTime();
String before = audit.captureBefore() ? sanitizer.sanitize(pjp.getArgs()) : null;
Object result = null; Throwable failure = null;
try { result = pjp.proceed(); return result; }
catch (Throwable ex) { failure = ex; throw ex; }
finally {
try { publish(pjp, audit, before, result, failure, (System.nanoTime() - start) / 1_000_000); }
catch (RuntimeException ex) { log.warn("Audit capture failed", ex); }
}
}
private void publish(ProceedingJoinPoint pjp, BusinessAudit a, String before, Object result,
Throwable failure, long duration) {
HttpServletRequest req = request();
AuthenticatedUser user = currentUser();
String resourceId = evaluateResourceId(pjp, a.resourceId());
sink.publish(new AuditEvent(a.event(), a.resourceType(), resourceId, a.description(),
req == null ? null : CorrelationContext.requestId(req), req == null ? null : CorrelationContext.traceId(req),
user == null ? headerLong(req, "X-Tenant-Id") : user.tenantId(),
user == null ? headerLong(req, "X-Operator-Tenant-Id") : user.operatorTenantId(),
user == null ? null : user.userId(), user == null ? null : user.username(),
user == null ? header(req, "X-App-Code") : user.appCode(), user == null ? null : user.clientType(),
req == null ? null : req.getMethod(), req == null ? null : req.getRequestURI(),
req == null ? null : clientIp(req), duration, failure == null,
failure == null ? null : failure.getClass().getSimpleName(),
failure == null ? null : failure.getMessage(), before,
a.captureAfter() ? sanitizer.sanitize(result) : null, Instant.now(),
serviceName));
}
private HttpServletRequest request() {
var attrs = RequestContextHolder.getRequestAttributes();
return attrs instanceof ServletRequestAttributes s ? s.getRequest() : null;
}
private AuthenticatedUser currentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return auth != null && auth.getPrincipal() instanceof AuthenticatedUser u ? u : null;
}
private String evaluateResourceId(ProceedingJoinPoint p, String expression) {
if (expression == null || expression.isBlank()) return null;
MethodSignature s = (MethodSignature) p.getSignature();
var ctx = new MethodBasedEvaluationContext(p.getTarget(), s.getMethod(), p.getArgs(), new DefaultParameterNameDiscoverer());
Object v = parser.parseExpression(expression).getValue(ctx); return v == null ? null : v.toString();
}
private static String header(HttpServletRequest r, String n) { return r == null ? null : r.getHeader(n); }
private static Long headerLong(HttpServletRequest r, String n) { try { return header(r, n) == null ? null : Long.valueOf(header(r, n)); } catch (NumberFormatException e) { return null; } }
private static String clientIp(HttpServletRequest r) { String x = r.getHeader("X-Forwarded-For"); return x == null ? r.getRemoteAddr() : x.split(",")[0].trim(); }
}