package jnpf.bizcommon.audit.service.title; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** 一次操作的记录标题:主表一行 + 子表每条记录一行。 */ public final class AuditRecordTitle { /** {@link #subScope()} 的取值:输入侧没看全——还有子表行(甚至整张子表)根本没读。 */ public static final String SCOPE_SCAN_TRUNCATED = "scanTruncated"; /** {@link #subScope()} 的取值:扫描完整,但产出的标题条数撞了上限被截。 */ public static final String SCOPE_ROWS_TRUNCATED = "rowsTruncated"; public static final AuditRecordTitle EMPTY = new AuditRecordTitle(null, Collections.emptyList(), null, false, null); private final String mainTitle; private final List subTitles; private final String subTable; private final boolean subDataUnavailable; private final String subScope; public AuditRecordTitle(String mainTitle, List subTitles, String subTable, boolean subDataUnavailable, String subScope) { this.mainTitle = mainTitle == null || mainTitle.trim().isEmpty() ? null : mainTitle; this.subTitles = subTitles == null || subTitles.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList(subTitles)); this.subTable = subTable; this.subDataUnavailable = subDataUnavailable; this.subScope = subScope; } /** 主表标题行;一个字段都没勾、或勾了但全为空时返回 null(调用方回落 bizCode)。 */ public String mainTitle() { return mainTitle; } /** 子表标题行,一条子记录一行。**未必是全量**,看 {@link #subScope()}。 */ public List subTitles() { return subTitles; } /** * 子表标题的完整性。{@code null} = 完整;否则是 {@link #SCOPE_SCAN_TRUNCATED} * 或 {@link #SCOPE_ROWS_TRUNCATED}。 * *

为什么必须有这个(2026-08-05 Codex 复审 Low):两道上限一直是**悄悄**截的, * 读的人看到 200 条会以为那就是全部。资源上界下推之后又多了一种截法——额度耗尽时 * 后面整张子表都不碰,连「事件里没带这批数据」({@link #subDataUnavailable()})都测不出来, * 于是「判不了」被伪装成了「没发生」(L075,本批反复在防的同一个形状)。 * *

为什么不是「把没看的补看完」:那要为每张剩余子表整份反序列化一遍, * 正是资源上界刚拆掉的那笔开销。诚实地说「我没看全」比花大价钱看全更划算—— * 真要全量,读的人可以去查那条记录本身。 * *

{@code scanTruncated} 优先于 {@code rowsTruncated}:输入侧就没看全时, * 产出侧截没截已经不是最重要的信息了。 */ public String subScope() { return subScope; } public String subTable() { return subTable; } /** 表单配了子表标题字段,但事件里取不到子表数据——如实留痕,不能当成"没有子表"。 */ public boolean subDataUnavailable() { return subDataUnavailable; } public boolean isEmpty() { return mainTitle == null && subTitles.isEmpty() && !subDataUnavailable; } }