From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master

---
 jnpf-biz-common/jnpf-audit-sdk/src/test/java/jnpf/audit/sdk/AuditTxHolderConcurrencyChecks.java |  447 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 447 insertions(+), 0 deletions(-)

diff --git a/jnpf-biz-common/jnpf-audit-sdk/src/test/java/jnpf/audit/sdk/AuditTxHolderConcurrencyChecks.java b/jnpf-biz-common/jnpf-audit-sdk/src/test/java/jnpf/audit/sdk/AuditTxHolderConcurrencyChecks.java
new file mode 100644
index 0000000..48e0432
--- /dev/null
+++ b/jnpf-biz-common/jnpf-audit-sdk/src/test/java/jnpf/audit/sdk/AuditTxHolderConcurrencyChecks.java
@@ -0,0 +1,447 @@
+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 鐨勫苟鍙戞椂搴忕煩闃垫柇瑷�锛圡2 operationId 鍒嗗弶淇锛宻pec 搂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 鍦ㄥ墠銆乺egister 鍦ㄥ悗锛屼笉鏄�"鍏堝叏閮ㄦ敞鍐屽啀鍏ㄩ儴鎭㈠"
+ *   瀹屾垚  triggerAfterCompletion:
+ *          getSynchronizations()锛堝亸绉� 7锛夆啋 clearSynchronization()锛堝亸绉� 11锛夆啋 invokeAfterCompletion()锛堝亸绉� 31锛�
+ *          鈥斺�攁fterCompletion 鍥炶皟鍦ㄥ悓姝�**琚竻闄や箣鍚�**鎵ц锛屾晠鏈被鐢�
+ *            TransactionSynchronizationUtils.invokeAfterCompletion(list, status) 鑰岄潪
+ *            triggerAfterCompletion(int)锛堝悗鑰呰姹傚悓姝ヤ粛娲昏穬锛屼笌鐪熷疄鐩稿弽锛�
+ * </pre>
+ *
+ * <p>鏈被鏄� A 杞繚鐪熷害缂哄彛鐨勫敮涓�涔﹂潰渚濇嵁锛歍ransactionSynchronizationUtils 娌℃湁 suspend/resume
+ * 鐨勫叕寮�瑙﹀彂鍣紝鎸傝捣/鎭㈠椤哄簭鍏ㄩ潬鏈被鑷缂栨帓銆傜湡瀹為摼璺氦鍙夐獙璇佽 tasks/audit-opid-e2e.sh銆�
+ *
+ * <p>杩愯锛歜ash 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 涓ょ骇闄嶇骇閾捐矾锛坰pec 搂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 蹇呴』褰掗浂銆乭older 璧勬簮蹇呴』宸茶В缁戯紙鍦烘櫙 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 + " 鏈�佸共鍑�锛圡DC 涓� 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锛宐uffer 鐢� submit 鍒涘缓 */
+    private static void s4() {
+        System.out.println("[S4] 褰㈡�佺敳锛氫綔鐢ㄥ煙鍦ㄤ簨鍔″ + REQUIRES_NEW锛坆uffer 鐢� 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锛宐uffer 鐢� beginOperation 鍒涘缓銆�
+     * 杩欐槸 v1 璁捐婕忔帀鐨勫舰鎬侊細鍙敼 createBuffer 涓�琛屾椂**浠嶇劧绾�**锛屽繀椤婚潬 scopeOwned 鎵嶈浆缁裤��
+     */
+    private static void s6() {
+        System.out.println("[S6] 褰㈡�佷笝锛氫簨鍔″凡婵�娲昏繘鍏� + REQUIRES_NEW锛坆uffer 鐢� beginOperation 寤猴級");
+        reset();
+        beginTx();
+        // 闃插亣缁匡紙spec 搂6-A锛夛細蹇呴』鏄� MDC 涓虹┖鏃剁敱 beginOperation 寤� buffer锛�
+        // 鍚﹀垯浼氶��鍖栨垚鍦烘櫙 4 鐨� previousMdc 璺緞鑰屽亣缁�
+        check("S6 鍓嶇疆锛歜eginOperation 涔嬪墠 MDC 涓虹┖", MDC.get(KEY) == null, "MDC=" + MDC.get(KEY));
+        check("S6 鍓嶇疆锛歜eginOperation 涔嬪墠鏃� buffer", AuditTxHolder.currentOperationId() == null, "宸叉湁 buffer");
+        AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
+        String scopeId = scope.operationId();
+        check("S6 鍓嶇疆锛歜uffer 纭敱 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锛氬舰鎬佷竵鈥斺�攕ubmit 鍏堝缓 buffer銆侀殢鍚庢墠杩涗綔鐢ㄥ煙 + REQUIRES_NEW锛堥獙"澶嶇敤璺緞涔熺疆浣�"锛� */
+    private static void s7() {
+        System.out.println("[S7] 褰㈡�佷竵锛歴ubmit 鍏堝缓 buffer銆佸悗杩涗綔鐢ㄥ煙 + REQUIRES_NEW");
+        reset();
+        beginTx();
+        emitInner();
+        String bufferId = AuditTxHolder.currentOperationId();
+        check("S7 鍓嶇疆锛歜uffer 鐢� 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 骞跺彂鏃跺簭鐭╅樀锛坰pec 搂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("鍏ㄧ豢銆�");
+    }
+}

--
Gitblit v1.8.0