package jnpf.audit.sdk; import jnpf.audit.AuditEventApi; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; /** * audit-sdk 自动装配(Task 7 Step 5)。 * *

注册机制:{@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports} * ——平台默认构建走 JDK21/boot3 profile(Spring Boot 3.5.7),Spring Boot 3 已移除 spring.factories * 的自动装配入口,必须用 {@code .imports};且该机制自 Spring Boot 2.7 起即受支持,对 boot2 profile 亦向后兼容。 * *

{@code audit.enabled}(默认 true,缺省视为启用)为总开关;置 false 时整套 Bean 不装配。 * 各 Bean 均加 {@link ConditionalOnMissingBean} 供宿主按需覆盖。 * *

层次现状(2026-08-11 起):SDK 侧只剩层 2({@code @AuditLog} 切面)与层 3 * ({@link AuditClient} 显式埋点)。层 1(MyBatis Executor 插件)已整套移除——它自交付起 * 一直默认关,{@code audit_events} 表内 {@code source_layer=1} 零行,属未启用即负债。 * 需要恢复见 {@code docs/handoff-2026-08-11-audit-into-biz-common.md} 记的 git revert 坐标。 */ @AutoConfiguration @ConditionalOnProperty(name = "audit.enabled", matchIfMissing = true) public class AuditSdkAutoConfiguration { @Bean @ConditionalOnMissingBean public AuditContextCollector auditContextCollector(Environment environment) { return new AuditContextCollector(environment.getProperty("spring.application.name")); } /** * spool 双层锁(Codex 批次二复验 #1/#2 终局修复):writer 与 replayer 共享同一实例, * 把彼此的 spool 文件操作串行化(进程内 monitor + 跨进程 {@code spool.lock} FileLock)。 */ @Bean @ConditionalOnMissingBean public AuditSpoolLock auditSpoolLock( @Value("${audit.spool-dir:./audit-spool}") String spoolDir) { return new AuditSpoolLock(spoolDir); } @Bean @ConditionalOnMissingBean public AuditSpoolWriter auditSpoolWriter( @Value("${audit.spool-dir:./audit-spool}") String spoolDir, AuditSpoolLock spoolLock) { return new AuditSpoolWriter(spoolDir, spoolLock); } @Bean @ConditionalOnMissingBean public AuditEventPublisher auditEventPublisher(ObjectProvider feignApi, AuditSpoolWriter spoolWriter) { return new AuditEventPublisher(feignApi, spoolWriter); } @Bean @ConditionalOnMissingBean public AuditSpoolReplayer auditSpoolReplayer(AuditEventPublisher publisher, @Value("${audit.spool-dir:./audit-spool}") String spoolDir, @Value("${audit.spool-replay-enabled:true}") boolean replayEnabled, // 调度周期(毫秒):默认 300000(5 分钟,spec);测试环境可调短加速降级链重放验证。 // 仅改轮询频率,不改孤儿年龄安全阈值(见 AuditSpoolReplayer.ORPHAN_REPLAYING_AGE_MS)。 @Value("${audit.spool-replay-interval-ms:300000}") long replayIntervalMs, AuditSpoolLock spoolLock) { return new AuditSpoolReplayer(publisher, spoolDir, replayEnabled, spoolLock, replayIntervalMs); } @Bean @ConditionalOnMissingBean public AuditRequestScopeFilter auditRequestScopeFilter( @Value("${audit.request-scope.frontend-only:false}") boolean frontendOnly, @Value("${audit.request-scope.allowed-app-codes:}") String allowedAppCodes) { return new AuditRequestScopeFilter(frontendOnly, allowedAppCodes); } @Bean @ConditionalOnMissingBean public AuditClient auditClient(AuditContextCollector collector, AuditEventPublisher publisher, AuditRequestScopeFilter requestScopeFilter, @Value("${audit.enabled:true}") boolean enabled) { return new AuditClient(collector, publisher, enabled, requestScopeFilter); } /** * 层 2 切面(默认开启,可用 audit.layer2-enabled=false 单独关)。 * @ConditionalOnClass 防止宿主没引 AOP starter 时装配失败。 */ @Bean @ConditionalOnMissingBean @ConditionalOnClass(org.aspectj.lang.ProceedingJoinPoint.class) @ConditionalOnProperty(name = "audit.layer2-enabled", matchIfMissing = true) public jnpf.audit.sdk.aspect.AuditLogAspect auditLogAspect(AuditClient auditClient, AuditEventPublisher publisher, ApplicationContext applicationContext) { return new jnpf.audit.sdk.aspect.AuditLogAspect(auditClient, publisher, applicationContext); } }