package jnpf.audit.sdk;
|
|
import jnpf.audit.AuditApiConsts;
|
import jnpf.audit.AuditOperationIds;
|
import jnpf.audit.model.AuditEventDTO;
|
import org.slf4j.MDC;
|
import org.springframework.transaction.support.TransactionSynchronization;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
import org.springframework.transaction.support.TransactionSynchronizationUtils;
|
|
import java.util.ArrayList;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Set;
|
|
/**
|
* AuditTxHolder 的并发时序矩阵断言(M2 operationId 分叉修复,spec §6-A)。
|
*
|
* <p><b>本类模拟事务生命周期的编排顺序,逐条对照 spring-tx 6.2.12 的
|
* {@code AbstractPlatformTransactionManager} 字节码得出,改动前请先复核:</b>
|
* <pre>
|
* 挂起 doSuspendSynchronization:
|
* getSynchronizations()(偏移 0)→ 逐个 suspend()(偏移 31)→ clearSynchronization()(偏移 39)
|
* 恢复 doResumeSynchronization:
|
* initSynchronization()(偏移 0)→ 循环内**逐元素交替** resume()(偏移 30)→ registerSynchronization()(偏移 36)
|
* ——注意是 resume 在前、register 在后,不是"先全部注册再全部恢复"
|
* 完成 triggerAfterCompletion:
|
* getSynchronizations()(偏移 7)→ clearSynchronization()(偏移 11)→ invokeAfterCompletion()(偏移 31)
|
* ——afterCompletion 回调在同步**被清除之后**执行,故本类用
|
* TransactionSynchronizationUtils.invokeAfterCompletion(list, status) 而非
|
* triggerAfterCompletion(int)(后者要求同步仍活跃,与真实相反)
|
* </pre>
|
*
|
* <p>本类是 A 轮保真度缺口的唯一书面依据:TransactionSynchronizationUtils 没有 suspend/resume
|
* 的公开触发器,挂起/恢复顺序全靠本类自行编排。真实链路交叉验证见 tasks/audit-opid-e2e.sh。
|
*
|
* <p>运行:bash tasks/audit-opid-checks.sh
|
*/
|
public class AuditTxHolderConcurrencyChecks {
|
|
private static final String KEY = AuditApiConsts.MDC_OPERATION_ID;
|
private static final List<AuditEventDTO> SENT = new ArrayList<AuditEventDTO>();
|
private static int failures = 0;
|
private static int checksRun = 0;
|
private static int dirtyTail = 0;
|
|
/** 假发送器:只记事件,不触碰 Feign / spool 两级降级链路(spec §6-A 已核实可行) */
|
private static final AuditEventPublisher PUB = new AuditEventPublisher(null, null) {
|
@Override
|
public void publishAsync(List<AuditEventDTO> events) {
|
SENT.addAll(events);
|
}
|
};
|
|
// ==================== 事务生命周期模拟(顺序依据见类注释)====================
|
|
private static void beginTx() {
|
TransactionSynchronizationManager.initSynchronization();
|
}
|
|
private static void commitTx() {
|
TransactionSynchronizationUtils.triggerBeforeCommit(false);
|
TransactionSynchronizationUtils.triggerBeforeCompletion();
|
TransactionSynchronizationUtils.triggerAfterCommit();
|
completeTx(TransactionSynchronization.STATUS_COMMITTED);
|
}
|
|
private static void rollbackTx() {
|
TransactionSynchronizationUtils.triggerBeforeCompletion();
|
completeTx(TransactionSynchronization.STATUS_ROLLED_BACK);
|
}
|
|
private static void completeTx(int status) {
|
List<TransactionSynchronization> syncs = TransactionSynchronizationManager.getSynchronizations();
|
TransactionSynchronizationManager.clearSynchronization();
|
TransactionSynchronizationUtils.invokeAfterCompletion(syncs, status);
|
}
|
|
private static List<TransactionSynchronization> suspendTx() {
|
List<TransactionSynchronization> syncs = TransactionSynchronizationManager.getSynchronizations();
|
for (TransactionSynchronization s : syncs) {
|
s.suspend();
|
}
|
TransactionSynchronizationManager.clearSynchronization();
|
return syncs;
|
}
|
|
private static void resumeTx(List<TransactionSynchronization> syncs) {
|
TransactionSynchronizationManager.initSynchronization();
|
for (TransactionSynchronization s : syncs) {
|
s.resume();
|
TransactionSynchronizationManager.registerSynchronization(s);
|
}
|
}
|
|
// ==================== 事件构造 ====================
|
|
/** 层 1 / 层 3 事件:不带 operationId,由 holder 决定(对应 Executor 插件与显式 record) */
|
private static void emitInner() {
|
AuditTxHolder.submit(dto(null), PUB);
|
}
|
|
/** 层 2 事件:切面显式带上 scope 的 id(照抄 AuditLogAspect.java:68,90) */
|
private static void emitOuter(String operationId) {
|
AuditTxHolder.submit(dto(operationId), PUB);
|
}
|
|
private static AuditEventDTO dto(String operationId) {
|
return AuditEventDTO.builder()
|
.operationId(operationId)
|
.eventType("BIZ_ACTION")
|
.actionCode("UPDATE")
|
.sourceLayer(1)
|
.build();
|
}
|
|
private static void emitSignUsed() {
|
AuditTxHolder.submit(AuditEventDTO.builder()
|
.eventType("E_SIGNATURE")
|
.actionCode("SIGN_USED")
|
.bizType("JIANCE_TASK")
|
.bizCode("LS-ROLLBACK")
|
.targetTable("lims_sign")
|
.targetId("sign-rollback")
|
.sourceLayer(3)
|
.build(), PUB);
|
}
|
|
// ==================== 断言工具 ====================
|
|
private static void reset() {
|
SENT.clear();
|
MDC.remove(KEY);
|
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
TransactionSynchronizationManager.clearSynchronization();
|
}
|
}
|
|
private static void check(String name, boolean ok, String detail) {
|
checksRun++;
|
if (ok) {
|
System.out.println(" [PASS] " + name);
|
} else {
|
failures++;
|
System.out.println(" [FAIL] " + name + " —— " + detail);
|
}
|
}
|
|
/** 每个场景收尾都要跑:MDC 必须归零、holder 资源必须已解绑(场景 9 的实现) */
|
private static void assertClean(String scenario) {
|
String mdc = MDC.get(KEY);
|
String res = AuditTxHolder.currentOperationId();
|
boolean ok = (mdc == null) && (res == null);
|
if (!ok) {
|
dirtyTail++;
|
}
|
check(scenario + " 末态干净(MDC 与 holder 资源均已清)", ok,
|
"MDC=" + mdc + ", currentOperationId=" + res);
|
}
|
|
private static Set<String> idsOf(List<AuditEventDTO> events) {
|
Set<String> ids = new HashSet<String>();
|
for (AuditEventDTO e : events) {
|
ids.add(e.getOperationId());
|
}
|
return ids;
|
}
|
|
private static String dump() {
|
StringBuilder sb = new StringBuilder("发出 ").append(SENT.size()).append(" 条: ");
|
for (AuditEventDTO e : SENT) {
|
sb.append(e.getOperationId()).append(' ');
|
}
|
return sb.toString();
|
}
|
|
// ==================== 十一项场景 ====================
|
|
/** 场景 1:无事务进入、无内部事务提交(现状不回归) */
|
private static void s1() {
|
System.out.println("[S1] 无事务进入、无内部事务提交");
|
reset();
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
|
String scopeId = scope.operationId();
|
emitOuter(scopeId);
|
scope.close();
|
check("S1 层2 事件用 scope 的 id", SENT.size() == 1 && scopeId.equals(SENT.get(0).getOperationId()), dump());
|
assertClean("S1");
|
}
|
|
/** 场景 2:事务已激活进入 + 事务内提交(今日 lims 四处的形态,现状不回归) */
|
private static void s2() {
|
System.out.println("[S2] 事务已激活进入 + 事务内提交(今日 lims 形态)");
|
reset();
|
beginTx();
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
|
String scopeId = scope.operationId();
|
emitInner();
|
emitOuter(scopeId);
|
scope.close();
|
commitTx();
|
check("S2 层1 与层2 同 id", SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
|
check("S2 该 id 即 scope 的 id", idsOf(SENT).contains(scopeId), "scopeId=" + scopeId + ", " + dump());
|
assertClean("S2");
|
}
|
|
/** 场景 3:无事务进入 + 内部事务内提交(spec §1.2 分叉;全未改时必红) */
|
private static void s3() {
|
System.out.println("[S3] 无事务进入 + 内部事务内提交(§1.2 分叉)");
|
reset();
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
|
String scopeId = scope.operationId();
|
beginTx();
|
emitInner();
|
commitTx();
|
emitOuter(scopeId);
|
scope.close();
|
check("S3 层1 与层2 同 id", SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
|
assertClean("S3");
|
}
|
|
/** 场景 4:形态甲——作用域在事务外 + REQUIRES_NEW,buffer 由 submit 创建 */
|
private static void s4() {
|
System.out.println("[S4] 形态甲:作用域在事务外 + REQUIRES_NEW(buffer 由 submit 建)");
|
reset();
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
|
String scopeId = scope.operationId();
|
beginTx();
|
// 防假绿:本场景 buffer 必须由 submit 创建,且创建时 MDC 已有值
|
check("S4 前置:进入外层事务时 MDC 已被作用域写入", scopeId.equals(MDC.get(KEY)), "MDC=" + MDC.get(KEY));
|
check("S4 前置:外层事务此刻尚无 buffer", AuditTxHolder.currentOperationId() == null, "已有 buffer");
|
emitInner();
|
List<TransactionSynchronization> susp = suspendTx();
|
beginTx();
|
emitInner();
|
commitTx();
|
resumeTx(susp);
|
emitInner();
|
commitTx();
|
emitOuter(scopeId);
|
scope.close();
|
check("S4 内外事务与层2 全部同 id", SENT.size() == 4 && idsOf(SENT).size() == 1, dump());
|
assertClean("S4");
|
}
|
|
/** 场景 5:形态乙——无作用域 + REQUIRES_NEW,内外必须各自独立(守 M1 终验#6) */
|
private static void s5() {
|
System.out.println("[S5] 形态乙:无作用域 + REQUIRES_NEW(内外必须不同 id)");
|
reset();
|
beginTx();
|
check("S5 前置:无作用域,MDC 为空", MDC.get(KEY) == null, "MDC=" + MDC.get(KEY));
|
emitInner();
|
List<TransactionSynchronization> susp = suspendTx();
|
beginTx();
|
check("S5 前置:挂起后 MDC 必须被清空", MDC.get(KEY) == null, "MDC=" + MDC.get(KEY));
|
emitInner();
|
commitTx();
|
resumeTx(susp);
|
commitTx();
|
check("S5 内外事务 id 不同(终验#6 原行为)", SENT.size() == 2 && idsOf(SENT).size() == 2, dump());
|
assertClean("S5");
|
}
|
|
/**
|
* 场景 6:形态丙——事务已激活进入 + REQUIRES_NEW,buffer 由 beginOperation 创建。
|
* 这是 v1 设计漏掉的形态:只改 createBuffer 一行时**仍然红**,必须靠 scopeOwned 才转绿。
|
*/
|
private static void s6() {
|
System.out.println("[S6] 形态丙:事务已激活进入 + REQUIRES_NEW(buffer 由 beginOperation 建)");
|
reset();
|
beginTx();
|
// 防假绿(spec §6-A):必须是 MDC 为空时由 beginOperation 建 buffer,
|
// 否则会退化成场景 4 的 previousMdc 路径而假绿
|
check("S6 前置:beginOperation 之前 MDC 为空", MDC.get(KEY) == null, "MDC=" + MDC.get(KEY));
|
check("S6 前置:beginOperation 之前无 buffer", AuditTxHolder.currentOperationId() == null, "已有 buffer");
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
|
String scopeId = scope.operationId();
|
check("S6 前置:buffer 确由 beginOperation 创建", scopeId != null && scopeId.equals(AuditTxHolder.currentOperationId()),
|
"scopeId=" + scopeId + ", current=" + AuditTxHolder.currentOperationId());
|
List<TransactionSynchronization> susp = suspendTx();
|
beginTx();
|
emitInner();
|
commitTx();
|
resumeTx(susp);
|
emitOuter(scopeId);
|
scope.close();
|
commitTx();
|
check("S6 内层 REQUIRES_NEW 与层2 同 id", SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
|
check("S6 该 id 即 scope 的 id", idsOf(SENT).contains(scopeId), "scopeId=" + scopeId + ", " + dump());
|
assertClean("S6");
|
}
|
|
/** 场景 7:形态丁——submit 先建 buffer、随后才进作用域 + REQUIRES_NEW(验"复用路径也置位") */
|
private static void s7() {
|
System.out.println("[S7] 形态丁:submit 先建 buffer、后进作用域 + REQUIRES_NEW");
|
reset();
|
beginTx();
|
emitInner();
|
String bufferId = AuditTxHolder.currentOperationId();
|
check("S7 前置:buffer 由 submit 创建", bufferId != null, "无 buffer");
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
|
String scopeId = scope.operationId();
|
check("S7 前置:作用域复用既有 buffer", bufferId.equals(scopeId), "bufferId=" + bufferId + ", scopeId=" + scopeId);
|
List<TransactionSynchronization> susp = suspendTx();
|
beginTx();
|
emitInner();
|
commitTx();
|
resumeTx(susp);
|
emitOuter(scopeId);
|
scope.close();
|
commitTx();
|
check("S7 全链路同 id", SENT.size() == 3 && idsOf(SENT).size() == 1, dump());
|
assertClean("S7");
|
}
|
|
/** 场景 8:形态戊——标注方法自身 REQUIRES_NEW、调用方有事务 */
|
private static void s8() {
|
System.out.println("[S8] 形态戊:标注方法自身 REQUIRES_NEW、调用方有事务");
|
reset();
|
beginTx();
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
|
String scopeId = scope.operationId();
|
List<TransactionSynchronization> susp = suspendTx();
|
beginTx();
|
emitInner();
|
commitTx();
|
resumeTx(susp);
|
emitOuter(scopeId);
|
scope.close();
|
commitTx();
|
check("S8 自身事务与调用方事务同 id", SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
|
check("S8 该 id 即 scope 的 id", idsOf(SENT).contains(scopeId), "scopeId=" + scopeId + ", " + dump());
|
assertClean("S8");
|
}
|
|
/** 场景 10:回滚不发(单事务,守 M1 原行为) */
|
private static void s10() {
|
System.out.println("[S10] 回滚不发(单事务)");
|
reset();
|
beginTx();
|
emitInner();
|
rollbackTx();
|
check("S10 回滚后无事件发出", SENT.isEmpty(), dump());
|
assertClean("S10");
|
}
|
|
/** 场景 11:形态丙下 内层回滚 + 外层提交;并含双回滚变体 */
|
private static void s11() {
|
System.out.println("[S11] 形态丙下 内层回滚 + 外层提交");
|
reset();
|
beginTx();
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
|
String scopeId = scope.operationId();
|
List<TransactionSynchronization> susp = suspendTx();
|
beginTx();
|
emitInner();
|
rollbackTx();
|
resumeTx(susp);
|
emitOuter(scopeId);
|
scope.close();
|
commitTx();
|
check("S11 内层回滚不发、外层照发", SENT.size() == 1 && scopeId.equals(SENT.get(0).getOperationId()), dump());
|
assertClean("S11");
|
|
System.out.println("[S11b] 双回滚变体");
|
reset();
|
beginTx();
|
AuditTxHolder.OperationScope scope2 = AuditTxHolder.beginOperation(PUB);
|
List<TransactionSynchronization> susp2 = suspendTx();
|
beginTx();
|
emitInner();
|
rollbackTx();
|
resumeTx(susp2);
|
emitOuter(scope2.operationId());
|
scope2.close();
|
rollbackTx();
|
check("S11b 内外双回滚:一条都不发", SENT.isEmpty(), dump());
|
assertClean("S11b");
|
}
|
|
/** SIGN_USED 与业务事件同事务:共享 operationId,整体回滚时均不发。 */
|
private static void s12() {
|
System.out.println("[S12] SIGN_USED 与业务事件的提交/回滚语义");
|
reset();
|
beginTx();
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
|
emitInner();
|
emitSignUsed();
|
scope.close();
|
commitTx();
|
check("S12 提交时业务事件与 SIGN_USED 共享 operationId",
|
SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
|
assertClean("S12-commit");
|
|
reset();
|
beginTx();
|
AuditTxHolder.OperationScope rollbackScope = AuditTxHolder.beginOperation(PUB);
|
emitInner();
|
emitSignUsed();
|
rollbackScope.close();
|
rollbackTx();
|
check("S12 回滚时 SIGN_USED 与业务事件均不发", SENT.isEmpty(), dump());
|
assertClean("S12-rollback");
|
}
|
|
/** 同一 biz_sign 的业务与签名事件共享一个稳定 operationId。 */
|
private static void s13() {
|
System.out.println("[S13] biz_sign 关联作用域统一 operationId");
|
reset();
|
String signId = "839875756384423365";
|
beginTx();
|
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB, AuditOperationIds.correlation(signId));
|
emitInner();
|
emitSignUsed();
|
scope.close();
|
commitTx();
|
check("S13 同一前端操作的业务与签名事件共享 operationId",
|
SENT.size() == 2 && idsOf(SENT).size() == 1
|
&& idsOf(SENT).contains(AuditOperationIds.correlation(signId)), dump());
|
assertClean("S13");
|
}
|
|
public static void main(String[] args) {
|
System.out.println("=== AuditTxHolder 并发时序矩阵(spec §6-A + SIGN_USED 回滚)===");
|
s1();
|
s2();
|
s3();
|
s4();
|
s5();
|
s6();
|
s7();
|
s8();
|
s10();
|
s11();
|
s12();
|
s13();
|
System.out.println();
|
System.out.println("[S9] 线程复用防线汇总:末态不干净的场景数 = " + dirtyTail);
|
System.out.println();
|
System.out.println("=== 断言 " + checksRun + " 条,失败 " + failures + " 条 ===");
|
if (failures > 0) {
|
System.out.println("红。改动前这是预期结果——对照 plan 的两层红基线表核对红的是哪几项。");
|
System.exit(1);
|
}
|
System.out.println("全绿。");
|
}
|
}
|