刘光辉
昨天 bb638871a7fb692d80f1b7a758f991dc0879002c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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)。
 *
 * <p>注册机制:{@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 亦向后兼容。
 *
 * <p>{@code audit.enabled}(默认 true,缺省视为启用)为总开关;置 false 时整套 Bean 不装配。
 * 各 Bean 均加 {@link ConditionalOnMissingBean} 供宿主按需覆盖。
 *
 * <p><b>层次现状(2026-08-11 起)</b>: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 <b>共享同一实例</b>,
     * 把彼此的 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<AuditEventApi> 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);
    }
}