docs: consolidate flight state design

This commit is contained in:
windyboy
2026-09-09 11:43:34 +08:00
parent 4449ee5dcc
commit 2b112f527c
21 changed files with 178 additions and 1173 deletions
+2 -2
View File
@@ -161,8 +161,8 @@ MICRONAUT_ENVIRONMENTS=dev ./gradlew run # dev stub 冒烟:内存 stub,无
## 文档 ## 文档
从 [设计文档入口](docs/README.md) 开始阅读;最新完成度 从 [设计文档入口](docs/README.md) 开始阅读;航班状态设计
[ACM2-29 核查与简化计划](docs/acm2-29-audit-and-simplification.md)。 [docs/flight-state.md](docs/flight-state.md)。
- [docs/architecture.md](docs/architecture.md):架构速览——**中间件定位**、总体拓扑(JDBC 轮询主路径)、 - [docs/architecture.md](docs/architecture.md):架构速览——**中间件定位**、总体拓扑(JDBC 轮询主路径)、
上下游边界、模块职责、关键决策、数据边界、部署与安全姿态、可观测性、就绪度。 上下游边界、模块职责、关键决策、数据边界、部署与安全姿态、可观测性、就绪度。
+2 -6
View File
@@ -6,13 +6,9 @@
|---|---| |---|---|
| [architecture.md](architecture.md) | 系统边界、模块职责、FIFO、单写者、数据库归属。 | | [architecture.md](architecture.md) | 系统边界、模块职责、FIFO、单写者、数据库归属。 |
| [design.md](design.md) | 管道流程、处理/投递状态、恢复与运行配置。 | | [design.md](design.md) | 管道流程、处理/投递状态、恢复与运行配置。 |
| [flight-state-design-v2.md](flight-state-design-v2.md) | 当前航班表、命令与事务实现,及明确标记的缺口。路径保留以兼容历史引用。 | | [flight-state.md](flight-state.md) | 航班当前态、快照账本、表关系、事务流程和开放问题。 |
| [flight-state-semantics.md](flight-state-semantics.md) | 字段缺失、清除、集合替换及 wire 语义;区分实现和验收目标。 |
| [user-stories.md](user-stories.md) | US/OPS 验收目标,不能用“当前基础”替代完成证据。 | | [user-stories.md](user-stories.md) | US/OPS 验收目标,不能用“当前基础”替代完成证据。 |
| [decision-flight-state.md](decision-flight-state.md) | 有效决策与理由,历史全文转入 legacy。 | | [decision-flight-state.md](decision-flight-state.md) | 有效决策与理由,历史全文转入 legacy。 |
| [ACM2-29 核查与简化计划](acm2-29-audit-and-simplification.md) | 本次完成度、问题证据、简化边界和后续验收计划。 |
| [旧评审](review-flight-state-2026-09-08.md) | cd5de56 基线的历史评审,不能当作当前缺陷或完成清单。 |
| [SIS 规范](legacy/SIS_AODB_RMS-V0.1.md) / [XSD](legacy/unisysaodbsis.xsd) | 外部协议事实;即使在 legacy 目录仍是兼容依据。 | | [SIS 规范](legacy/SIS_AODB_RMS-V0.1.md) / [XSD](legacy/unisysaodbsis.xsd) | 外部协议事实;即使在 legacy 目录仍是兼容依据。 |
维护规则:架构写约束,设计写当前机制,故事写目标,核查报告写证据,Plane 跟踪工作。 维护规则:架构写约束,设计写机制,故事写目标,Plane 跟踪工作。不要在多份文档复制阶段清单。
不在多份文档复制阶段完成清单;修订状态必须同步受影响的规则,但不改写历史迁移。
-123
View File
@@ -1,123 +0,0 @@
# ACM2-29 完成度核查与简化计划
日期:2026-09-08。核查基线:`7c2d22e`,开始时工作区干净。
范围:Plane ACM2-29 正文及评论、当前生产代码、迁移、测试与全部顶层设计文档。
本文包含本次本地修正,不代表代码已提交、发布或现场验收。
后续工作:[ACM2-30](https://plane.chans.xyz/space/projects/0b5086dd-dd33-4893-86d6-9e716a21b21f/issues/eb647af8-b9a5-4417-aeb6-f9da4e1a3661),已创建在 Backlog,正文已读回核对。与 ACM2-29 的 relates_to 创建返回成功;关系查询接口报参数错误,未能独立读回复核关联。
## 1. 结论
**ACM2-29 没有全部完成。** PostgreSQL 存储重构内核大体落地,但真实快照链路、查询接口、
部分保真与恢复不变量、数据迁移验收及 Oracle 完整适配尚未完成。
正文仍要求“全宽表零子表”,2026-09-08 09:49 评论已撤销该方案;17:44 评论又称 P2–P4 全部完成。
应按修订后的 v2 目标核查,不再按已撤销的正文补做有损槽位,也不将收口评论当作证明。
**存在局部过度工程化,主要是边界重复和文档失真,不是表数量本身。**
一套关系库、显式清除、明细保真、事务、FIFO、outbox 和回填重试都有具体失败场景支撑。
再次推倒已落地的无损存储会增加迁移风险;优先减少重复表示、未使用分支和无效承诺。
## 2. 原计划完成度
| 工作 | 核查结论 | 代码或证据 |
|---|---|---|
| P0:CAS 消息身份、成功后回填隔离 | 部分完成 | SnapshotFlow 锁内 LAST_MESSAGE_ID/CAS 已实现;回填待办写入再次失败仍可冒泡到处理失败边界。 |
| P1:无损表、命令和唯一写入口 | 内核完成,边界未全验收 | V1.3.0、FlightStateEngine、persistNextStates9 张表/10 类集合。ROUT/ERUT、未知属性和空数组仍有缺口。 |
| P1DNLD + 一个 FLOP 纵向链 | GTDT 已有;真实 DNLD 未完成 | JacksonXmlCodec 的 SCHD body 为 nullStageResult.parser 默认 null,仅测试赋值;主泵先要求 Handler,生产只注册 GTDT。 |
| P2-1:双读对拍 | 历史工具工作存在,未证明现场对拍 | 2af71e8 曾有双读比较,7c2d22e 移除旧读路径;当前保留 FlightStoreDiffTool,未见真实输入逐字段等值报告。 |
| P2-2:权威读、展示视图、查询接口 | 仓储和视图完成,接口未完成 | V1.3.1、FlightSchdReadAssemblerInboxController 的 /all/flights 仍是 TODO。 |
| P2-3:回填持久补偿 | 有待办和扫描,但崩溃窗口未闭合 | BACKFILL_TODO 在外部回填失败之后才 record;提交后立即崩溃时没有待办;轮询入队不会重置终态。 |
| P2-4:带数据升级 | 脚本可执行已验证,无损升级未完成 | FlywayMigrationTest 显式确认 V1.2 删除集合数据;没有旧集合回填到明细的实现。 |
| P2-5:锁内读取及语义矩阵 | 快照内核完成;文档与代码仍有偏差 | 增量事件在锁外预览产生;矩阵原称文本 Clear、DELY 历史保留及明细 FK,代码并不支持这些陈述。 |
| P3-APostgreSQL 验收 | 现有套件全绿,不是完整协议矩阵 | 本次基线 100 测试、0 失败、0 跳过,含 16 JDBC + 2 迁移测试;“16 类”样例未覆盖 ERUT 与 ROUT 共存。 |
| P3-BOracle 11g | 未完成,不只是等待现场 | 仅两个 SQL 模板;无 DDL、驱动接入和完整绑定,其他仓储仍含 PG SQL。 |
| P4:旧列退场、索引与回退窗口 | 代码退场完成,运营验收证据不足 | V1.4.0 删除旧列及冗余索引;没有真实查询 EXPLAIN、备份恢复与现场回退窗口关闭证明。 |
| 32 个 Handler | 未完成,后续业务范围 | 当前只有 GtdtHandler 生产实现,其余不应因存储收口而视为已交付。 |
ACM2-29 当前为 In Progress,本次没有修改其状态、正文或发表评论。
## 3. 具体问题与优先级
### A. 本次已修:日期差删破坏保留航班明细
JdbcFlightSchdRepository.deleteDiffByDay 原先先按传入 FLID 无条件删除明细,再按 FDAY 删除主行。
输入包含跨日迁移或 FDAY=NULL 的航班时,主行保留、集合丢失,违反快照域化差删不变量。
本次先按 FDAY 选择实际删除成员,主表与明细使用同一范围;继续由主泵事务行锁保护。
没有增加表或修改已发布迁移。
新增真实 PG 测试同时覆盖目标日期删除、跨日成员保留、ADFT 无归属成员保留;
原实现测试失败,修正后通过。它揭示的是此前测试矩阵遗漏,而非原测试结果造假。
### B. P0:尚未闭合的正确性边界
1. **路线键冲突**V1.3.0 的 flight_route_point 主键只有 (FLID, ORDINAL)
replaceRoutes 对 ROUT/ERUT 各从 ordinal=1 写入,同航班共存时冲突。
通过新迁移改为 (FLID, ROUTE_KIND, ORDINAL),补真实 PG 往返与整事务回滚测试。
2. **补偿落账窗口**Pump/SnapshotFlow 的回填在业务提交后执行,失败才写待办;
record 再失败会逃逸到 MessageProcessor.processOne 的 INFRA 落账,可能降级已提交成功态。
将回填意图与终态同事务写入;成功回填后确认待办。覆盖提交后立即崩溃、待办写失败、重复及 DEAD 终态。
3. **落库和 wire 来源不同**GtdtHandler 锁外计算 preview/SchdPush,主泵锁内重新读态计算 nextState。
改为事务内一份 nextState 同时服务持久化与状态事件;保留独立 msg 通知意图的业务语义。
4. **“无损”不是全字段保证**parseCollection 可接受非对象数组元素;未知属性被存储列映射忽略;
Replace([]) 的 wire 与读回不同;异常对象局部替换可能残留旧列;文本 null 清除与文档曾不一致。
固定合法字段/形状及空值黄金样例,未知输入显式拒绝,避免成功后静默丢字段。
以上 B 项本次记录为后续工作,没有声称已修复。
### C. P1:真实能力和验收
- 接通真实 XML DNLD/RESP 的解析、整包验证与路由,补无匹配/迟到 RESP;不依赖测试全局 parser。
- 实现 /all/flights 兼容接口;按真实 wire 契约验证。
- findAll/findByFlids 当前每航班追加 10 次集合查询;批量加载并明确同一数据库快照,
在实际规模下记录查询数和执行计划,不添加无消费方的索引或缓存。
- V1.1→V1.4 带数据升级必须提供备份/可信输入恢复步骤和逐字段核对;
不能把“确认旧集合已经丢失”的迁移测试作为无损验收。
### D. Oracle 是完整实施项
Oracle11gDialect 没有生产调用方,snapshot MERGE 的插入列包括 4 个追踪/时间字段,
模板尾部却只生成 3 个占位符;现有 PG 绑定也无法复用。
还需完整 DDL、其他仓储 SQL、生成 ID、CLOB、空值、驱动/Flyway/JDK 组合及目标 11.2 集成测试。
现场环境只影响目标库实测,不应把尚未编写的适配代码归为外部阻塞。
## 4. 简化方案
| 保留 | 简化或延后 | 原因 |
|---|---|---|
| 单库事务、行锁、身份、outbox | 不新增双库镜像/分布式协调层 | 当前单写者范围足够,不解决假设的扩容问题。 |
| 主表 + 明细 | 不重回固定槽位,不重新设计整套存储 | 三门、重复资源、多次操作是已有契约数据。 |
| FlightNextState | 逐步消除 Handler 预览与主泵重复计算、字段集 JSON 往返 | 一条状态生成路径更容易证明 DB/wire 等值。 |
| 必要 Clear/Replace | 删除未使用的 snapshotReplace 命令解析参数;Apply 待真实协议调用方再决定 | 两种解析行为实际相同,Apply 又假设了未确认的定位唯一性。 |
| 数据库列映射 | 复用已有单一列清单;其余资源元数据按需要集中,避免再造注册框架 | 减少漂移而不新增架构层。 |
| 回填待办 | 一条持久队列覆盖中断恢复 | 用事务登记意图替代失败后记录和额外重启推断。 |
| 当前 PG 实现和既定 Oracle 目标 | 不继续堆未运行的通用方言模板 | SQL、绑定、DDL 必须作为可执行整体设计。 |
| 回归与对拍 | 保留可发现失败的矩阵;不为删空壳编写镜像测试 | 验收关注故障行为而非类数量。 |
本次已实施的小范围简化:读写复用 FlightSchdReadAssembler 列清单,删除
FlightDetailTables 未使用的集合清单和 hasAnyDetailRows 旧回退探测;移除 commandsFromFields
未使用的 snapshotReplace 参数及全部调用实参;更新误导的双写注释。
大范围模型、API、数据迁移和 Oracle 实施进入新 issue,避免将未验证的业务变更混入文档整理。
## 5. 下一批实施顺序与验收
1. **可靠性收口**:路线键、回填意图、DB/wire 单一状态、严格字段验证。
验收:真实 PG 的正常/失败/回滚/重放/空值矩阵全绿,成功终态不降级。
2. **最小真实链路**DNLD → GTDT → 完整查询 → outbox,并验证 RESP 守卫。
验收:实际 XML 输入,不手动赋值 StageResult.parser;真实存储和查询、事件契约一致。
3. **读模型与升级**:批量一致读取、实际查询计划、带数据恢复和对拍证据。
验收:查询次数按批次有界,集合/源序号/顺序保真,备份恢复有演练记录。
4. **Oracle 完整适配**:按既定部署边界实现并单独验收,不借 PG 绿灯替代。
验收:目标 11.2 上新建/升级、DML、并发锁、事务恢复及字符语义通过。
每一步交付时同步对应设计及完成证据。32 Handler 扩展、阶段 B 和生产运维仍按原业务工作项推进,
本 issue 不承诺一次完成所有产品功能。不以类已存在、迁移成功或合并提交代替功能/发布验收。
## 6. 本次验证与文档整理
- 基线 ./gradlew test100 测试,0 失败,0 跳过。
- 新差删测试在原实现失败;修正后 ./gradlew test101 测试,0 失败,0 跳过,
包括 17 JDBC 与 2 Flyway 测试,Testcontainers 提供真实 PostgreSQL。
- 未连接 Oracle,未验证现场部署、生产负载或历史数据恢复。
- 重写航班设计为当前实现基线;历史决策归档,有效决策缩为摘要;
同步 architecture/design/user-stories/semantics/README,旧评审明确标为历史。
- 新 [文档入口](README.md) 规定各文件唯一职责,后续不再维护多份阶段完成清单。
+1 -2
View File
@@ -15,8 +15,7 @@ msgexchange-v2 是机场 OMMS 的上游报文处理中间件,用于替换旧
本文描述架构约束,不代表所有能力已实现;实现缺口见第 9 节。模块交互、状态机和参数详见 [design.md](design.md),需求见 [user-stories.md](user-stories.md)。历史报文契约仍以 [SIS 接口规范](legacy/SIS_AODB_RMS-V0.1.md) 和 [XSD](legacy/unisysaodbsis.xsd) 为兼容依据,其他 legacy 资料仅作参考。 本文描述架构约束,不代表所有能力已实现;实现缺口见第 9 节。模块交互、状态机和参数详见 [design.md](design.md),需求见 [user-stories.md](user-stories.md)。历史报文契约仍以 [SIS 接口规范](legacy/SIS_AODB_RMS-V0.1.md) 和 [XSD](legacy/unisysaodbsis.xsd) 为兼容依据,其他 legacy 资料仅作参考。
现场供库时目标为 Oracle 11g,否则自建 PostgreSQL;当前只有 PG 实现可运行,Oracle 不是已支持的平台。 现场供库时目标为 Oracle 11g,否则自建 PostgreSQL;当前只有 PG 实现可运行,Oracle 不是已支持的平台。
航班表结构统一见 [航班状态设计](flight-state-design-v2.md),当前验收结论见 航班表结构、快照账本和处理逻辑见 [航班状态设计](flight-state.md)。
[ACM2-29 核查](acm2-29-audit-and-simplification.md)。
## 2. 总体架构 ## 2. 总体架构
+1 -3
View File
@@ -25,6 +25,4 @@
- ACM2-29 v2:明细表及唯一写路径落地,但不能据此认定全部端到端和平台验收完成。 - ACM2-29 v2:明细表及唯一写路径落地,但不能据此认定全部端到端和平台验收完成。
- 历史原文保存在 [决策历史](legacy/decision-flight-state-history.md),不作为当前实施指令。 - 历史原文保存在 [决策历史](legacy/decision-flight-state-history.md),不作为当前实施指令。
当前规则分别由 [航班状态设计](flight-state-design-v2.md)、 当前规则由 [航班状态设计](flight-state.md) 和 [管道设计](design.md) 维护。
[字段语义](flight-state-semantics.md) 和 [管道设计](design.md) 维护。
完成度、发现的问题和下一批实施计划见 [ACM2-29 核查](acm2-29-audit-and-simplification.md)。
+2 -3
View File
@@ -6,9 +6,8 @@
阶段 A 采纳 ACM2-28 选项 C 定案:运营航班权威状态落自有 PostgreSQL(表 `FLIGHT_SCHD``SCHD_GEN`),Redis 彻底退出动态权威与全部写路径。阶段 B 的历史投影和清场暂不启用。 阶段 A 采纳 ACM2-28 选项 C 定案:运营航班权威状态落自有 PostgreSQL(表 `FLIGHT_SCHD``SCHD_GEN`),Redis 彻底退出动态权威与全部写路径。阶段 B 的历史投影和清场暂不启用。
本文的流程是验收目标,当前差异见 §10。航班存储细节统一由 本文的流程是验收目标,当前差异见 §10。航班状态设计统一由
[航班状态设计](flight-state-design-v2.md) 和 [字段语义](flight-state-semantics.md) 维护 [航班状态设计](flight-state.md) 维护
2026-09-08 的代码证据与剩余计划见 [核查报告](acm2-29-audit-and-simplification.md)。
## 2. 数据与领域模型 ## 2. 数据与领域模型
-184
View File
@@ -1,184 +0,0 @@
# 运营航班状态设计
状态:PostgreSQL 实现基线,2026-09-08 核对。未完成项在各节明确标注;文件名沿用 v2,兼容历史引用。
配套文档:完成度证据见 [ACM2-29 核查与简化计划](acm2-29-audit-and-simplification.md),字段级行为见 [语义矩阵](flight-state-semantics.md),决策理由见 [决策摘要](decision-flight-state.md)。
## 1. 系统做什么
系统从共享 MySQL 信箱接收 SIS 报文,把报文合并成本系统的"航班当前状态",再把变化以事件发出。
三条底线:
- **一套权威状态。** 读写都指向本地数据库;共享 MySQL 和 Kafka 是提交之后的外部边界,失败各自重试,不会让已提交的状态回滚或重做。
- **一个活动主泵。** 生产同时只有一个实例在处理;事务行锁只保护本地提交,不是多实例协调机制。
- **一条提交事务。** 状态、outbox、处理终态在同一个本地事务里落库,要么全成功要么全回滚。
处理顺序严格按信箱 FIFO,按业务身份去重,失败按退避重试。
数据库:现场提供库时目标是 Oracle 11g;当前实际接通的只有 PostgreSQL,本文描述 PG 实现。
## 2. 存储:一张主表加九张明细表
### 为什么不用固定槽位
航班资源是变长集合:登机门通常一两个、值机柜台一到三个、转盘一个,报文规范理论上限 99。ACM2-29 曾定过"全标量宽表、零子表"方案(GATE1/GATE2 式固定槽位),当天撤销——槽位会截断数据,而设计要求保留重复资源的全部属性、输入顺序和源序号。结论:标量进主表,集合进明细表。
### 表清单
| 表 | 作用 |
|---|---|
| `FLIGHT_SCHD` | 主表,一行一个 FLID:标量字段、异常前缀列、SRVT/VIPF/MAFL_TEXT、FDAY、STATE_VERSION、LAST_MESSAGE_ID、时间戳。 |
| 8 张明细表 | gate、checkin、belt、stand_plan、chute、delay、bridge_op、chock_op;主键 (FLID, ORDINAL)。 |
| `FLIGHT_ROUTE_POINT` | 第 9 张明细表,ROUT/ERUT 两类路线共用,ROUTE_KIND 区分。 |
| `SCHD_GEN` / `SCHD_GEN_FLID` | 日代版本、最后消息身份、当日成员集合;成员表带外键级联删除。 |
| `PIPELINE_LOCK` | 单写者行锁,每个处理事务先锁 FLIGHT_SCHD_WRITER 行。 |
| `BACKFILL_TODO` | 回填待办,见第 4 节。 |
| `FLIGHT_SCHD_DISPLAY` | PG 展示视图(前两个登机门/柜台、首个转盘/延误、资源数),只是投影,不是状态来源。 |
9 张明细表承载 10 类集合——路线表一张顶两类。
三个要知道的事实:
1. **明细表没有声明外键**,清理由仓储显式执行,不靠数据库级联;SOURCE_SEQ 可空且不唯一,只是记录——逐条定位更新的 Apply 协议因此不铺开(无生产调用方,定位键不可靠)。
2. **差删按日期圈定范围**:先选出目标 FDAY 的实际成员,再删这些成员的明细和主行;跨日迁移和 FDAY 为空的航班不动。(这里曾有 bug:先删明细再圈主行,导致保留航班的集合被误删;已修复并有真实 PG 回归。)
3. **演进纪律**:不为尚无消费者的查询加类型化时间列、摘要、额外载荷表或索引;时间先保留原始字符串,字段与长度以已应用迁移为准。
### 已知待修
路线表主键仍是 (FLID, ORDINAL):同一航班 ROUT 和 ERUT 各自从序号 1 写起,共存即冲突。计划用新迁移改成 (FLID, ROUTE_KIND, ORDINAL),见 ACM2-30。
## 3. 航班状态的两条来路:请求/下载与动态数据
状态只有两个来源,各成一条线:
- **请求/下载线(基准)**:建立某天航班的基准全量。本系统向 AODB 发日计划请求,AODB 回 SCHD 报文(一整天全部航班),快照整体落库;当天没再出现的航班被差删。
- **动态线(运行事件)**:跟踪运行中的持续变化——实际时间、登机门开关、值机柜台、行李转盘、延误、靠撤桥、轮挡等。FLOP 类事件规范有 29 类,当前已接 GTDT 一类;一条报文只改一个航班的一类资源,其余字段不动。
两条线共用同一个入口和同一套事务/失败规则:进事务后都是"锁内读态 → 合并 → 写库 → 事件入 outbox → 终态 → 提交 → 回填"。
```text
信箱队头 → 解码 → 绑定身份(去重)→ 按 Handler 路由
├─ SCHD-DNLD(已接通)────────→ 请求/下载线
├─ SCHD-RESP / ADFT(未接通)─→ 请求/下载线
└─ FLOP 事件(已接 GTDT)────→ 动态线
```
### 公共入口(所有消息都走)
1. **排队**:信箱消息和定时任务共用一个 FIFO 队列,主泵只处理队头;队头失败按退避等待,重试超限或队头滞留超过时限 → DEAD。
2. **解码**:报文非法 → DEAD 不重试;解码器缺陷类错误 → FAILED 退避重试。
3. **绑定身份**:幂等键 = 发送方|类型|子类型|序号,仅首次处理时绑定;已被其他消息占用 → SKIPPED(重复),自身照常回填共享信箱。
4. **路由**:按报文类型找 Handler;没有注册的 Handler → FAILED(可重放,不写终态)。
### 请求/下载线:基准全量怎么来
SIS 循环:本系统发日计划请求 → AODB 回 SCHD。SCHD 三个子类型载荷结构相同:
- **DNLD**:AODB 定时下发的时刻表下载(当前唯一接通的)。
- **RESP**:应答本系统的日计划请求。
- **ADFT**:临时/加班航班,单条航班记录。
请求登记在 REQ_TRACK(同类并发=1,新请求把旧请求强制置 EXPIRED),出站经 COUTMSGS outbox——发送与超时重发尚未接线。
收到下载后的落库步骤:
1. 解析整包并校验,单包上限 10000 个航班(超限 DEAD);真实解析未接通,测试靠注入 staging 数据。
2. 进事务(先锁 PIPELINE_LOCK 行):
- **重放短路**:该消息已提交过(日代 LAST_MESSAGE_ID 相同)→ 直接 SUCCEEDED,不加版本、不发事件。
- **算成员差**:新快照里消失的航班进差删名单。
- **合并写入**:锁内逐航班读当前态 → 合并 → 整集合替换,FDAY 置为快照日期。
- **差删**:删差删名单航班的明细和主行(规则见第 2 节)。
- **CAS 推进日代版本**:版本不符回滚 → FAILED(INFRA) 退避。
- **应答匹配**:有待答的 SCHD 请求则标记完成。
- 每个航班一条 KAFKA_SCHD 事件入 outboxPROC_STATE → SUCCEEDED;提交。
3. 提交后回填共享信箱(失败落 BACKFILL_TODO,见第 4 节);重放短路同样要回填。
现状:只有 DNLD 被路由进这条流程;RESP 和 ADFT 还没有 Handler,进来会记 FAILED(UNSUPPORTED)——"请求 → 应答 → 标记完成"的闭环待接通。参考数据请求(静态主数据 21 类)用同一张 REQ_TRACK,但应答落参考库、由 RequestCoordinator 处理,与航班状态无关。
### 动态线:运行事件怎么改状态(以 GTDT 为例)
1. **Handler 纯函数决策**:输入 = 受影响航班的当前态 + 报文,输出 = 字段变更和待发事件;这一步不算账、不落库。每类资源的语义在 Handler 里固定,如 GTDT 是登机门集合整体替换(GTNO=0 表示清除)。
2. 进事务(先锁 PIPELINE_LOCK 行):
- 每个受影响航班:锁内重读最新态 → 合并字段变更 → 整集合替换写入,保留已有 FDAY。
- 事件(KAFKA_MSG 通知 + KAFKA_SCHD 状态推送)入 outboxPROC_STATE → SUCCEEDED;提交。
3. 提交后回填共享信箱(同上)。
### 状态变更的实现(两条线共用)
所有状态变更都走同一套加工链:**报文 → 命令 → 合并出新状态 → 写库**。
#### 数据的统一形态
内存里一个航班的状态就是一个模型(FlightNextState):一组标量键值 + 10 类集合(每类是一组条目,每条目一组键值)+ 版本/消息追踪字段。主表存标量,明细表存集合,列映射固定(如 GTDT 的 GATE/GOTM/GCTM → flight_gate 对应列);读回时按 ORDINAL 组装集合,DB 权威态 = 主行 + 明细行。
#### 第一步:报文变成命令
`commandsFromFields` 把 Handler 解出的字段集翻成命令,规则:
- **字段没出现 = 不动**。增量报文只带变化的部分,其余不碰。
- **标量出现 = Set**(覆盖)。异常键(FDIV/FRET/FLAB)载荷为空串、null、空对象时 = Clear。
- **集合出现 = Replace**(整组替换为报文给的条目);整组条目序号属性为 0(如 GTNO=0)= Clear(显式清除);清除标记和正常条目混在一组直接拒绝,不猜。
- DELY 没有协议序号属性,不支持逐条 Apply;清除走空数组替换。
#### 第二步:命令合并进当前态(纯函数)
`FlightStateEngine.apply` 从当前态拷贝一份,逐条应用命令:Set 覆盖键值,Clear 删键并记入 clearedKeysReplace 整组换,Clear 删组。然后版本 +1、记 LAST_MESSAGE_ID,产出 FlightNextState。纯函数:不碰库、不发事件,同样输入永远同样输出。
#### 第三步:新状态写进库(唯一写入口 persistNextStates
- **主表标量**:快照线整行替换(FDAY=快照日期);动态线只 UPDATE 本次出现的列,clearedKeys 对应的物理列显式置 NULL,新航班先插一行(FDAY 为空)。
- **明细集合**:每次写航班,9 张明细表对该 FLID 先 DELETE 再按合并后的集合整组 INSERT——即使某个集合本次没变也重写,保证库里与内存状态严格一致。这就是"整集合替换"的落地:ORDINAL 按输入顺序 1..NSOURCE_SEQ 存协议序号,RECORD_VERSION 记写入时的版本。
- last_message_id / state_version / updated_at 一并更新。
#### 事件从状态序列化而来
KAFKA_SCHD 载荷 = FlightNextState 序列化(FlightFieldsJson):键按字典序输出保证同状态字节级稳定;集合键输出为真 JSON 数组/对象,不做双重字符串编码。快照线的事件与落库用同一份事务内状态;动态线的事件目前来自 Handler 锁外预览(见已知偏差)。
### 失败规则(两条线共用)
事务内任何一步失败 → 整体回滚,消息记 FAILED 退避重试。已提交的成功不受后续失败影响:回填和投递失败只落补偿待办,不降级 SUCCEEDED,不重放业务。
### 不变量(两条线共用)
- **状态、事件、终态同事务**:库里能看到的必是完整提交结果。
- **计算是合并不是覆盖**:报文没提的字段一律不动(加工链见上节)。快照更新 FDAY,动态线保留 FDAY,新行为空。
- **快照重放只有一道防线**LAST_MESSAGE_ID 识别最近一次快照的重放;更早的历史快照重入是否安全,靠人工重放的版本/业务日期策略。
- 字段缺失、清除、空数组、异常对象的精确行为见语义矩阵。
### 已知偏差(待收敛)
GTDT Handler 在事务外算一遍预览并构造事件,主泵在事务里再算一遍。目标是 Handler 只表达业务变化,事务内算出唯一一份 nextState,落库和 KAFKA_SCHD 共用——尚未实现。
## 4. 提交之后:回填与事件
提交成功后有两件外部事:回填共享 MySQL 信箱、异步投递 Kafka,各自独立重试。
回填恢复靠一条持久待办:
- 现状:只在回填失败后写 `BACKFILL_TODO`;提交后立即崩溃的窗口没有待办,恢复不了。
- 目标:在业务终态同一事务里登记回填意图,提交后回填成功再删待办。崩溃自然有账可查,不需要额外的"重启对账框架"。
- 红线:登记待办再失败,不能把已提交的 SUCCEEDED 降级为 FAILED。
## 5. 怎么读
现在读一个航班 = 主行 1 次 + 10 类集合各 1 次,全量约 1+10N 次查询;多次读不在同一数据库快照,主子状态不保证一致。
后续在仓储边界按 FLID 批次加载明细,并明确一致性读事务;不引入缓存权威或通用查询框架。`GET /all/flights` 仍是 Controller TODO——仓储方法和展示视图存在不等于接口已交付。
KAFKA_SCHD 输出结构化 JSON,集合禁止双重编码成字符串。清除的 wire 表达、空数组、未知属性、异常局部替换仍有验收缺口,见核查报告;一个全字段样例通过不等于整个 SIS 协议无损。
## 6. Oracle:一整个工作项,不是"等环境"
PG 是唯一实现:SqlDialect 只覆盖两条主行 upsertOracle11gDialect 是没接线的模板(绑定顺序不兼容、MERGE 列数和占位符对不上);迁移目录只有 README,驱动、其余 SQL、视图、目标库测试都没写。
口径因此是"完整适配并验收":以一条跑通的仓储纵向链定接口,处理 DDL、绑定、空字符串、长文本,再上目标 11.2 实测。入口见 [Oracle 迁移说明](../src/main/resources/db/migration/oracle11g/README.md)。
## 7. 老数据升级:脚本能跑不等于数据无损
V1.1→V1.4V1.2 删 *_TXTV1.3 建空明细表,V1.4 删旧槽位列——旧集合数据没有任何自动恢复链路。带数据的实例升级前必须:导出/备份 → 选完整 DNLD 或可信原文恢复 → 逐字段核对后再切换。已发布迁移不改(不靠改历史隐藏损失),也别指望从已删槽位找回数据。
## 8. 现状
已验证:PG 上的事务、部分集合往返、CAS、行锁、脚本升级,101 个测试全绿。
未完成:真实 DNLD/RESP 解析与生产路由、`/all/flights`、全字段与空值无损往返、提交后崩溃恢复、带数据无损迁移、查询规模证据、Oracle 实测、现场对拍。分项证据在核查报告与 Plane,本文不复述工单流水账。
-61
View File
@@ -1,61 +0,0 @@
# 运营航班字段语义矩阵(v2 权威口径)
状态:2026-09-08 实现核对;以下明确区分当前行为与待验收语义。
依据:[航班状态设计](flight-state-design-v2.md)、[SIS 规范](legacy/SIS_AODB_RMS-V0.1.md)、`FlightStateEngine` 实现。
本文是字段语义入口;历史决策不覆盖本文。已发现的不一致和修正计划见 [核查报告](acm2-29-audit-and-simplification.md)。
## 1. 命令模型
当前 Handler 返回字段集,FlightStateEngine 再解析为命令;仓储仍会解析异常/文本 JSON。显式命令尚未贯通所有边界:
| 命令 | 标量 | 集合 |
|---|---|---|
| Unchanged(未出现) | 不修改 | 不修改 |
| Set(value) | 覆盖 | — |
| Clear | 删除该键,物理列置 NULL | 删除全部明细行 |
| Replace(list) | — | 同事务 DELETE + 批量 INSERT 整集合替换 |
| Apply(item, sourceSeq) | — | 引擎内预留,按首个 SOURCE_SEQ 匹配更新,否则追加;无生产调用方,尚非已确认业务协议。 |
- 序号属性为 `"0"` 的条目是协议显式清除标记:**仅当集合内全部条目均为 0** 时翻译为 Clear;与常规条目混排属非法结构,fail fast 拒绝。
- 空数组 `[]` = Replace(空集):明细行清空(区别于 Unchanged 的键缺失)。
- 验收目标:非法 JSON、未知形状、超容输入不得截断后成功。当前 parseCollection 未校验数组元素必须是对象,仓储会忽略未知属性;该目标尚未完成。
## 2. 16 类结构映射
| 集合键 | 存储载体 | 序号属性 | 出现语义 | 显式清除 | Apply |
|---|---|---|---|---|---|
| GTDT 登机口 | `flight_gate` | GTNO | Replace | GTNO=0 全清 | 支持(GTNO 定位) |
| CKDT 值机柜台 | `flight_checkin` | CKNO | Replace | CKNO=0 全清 | 支持;同 CHKC 多舱位分配按 CKNO 区分,不按资源号去重 |
| CLDT 行李转盘 | `flight_belt` | CLNO | Replace | CLNO=0 全清 | 支持 |
| PSDT 计划机位 | `flight_stand_plan` | PSNO | Replace | PSNO=0 全清 | 支持 |
| CHDT 行李滑槽 | `flight_chute` | CHNO | Replace | CHNO=0 全清 | 支持 |
| DELY 延误 | `flight_delay` | 无(仓储残留 DLNO 映射待清理) | Replace(仅保存本次集合,不追加历史) | `[]` 清空 | 不支持(协议无定位键) |
| ABTM 靠撤桥 | `flight_bridge_op` | ASNO | Replace(操作全集) | ASNO=0 全清 | 支持(ASNO 定位;多次靠/撤桥各占一行) |
| CHOT 轮挡 | `flight_chock_op` | CSNO | Replace | CSNO=0 全清 | 支持(CHID=ON/OFF 为业务属性,非序号) |
| ROUT 计划航路 | `flight_route_point(route_kind=ROUT)` | RTNO | Replace | RTNO=0 全清 | 支持 |
| ERUT 扩展航路 | `flight_route_point(route_kind=ERUT)` | RTNO | Replace | RTNO=0 全清 | 支持 |
| FDIV 备降 | 主表前缀列 `FDIV_*` | — | Set(对象) | `"null"`/`{}`/空串 → Clear | — |
| FRET 返航 | 主表前缀列 `FRET_*` | — | Set | 同上 | — |
| FLAB 中止 | 主表前缀列 `FLAB_*` | — | Set | 同上 | — |
| SRVT 服务 | 主表 `SRVT_TEXT` | — | Set | 当前字段转换器不将 null/{}/空串转 Clear;显式 ScalarCommand.Clear 可清列 | — |
| VIPF 贵宾 | 主表 `VIPF_TEXT` | — | Set | 同 SRVT | — |
| MAFL 共享航班 | 主表 `MAFL_TEXT` | — | Set | 同 SRVT | 无查询需求时维持现有载体 |
公共明细列:`FLID`(逻辑归属,当前没有数据库 FK)、`ORDINAL`(输入顺序,1 起)、`SOURCE_SEQ`(字符串、不唯一)、`RECORD_VERSION``CREATED_AT/UPDATED_AT`;当前 PK `(FLID, ORDINAL)`。路线表还需将 ROUTE_KIND 纳入主键,避免 ROUT/ERUT 冲突。相同资源号不代表同一条分配记录,禁止按资源号去重。表中“支持 Apply”仅指引擎分支,生产只生成 Replace/Clear。
## 3. 清除的库内与线上表达
- 库内:标量/异常/文本键 Clear → 对应列 `NULL`;集合键 Clear/Replace(空) → 明细行删除。
- 线上(KAFKA_SCHD):Clear 会移除键;Replace(空集) 当前仍生成 `[]`,数据库读回却缺键,尚未统一。下游清除契约必须固定黄金样例,再同时修正 nextState、落库读回与 wire;不得宣称已等值。
- 异常对象 Set 在增量 SQL 中只写出现的属性;旧对象被省略的属性可能残留。这与对象替换目标不一致,待回归修正。
## 4. 快照(DNLD)与增量(FLOP/ADFT
- DNLD 事务内核:外层标量字段级合并;出现的集合 Replace;集合缺失 = Unchanged。删除走 `SCHD_GEN` 差集 + `deleteDiffByDay`FDAY 域化)。真实 XML parser/生产路由仍未接通,不能视为端到端协议已验收。
- FLOP:字段级合并;Handler 产生的集合键出现即全量替换。
- 重放判定凭 `schd_gen.last_message_id`(快照)与 `identity_key`(消息去重);版本号只是顺序令牌。锁内 CAS 失败一律回滚 `FAILED(INFRA)`
## 5. 时区与空值
- SIS 时间串(`DDMONYYHHMM` 机场当地时)原值保存;类型化提升按查询需要另行定案(ACM2-29 遗留 3)。
- PG 当前保留字符串空值,集合 JSON null 属性会被解析为空串。Oracle 的空字符串/NULL 等价性还未在目标库验证;显式命令本身不能保证持久化往返保真。
+154 -363
View File
@@ -1,418 +1,209 @@
# 航班运行数据接入与当前状态管理设计 # 航班运行数据接入与当前状态管理设计
本文是航班状态接入与当前态存储的唯一设计与架构规范文档:统一界定系统职责、存储模型、字段语义、状态流转与跨模块不变量。已定决策与演进红线以正文为准;未完成项与开放风险在各节显式标注 本文说明航班当前态的设计逻辑:数据从哪里来、快照账本为什么存在、各表如何配合,以及消息如何完成事务提交和对外投递
**标注约定**:正文未加标注的段落是规范要求;凡标注「开放项 / 未决 / 已知偏差 / 待补 / 验收目标 / 现状」的段落,是对当前实现或未定设计的如实披露,不是已交付事实,两类内容不得混作依据 未标注内容是规范要求;“开放项”“未决”“已知偏差”尚未定案或尚未完成
## 阅读速览:专用数据与设计逻辑 ## 1. 目标与边界
### 专用数据与术语 系统从共享 MySQL 信箱接收 AODB 报文,在本地数据库维护航班当前态,再通过 Kafka 发布状态变化。
| 数据 / 术语 | 含义与用途 | 设计目标:
|---|---|
| SIS / AODB | SIS 是 AODB(机场运行数据库,上游业务权威)向各子系统分发数据的接口规范(SIS_AODB_RMS V0.1);本系统从共享 MySQL 信箱接收 AODB 报文。共享信箱是接入边界,本地数据库是本模块对外提供当前状态的唯一读取源,不是高于 AODB 的业务权威。 |
| `FLID` | AODB 分配的航班唯一 IDNumber(1-12),规范 §3.16.2);主表一行对应一个 `FLID`,明细、状态事件及删除通知均按它归属航班。`FLID` ≠ 航班号(`FLNO` 是标量列):关键键字段(航司代码、航班号、航班日期、航班时间、进离港标识)变更时,AODB 以「删除事件 FDEL + 新航班事件 ADFT」重新指名,两个事件的 `FLID` 可能不同(规范 §3.18);主航班与代码共享航班各有 `FLID`、事件分别发送,经 `MAID`/`TAID` 关联。 |
| `FLIGHT_SCHD` + 9 张明细表 | 一个航班的完整权威态:主表保存标量、异常对象前缀列及文本字段;明细表保存 10 类变长集合,其中 ROUT/ERUT 共用路线表。展示视图只是只读投影。 |
| `FDAY`(名单归属运营日) | 当前负责该航班成员管理与差删的日计划快照所覆盖的运营日(三个日期概念中的「名单归属日期」,区分见 §3.2),不是报文接收日或落库日,也不承诺等同于业务运营日。`NULL` 表示尚未被任何日计划代收录,不表示航班没有运行日;跨日归属由整包快照更新,动态事件不修改。 |
| `SCHD_GEN` / `SCHD_GEN_FLID` | 每个运营日的快照账本与当前成员名单。成员资格以名单为准;差删先比较新旧名单,再用当前 `FDAY` 防止误删已迁往其他运营日的航班。 |
| `VERSION` / `STATE_VERSION` | 前者是同一运营日的快照版本,每次新快照推进;后者是单个航班的状态版本。运营日、快照版本、航班状态版本是不同维度。 |
| `identity_key` / `LAST_MESSAGE_ID` | 前者按“发送方\|类型\|子类型\|序号”识别重复业务消息;后者追踪最近一次写入消息,其中快照账本的该字段用于最近一条快照的重放短路,不能拦住任意历史快照。 |
| `ORDINAL` / `SOURCE_SEQ` | 前者是明细输入顺序(从 1 起),后者保存报文中的源序号。源序号可空、不唯一,资源号也不是去重依据,不能据此把多条分配记录合并为一条。 |
| SCHDDNLD / RESP / ADFT | 日计划相关报文。DNLD、RESP 按整包快照换代并差删;ADFT 是单航班临时增量,不换代、不差删、不完成请求。当前仅 DNLD 接入,RESP/ADFT 及请求闭环仍有未决项。 |
| FLOP / GTDT | FLOP 是字段级航班运行事件;规范 §3.19–3.43 定义了 25 类航班级事件(AODB→RMS 事件总计 44 类,§3.13.44),GTDT(§3.34)是其中的登机门更新。当前仅接入 GTDT;动态事件只表达所携带字段的变化。 |
| `REQ_TRACK` / `COUTMSGS` | 前者登记请求及其应答、超时状态,后者承担请求出站 outbox;出站发送与超时重发尚未实现。 |
| `PROC_STATE` / `BACKFILL_TODO` | 前者记录消息处理状态,后者记录共享信箱回填补偿。回填意图与业务终态同事务持久化仍是待补齐目标。 |
| `KAFKA_SCHD` / `KAFKA_MSG` / tombstone | 分别表示航班整态投影、变更通知、删除通知。整态投递可合并同航班的中间版本,消费端按整态替换处理、不得按补丁合并。本文 tombstone 指自定义删除事件:载荷仅含 `FLID``DELETED` 标记、value 非 null**不是** Kafka 日志压缩语义的原生墓碑消息,Kafka 不会对其执行压缩删除。 |
集合与异常、文本字段的逐项映射见第 6 节;表结构职责与代际记账见第 3 节;一个运营日的数据全景、历史版本不留存决策与跨运营日清理(开放项)见 §3.5/§3.6 1. **当前态唯一**:AODB 是业务事实来源,本地数据库是本模块唯一查询源
2. **严格有序**:单活动主泵按信箱 FIFO 处理消息。
3. **原子提交**:航班状态、快照账本、处理结果和 outbox 在同一事务提交。
4. **可恢复**:提交后的信箱回填和 Kafka 投递可独立重试,不重算业务。
5. **完整保存**:变长资源使用明细表,不用固定槽位截断。
### 设计逻辑 本模块只同步报文和维护当前态,不推导取消、延误、备降、返航等业务状态。旅客服务状态不在范围内。
- **提交**:单活动主泵(即消息处理主循环,下同)按 FIFO 推进;航班状态、快照账本、outbox 与处理终态同事务提交,回填和投递在提交后独立重试 生产环境同一时刻只能有一个活动主泵。`PIPELINE_LOCK` 可以串行化数据库事务,但不能代替消息认领、实例选主和故障切换
- **合并**:快照与动态事件均保留未出现的字段,显式清除才清空;同日快照覆盖其携带字段的已有值。集合按整组替换,保留全部条目及顺序。
- **记账**`SCHD_GEN` 每运营日一行,记录最近成功提交的快照版本和消息 ID;`SCHD_GEN_FLID` 每运营日保存该版快照的完整航班名单,新版提交时替换旧名单。
- **差删**:从旧名单中找出新快照不再包含的航班,仅删除当前 `FLIGHT_SCHD.FDAY` 仍等于该运营日的记录。未入代航班和已迁往其他日的航班保留,见 §3.3 示例。
- **投递**`KAFKA_SCHD` 按航班聚合为最新整态,删除发 tombstone;`KAFKA_MSG` 仅通知变化,两个主题不保证先后顺序。
## 1. 系统定位与核心不变量 ## 2. 数据来源与处理方式
本系统作为消息交换与航班运行状态汇聚核心:从共享 MySQL 信箱接入 SIS(AODB)报文,按报文语义把报文合并进本地权威的"航班当前状态",再把状态变化以标准化领域事件发往 Kafka。 | 来源 | 作用 | 处理方式 |
|---|---|---|
| SCHD DNLD / RESP | 建立某个 Operation Day 的完整基准 | 更新名单、写入航班、差删消失航班、推进快照版本 |
| SCHD ADFT | 新增或更新单个临时航班 | 不换代、不差删、不完成请求 |
| FLOP | 更新单个航班的运行变化 | 在当前态上合并,不修改 Operation Day |
系统坚持三条架构底线: DNLD/RESP 是完整快照:未发送的可选字段或集合表示上游当前没有该数据,应清除本地旧值。FLOP 是增量事件:未发送字段保持不变。
- **单一权威状态(Single Source of Truth)。** 本地数据库是本模块对外提供当前状态的唯一读取源;共享 MySQL 信箱与 Kafka 均为本地提交之后的外围边界,各自独立重试,任何外围故障都不会让已提交的状态回滚或重复计算。它不是高于 AODB 的业务权威——上游业务事实以 AODB 为源,本模块是其在本系统范围内的当前态投影 **开放项**:ADFT 的缺失字段表示清除还是保持不变,尚待确认。FLOP 集合表示完整集合还是单次变化,也须逐类确认(ACM2-31#10
- **单活动主泵(Single Active Pump)。** 生产部署约束为同一时刻仅一个实例执行信箱消费与状态推进。事务行锁可以串行化所有遵守该锁协议的数据库事务(跨进程、跨实例同样生效),但行锁本身不能实现消息认领、主实例选举、故障切换或全链路 FIFO——跨实例安全由「单活动实例」部署约束保证,而非行锁。
- **单提交事务(Single Commit Transaction)。** 状态、outbox 事件、处理终态(`PROC_STATE`)在同一个本地事务内落库,全部成功或整体回滚。
**职责边界。** 本模块只同步上游报文并维护当前态,不推导业务状态:取消、延误、备降、返航等运行状态由上游报文给定(对应 FDEL/CNCL/FDIV/FRET 等事件,规范 §3.27–3.33),本模块不做状态机推导;登机、登机结束等旅客服务状态不在接入范围。实际时间取 SIS §3.20 口径——进港为 ATA、出港为 ATD 的单值时间(本地列 `ACTT`),不区分 A-CDM 建模的落地/入位/离位/起飞等多节点;进出港过站关联按到港记录携带的 TAOP/TAFL/TAID 原样保存(见 §3.2)。 ## 3. 核心数据模型
处理按信箱 FIFO 严格有序推进,按业务身份幂等去重,失败按指数退避重试。 ### 3.1 表及职责
数据库口径:现场目标库为 Oracle 11g;当前实际接通并作为验证基准的只有 PostgreSQL,本文描述 PostgreSQL 实现细节。 | 表 | 粒度 | 职责 |
|---|---|---|
| `FLIGHT_SCHD` | 每个 `FLID` 一行 | 保存标量当前态、`OPERATION_DAY``STATE_VERSION` 和最近消息 ID |
| 8 张资源明细表 | 每个 `FLID` 多行 | 保存登机门、值机柜台、行李转盘、计划机位、滑槽、延误、靠撤桥、轮挡 |
| `FLIGHT_ROUTE_POINT` | 每个 `FLID` 多行 | 保存 ROUT/ERUT 路线点,以 `ROUTE_KIND` 区分 |
| `SCHD_GEN` | 每个 Operation Day 一行 | 保存当前快照的 `VERSION``LAST_MESSAGE_ID` |
| `SCHD_GEN_FLID` | 每个 Operation Day、每个 `FLID` 一行 | 保存当前快照的完整航班名单 |
| `PIPELINE_LOCK` | 单行 | 串行化状态写事务 |
| `PROC_STATE` | 每条消息一行 | 保存处理状态和重试结果 |
| outbox 表 | 每个待投递事件一行 | 保存状态事件、变更通知和删除通知 |
| `REQ_TRACK` / `COUTMSGS` | 每个请求一行 | 保存请求状态和请求出站消息 |
| `BACKFILL_TODO` | 每个待补偿回填一行 | 保存共享信箱回填任务 |
## 2. 关键架构决策 9 张明细表承载 10 类集合,因为 ROUT 和 ERUT 共用路线表。`ORDINAL` 保留输入顺序;`SOURCE_SEQ` 只保存上游序号,不作为唯一键或去重键。
| 架构决策 | 理由与边界 | ### 3.2 完整当前态
|---|---|
| 状态、快照代、处理终态与 outbox 同库提交 | 消除 Redis 状态与数据库终态跨库双写窗口;Redis 等缓存不参与动态状态权威。 |
| 当前验证基准为 PostgreSQL,现场目标库为 Oracle 11g | 单次部署只绑定一个内部权威库;Oracle 尚未完成全链路适配,不可作为运行时可用配置开关。 |
| 航班标量主表 + 9 张关系明细表 | 完整保留 10 类变长集合的输入顺序、源序号及多条资源/操作记录,不按展示槽位截断。 |
| 异常对象保留主表前缀列;SRVT/VIPF/MAFL 保留文本列 | 既有载体可承载时不新增抽象或关系表;其读写语义需以回归证明。 |
| 单活动主泵,事务内 `PIPELINE_LOCK` 行锁 | 行锁串行化遵守锁协议的数据库事务(跨实例同样生效),但不实现消息认领、FIFO 或跨实例协调;跨实例安全依赖单活动实例部署约束。 |
| 外部回填与事件投递按幂等重试处理 | 不使用跨库分布式事务;回填意图与业务终态同事务持久化是待补齐目标(见第 8 节)。 |
| 展示视图仅为只读投影 | 仅展示前序资源(前两个登机门/柜台等),不作为全量权威状态或数据一致性比对输入。 |
**废弃方案与架构约束。** 系统明确放弃:固定 GATE1/GATE2 式槽位作为权威、"超界数据静默丢弃"、"零子表为架构约束"、"存储模型决定零方言迁移成本"、以及脱离全量备份前提的 RPO=0 承诺。历史决策论证与撤销记录见 [决策历史](legacy/decision-flight-state-history.md)。 一个航班的完整当前态为:
## 3. 存储:标量主表 + 9 张明细表
### 3.1 为何不采用固定槽位
航班资源本质是变长集合,数量因航班、机型与保障安排而异。SIS 规范 §2 资源限制表给出协议上限:登机门、值机柜台、行李转盘每航班各 99,计划机位 9、当前机位 1,实际机位移动数量不限;规范未给出数量下限,本设计不做数量假设。ACM2-29 曾采用"全标量宽表、零子表"GATE1/GATE2 式固定槽位),当日即被推翻——槽位会截断数据,而设计约束要求保留重复资源的全部属性、输入顺序与源序号。结论:标量进主表,集合进明细表。
### 3.2 表清单
| 表 | 职责 |
|---|---|
| `FLIGHT_SCHD` | 主表,一行一 `FLID`:标量字段、异常前缀列、`SRVT/VIPF/MAFL_TEXT``FDAY``STATE_VERSION``LAST_MESSAGE_ID` 与时间戳。 |
| 8 张集合明细表 | `flight_gate`/`flight_checkin`/`flight_belt`/`flight_stand_plan`/`flight_chute`/`flight_delay`/`flight_bridge_op`/`flight_chock_op`;当前主键 `(FLID, ORDINAL)`。 |
| `FLIGHT_ROUTE_POINT` | 第 9 张明细表;ROUT/ERUT 两类路线共用,以 `ROUTE_KIND` 区分。 |
| `SCHD_GEN` / `SCHD_GEN_FLID` | 快照代际记账,见 3.3。 |
| `PIPELINE_LOCK` | 单写者行锁:每个处理事务先锁 `FLIGHT_SCHD_WRITER` 行;缺失时两事务并发读写同一航班会互相覆盖。 |
| `BACKFILL_TODO` | 回填补偿待办,见第 8 节。 |
| `FLIGHT_SCHD_DISPLAY` | PG 展示视图(前两个登机门/柜台、首个转盘/延误、资源数);仅为投影,非状态来源。 |
9 张明细表承载 10 类集合——路线表一张覆盖两类。
**`FDAY` 的语义。** 涉及「日期」的三个概念必须区分:**航班业务日期**(该航班实例业务上归属哪一天、以什么口径确定——模型不保存,ADFT/动态新增行 `FDAY=NULL` 正是因为模型不为未上计划的行保存业务日期)、**快照覆盖日期**(本份快照查询/发布的运营日 D)、**名单归属日期**(当前由哪一天的快照负责其成员管理与差删)。`FLIGHT_SCHD.FDAY` 保存的是第三者,由整包快照赋值,与接收日、落库日无关。三者在本协议下通常同值,但那是快照接管规则的结果,不是定义使然。动态事件保留 `FDAY`;动态新增或 ADFT 新增航班尚未被收录时为 `NULL`,后续被快照收录再赋值。`NULL` 不表示航班没有运行日,其清理规则见第 7 节。
**过站关联与报文排序(协议事实)。** 到港记录携带 TAOP/TAFL/TAID——离开当前机场的过站连接航班的承运人代码/航班号/AODB ID,仅到港有效(§3.16.2);SCHD 报文内离港航班先于到港航班、代码共享航班跟在主航班之后(§3.16.1 排序注释)。注意:V1.1 迁移注释把 TAOP/TAFL/TAID 标注为「实际承运」,与规范的「过站连接」语义不符,待核对修正(ACM2-31)。
### 3.3 快照记账:每运营日一份版本记录、一份成员名单
**记账单位是运营日,同一天可以多次更新。** 例如 9 月 9 日的日计划先后收到三份快照,对应同一运营日的版本 1、2、3。“换代”指替换该日的当前版本及名单,不是进入下一个自然日。两张账本表记录当前版,不保存各历史版本的完整航班状态。
#### 三份数据各记什么
| 数据 | 记录粒度 | 内容 | 用途 |
|---|---|---|---|
| `SCHD_GEN` | 每运营日一行 | `FDAY``VERSION``LAST_MESSAGE_ID` | 记录该日最近成功提交的快照版本及消息;版本用于 CAS,消息 ID 用于最近一条快照的重放判断。 |
| `SCHD_GEN_FLID` | 每运营日、每成员航班一条记录 | 运营日与 `FLID` 的对应关系 | 保存该日当前版快照包含的完整名单,作为下一次比较的旧名单;新版提交时整体替换。 |
| `FLIGHT_SCHD` | 每 `FLID` 一行 | 航班当前状态及 `FDAY` | 保存快照与动态事件合并后的状态;`FDAY` 表示当前归属,差删时据此排除已跨日迁移的航班。 |
`SCHD_GEN.VERSION` 是日计划快照版本;`FLIGHT_SCHD.STATE_VERSION` 是单航班状态版本。动态事件可以推进后者,但不更新快照版本或成员名单。
#### 一份新快照如何更新账本
设快照覆盖运营日为 D,消息 ID 为 M,航班名单为 N。以下动作与航班状态、outbox、处理终态在同一事务内完成:
1. 锁内读取 D 的账本和旧名单 O。首次快照没有旧名单,按空集比较,成功提交后版本为 1。
2. 若 M 等于账本的 `LAST_MESSAGE_ID`,按最近快照重放短路:不改航班状态、不替换名单、不推进版本、不新增事件;处理成功并执行提交后回填。
3. 计算候选差删名单 `O N`。逐项检查当前航班主行,只保留 `FLIGHT_SCHD.FDAY = D` 的航班作为实际删除对象。
4. 合并写入 N 中各航班的状态,将它们的 `FDAY` 设为 D;删除实际差删对象的主行、全部明细,并登记 tombstone。
5. 将 D 的 `SCHD_GEN_FLID` 名单整体替换为 N;以旧版本为条件执行 CAS,将 `VERSION` 加 1,并把 `LAST_MESSAGE_ID` 更新为 M。版本不符则整个事务回滚。
6. 登记状态事件和处理成功状态,提交。任一步失败,旧版本、旧名单和旧航班状态一并保留,不产生半份快照。
因此,版本计数对应**成功提交的新快照**,不是接收次数;失败重试和最近快照重放不额外加版本。
#### 示例:同日替换与跨日保护
下表以 A、B、C 表示三个 `FLID`,日期均为 2026 年。
| 顺序 | 输入 | `SCHD_GEN` 提交后 | `SCHD_GEN_FLID` 提交后 | 航班状态变化 |
|---|---|---|---|---|
| 1 | M19/9 快照 `{A, B}` | 9/9:版本 1,消息 M1 | 9/9`{A, B}` | A、B 写入,`FDAY=9/9`。 |
| 2 | M29/9 快照 `{A, C}` | 9/9:版本 2,消息 M2 | 9/9`{A, C}` | 候选差删 `{B}`;B 仍属 9/9,删除 B 并登记 tombstoneA 更新,C 新增。 |
| 3 | M39/10 快照 `{C}` | 9/10:版本 1,消息 M39/9 不变 | 9/10`{C}`9/9 仍为 `{A, C}` | C 被 9/10 快照收录,主行 `FDAY` 改为 9/10。 |
| 4 | M49/9 快照 `{A}` | 9/9:版本 3,消息 M4 | 9/9`{A}`9/10 仍为 `{C}` | 候选差删 `{C}`;C 当前属 9/10,保留主行和明细,不发删除事件。 |
| 5 | M4 再次进入快照重放检查 | 不变 | 不变 | 命中 9/9 的最近消息 ID,状态和事件不重复写入。 |
第 3 步说明两者为何都需要:**旧日名单记着上次快照包含 C,主行 `FDAY` 记着 C 已归属新日。** 名单负责找出消失者,主行归属负责判断能否删除。仅凭旧名单会误删跨日航班;仅扫描主行 `FDAY` 则丢失了“上次快照包含谁”的依据。
**重放的三种去向。** 同一条报文重新到达有三种情形,去重与重放守卫各管一层:
- **入口去重**:同一消息首次处理时已绑定 `identity_key`(见 4.1),再次从信箱到达按重复 `SKIPPED`
- **同一消息重试**:事务内失败退回重试时,消息未提交过,正常走全流程;提交成功后因入口去重不会重算。
- **人工重放**:历史快照报文(其消息 ID ≠ 当前 `LAST_MESSAGE_ID`)不会命中重放短路;若已清空入口身份记录而被人工重放,本设计不承诺旧状态不被覆盖——人工重放须携带明确的版本/业务日期策略方可放行(见第 7 节)。
需明确的实现约束:
1. **明细表不声明外键。** 清理动作由仓储显式执行,不依赖数据库级联。`SOURCE_SEQ` 可空且不唯一,仅为记录性质——逐条定位更新的 Apply 协议本期不实现(无生产调用方,且定位键不可靠)。
2. **差删按代圈定范围,不扫 `FDAY`。** 目标运营日 = 本份快照覆盖的运营日。最终删除集合 =(旧名单 − 新名单)∩ 当前仍 `FDAY`=目标运营日的航班;仅对该集合显式删除明细行与主行。`FDAY=NULL`、已归属其他运营日、未在旧名单中的航班均不受本次差删影响;删除与名单替换同事务提交。
3. **演进纪律。** 不为尚无消费者的查询新增类型化时间列、摘要、额外载荷表或索引;时间类字段先保留原始字符串,字段与长度以已应用迁移为准。
4. **跨日归属回迁(未决)。** 上述流程把新名单 N 中各航班的 `FDAY` 一律置为 D:若某航班已被更晚运营日的快照接管(`FDAY=D'`),一份迟到的旧日快照再次收录它时会把 `FDAY` 改回 D,并以旧值覆盖新日的合并态(示例:C 已属 9/10,迟到的 9/9 快照仍含 C → C 被改回 9/9)。现行「跨日保护」(约束 2)只拦截差删删除,不拦截归属回写与字段回退。归属迁移的时序判断是开放项(ACM2-31);定案前本设计不承诺 `FDAY` 单向迁移。
### 3.4 已知待修
路线表当前主键为 `(FLID, ORDINAL)`:同一航班 ROUT 与 ERUT 各自从序号 1 起写,二者共存即冲突。计划通过新迁移改为 `(FLID, ROUTE_KIND, ORDINAL)`(见 ACM2-30)。
### 3.5 一个运营日的数据全景
运营日 D 的快照提交后,库内构成如下整体视图:
| 数据 | 当天形态 |
|---|---|
| `SCHD_GEN` | 一行:D 的最新快照版本与 `LAST_MESSAGE_ID`,同日内被各次快照顺序覆盖。 |
| `SCHD_GEN_FLID` | 当前版快照的完整名单 N,每次新快照整体替换。 |
| `FLIGHT_SCHD` | 被 D 收录的航班各一行(`FDAY=D`),入库后可继续被动态事件推进;跨日被新快照收录时 `FDAY` 改写。 |
| 9 张明细表 | 各 `FDAY=D` 航班的集合行,随每次写入整组替换。 |
| tombstone / outbox | 差删航班与整态投递事件,与上述状态同事务落账。 |
**历史版本不留存(已定决策;下列为四项独立决策,不可互相推论)。****不保存状态历史**:账本只记当前版,同日快照版本 1→2→3 的中间完整状态不保存;② **不逐条投递中间版本**`KAFKA_SCHD` 只按当前整态输出;③ **不提供历史查询**:无此接口;④ **不保存原始报文**:由共享信箱侧保留策略决定,不是本模块承诺。①② 的理由:权威态唯一,差删与 CAS 只依赖当前名单与版本,消费端按 `(FLID, STATE_VERSION, UPDATED_AT)` 去重也不依赖中间版本。后果:当日被覆盖的中间态不可从库中恢复;「可凭重放原始报文重建」依赖信箱保留期限、处理顺序与解析器版本,只是理论可能性,不作为恢复承诺。需要中间态历史线或审计留存时须另行设计,本设计不承担。
### 3.6 跨运营日生命周期
- 差删是历史航班出场的唯一常规路径:旧名单 − 新名单 ∩ 仍属本日的航班,同事务删除主行、明细并登记 tombstone。
- 被新运营日快照接管的航班,其集合经整组替换,仅保留最新值、不保存被替换记录(同 3.5 决策)。
- `FDAY=NULL` 未归属行的清理见第 7 节;已归属历史日的数据没有对应的清理规则。
- **开放项(未决)**:旧运营日的 `SCHD_GEN` / `SCHD_GEN_FLID` 行随日期无限累积,当前无定期清理或归档设计;快照长期停发时历史日留存航班也无兜底清理。清理策略须待真实保留期限需求定案后补齐,清理动作须经 `FLIGHT_SCHD` 与账本的一致性校验,不得破坏差删依赖的"旧名单 + 主行 `FDAY`"双依据。跨日归属回迁的时序防护同样未决(§3.3 约束 4)。
## 4. 状态的两条来路
状态仅有两条来源,各成一条处理线:
- **请求/下载线(基准)。** 建立**指定运营日**航班的基准全量。本系统向 AODB 发送日计划请求,AODB 返回 SCHD 报文——该运营日的全部航班——快照整体落库;该运营日未再出现的航班被差删。
- **动态线(运行事件)。** 跟踪运行中的持续变化——实际时间、登机门开关、值机柜台、行李转盘、延误、靠撤桥、轮挡等。FLOP 字段级事件在规范 §3.193.43 共 25 类(AODB→RMS 事件总计 44 类),当前仅接入 GTDT 一类;单条报文只变更一个航班的一类资源,其余字段不受影响。
两条线共用同一入口与同一套事务/失败规则:进入事务后统一为"锁内读态 → 合并 → 写库 → 事件入 outbox → 终态 → 提交 → 回填"。
```text ```text
信箱队头 → 解码 → 绑定身份(去重)→ 按 Handler 路由 FLIGHT_SCHD 主行
├─ SCHD-DNLD(已接通)────────→ 请求/下载线 + 该 FLID 在 9 张明细表中的全部记录
├─ SCHD-RESP / ADFT(未接通)─→ 请求/下载线 = 航班完整当前态
└─ FLOP 事件(已接 GTDT)────→ 动态线
``` ```
图中「已接通/未接通」为适用范围标注,不是完成度承诺:「已接通」仅表示该类型已进入本设计的路由与事务链,解析、出站与应答闭环等环节是否齐备以 §4.2 各处未决标注为准 写入前先在内存中生成完整新状态,再更新主表和明细表。明细集合按组先删后插,保证数据库与内存状态一致。展示视图只是简化投影,不是权威状态,也不能用于完整数据比对
### 4.1 公共入口(所有消息必经) ### 3.3 Operation Day
1. **排队。** 信箱消息与定时任务共用一个 FIFO 队列,主泵只处理队头;队头失败按退避等待,重试超限或队头滞留超过时限 → `DEAD`。一条异常报文会阻塞其后所有航班与定时任务:最大阻塞时限、隔离/旁路规则与告警阈值是运维未决参数(ACM2-31) `OPERATION_DAY`**Operation Day(运营日)**,表示航班在运行保障业务上的日期,不是报文接收日或数据库落库日
2. **解码。** 报文非法 → `DEAD`(不重试);解码器缺陷类错误 → `FAILED`(退避重试)。
3. **绑定身份。** 幂等键 = `发送方|类型|子类型|序号`,仅首次处理时绑定;该键已被其他消息占用 → `SKIPPED`(重复),自身照常回填共享信箱。协议中 SEQN 为发送方设置的 6 位序号(1–999999,规范 §2 元数据),其唯一范围与重置/回绕策略规范未定义;上游序号重置、重启后重复,以及同身份不同载荷时的处理均为未决项,当前实现仅按键判重(ACM2-31)。
4. **路由。** 按报文类型定位 Handler;无注册 Handler → `FAILED`(可重放,不写终态)。
### 4.2 请求/下载线:基准全量的建立 - DNLD/RESP:取快照指定的 Operation Day。
- FLOP:保留航班当前 Operation Day。
- ADFT 或先于快照到达的 FLOP:无法取得时暂存 `NULL`,后续由快照补齐。
SIS 循环:本系统向 AODB 发送日计划请求 → AODB 返回 SCHD。三个子类型进入本流程时的规则: `OPERATION_DAY=NULL` 只表示运营日尚未取得,不表示航班没有运营日。
| 子类型 | `FDAY` 来源 | 是否换代 | 是否参与差删 | 可完成请求 | ## 4. 快照账本设计
|---|---|---|---|---|
| **DNLD** | 快照覆盖的运营日 | 是 | 是(整包成员差) | 是(运营日匹配时) |
| **RESP** | 快照覆盖的运营日 | 是 | 是(整包成员差) | 是(且要求对应 `REQ_TRACK` 存在) |
| **ADFT** | 无(保持/置为 NULL | 否 | 否 | 否 |
ADFT 为单条临时航班增量:只增改本条 `FLID`(整行整集合写入),不触发换代与整包差删,也不算完成任何请求。ADFT 报文不携带运营日,本行也不单独承载它:`FDAY=NULL` 仅表示该航班未被任何日计划代收录(见 §3.2)——临时航班在现实中有运行日,但模型不为未上计划的行保存该日期。同 `FLID` 后续被整包快照收录时由快照接管(见 §3.2 `FDAY` 语义)。 仅保存 `FLIGHT_SCHD` 无法判断上一份快照包含哪些航班,也无法安全差删、识别最近快照重放或防止并发推进。因此每个 Operation Day 使用两张账本表:
请求登记于 `REQ_TRACK`(同类并发=1,新请求把旧请求强制置 `EXPIRED`),出站经 `COUTMSGS` outbox——出站发送与超时重发尚未实现。
收到下载后的落库步骤:
1. 解析整包并校验,单包上限 10000 个航班(超限 → `DEAD`;协议口径:SCHD 单包不分段、不超过 CIIMS 10MB 上限,`RECS` 为 1–4 位记录数,§3.16.1)。**差删的完整性前提**:「本包 = 该运营日完整名单」由协议保证——规范注释明确日计划是「AODB 最新数据的完整快照」(§3.16.1 注释 2)、单包不分段、`RECS` 给出包内记录数(`RECS=0` 即空名单);`RECS` 与实收航班数的核对、同包重复 `FLID` 的处理为待补校验(ACM2-31)。
2. 进入事务(先锁 `PIPELINE_LOCK` 行):
- **重放短路**:该消息已提交过(与 `SCHD_GEN.LAST_MESSAGE_ID` 相同,记账见 3.3)→ 直接 `SUCCEEDED`,不加版本、不发事件。
- **算成员差**:新快照相对上代消失的航班进入差删名单。
- **合并写入**:锁内逐航班读当前态 → 合并 → 整集合替换,`FDAY` 置为该快照覆盖的运营日(不是报文接收或落库日期)。
- **差删**:删除差删名单内航班的明细与主行(规则见 3.3);每个被删航班同事务登记一条 tombstone 事件入 outbox(载荷见第 8 节)。
- **CAS 推进日代版本**:版本不符则整体回滚 → `FAILED(INFRA)` 退避。
- **应答匹配**:与 `REQ_TRACK` 同事务联动——关联键 =(运营日, 发送方, 请求类型),完成该组合下最新一条 PENDING 请求(协议无请求关联号:RQFD 请求仅含时间范围筛选字段,RESP 也不回指请求报文,此关联键是规范约束下的替代方案,与「同类并发=1、新请求强制 EXPIRED 旧请求」共同收敛错配风险);迟到(已 `EXPIRED`)或重复应答照常执行快照落库(数据仍权威),但不重复置完成、不报错;DNLD 仅在运营日匹配时完成请求,不按类型隐式完成。
- 每个航班一条 `KAFKA_SCHD` 事件入 outbox`PROC_STATE``SUCCEEDED`;提交。
3. 提交后回填共享信箱(失败落 `BACKFILL_TODO`,见第 8 节);重放短路同样执行回填。
**同日重快照的权威规则(M)。** 整包快照是同日**最高权威**:两次快照之间已应用的动态运行更新(实际时间、登机门、柜台等),在重叠字段上以快照值覆盖(含快照显式清空);动态报文更新当前状态,其与快照的冲突按本规则处理。快照省略的字段按「整行替换混合规则」(第 5、7 节)保留合并后当前值,不属于覆盖例外的组成部分。
**规则 M 的成立条件(未决)。** 本规则隐含两个上游前提:① 快照内容是 AODB 生成/发送时点的完整最新数据(§3.16.1 注释 2 支持生成时点口径);② 两条线的投递顺序与上游发送顺序一致(共享信箱 FIFO + 同一发送方 SEQN 单调)。若前提 ② 不成立(快照生成后延迟投递、SEQN 按类型分段编号等),重叠字段会被旧值回退——例如 10:00 生成的快照 10:03 才入队,会把 10:02 已处理的登机门更新改回旧值。前提 ② 尚未从协议条款逐条核实,把规则 M 作为对外承诺前须经设计评审确认(ACM2-31)。
**示例:一次 DNLD 快照的落库。** 示例中的 CA100、MU201、CZ300 均为 AODB 分配的 `FLID` 值(写成形似航班号仅为可读;`FLNO` 航班号是主表标量列,与 `FLID` 无恒定关系)。报文为 AODB 下发的**运营日 2026-09-09** 的整包快照,含 CA100、MU201 两个航班(`FDAY` 取快照覆盖的运营日 2026-09-09,与该报文何时接收、何时落库无关);库内该运营日的代(`SCHD_GEN`)当前成员为 CA100、CZ300。进入事务后依次发生:
| 步骤 | 动作 | 结果 |
|---|---|---|
| 重放检查 | 该消息 ID 等于日代 `LAST_MESSAGE_ID` | 否,继续 |
| 算成员差 | 上代成员 {CA100, CZ300} 新快照 {CA100, MU201},∩ 当前仍 `FDAY`=2026-09-09 | 差删名单 = {CZ300} |
| 合并写入 | CA100 已存在 → 锁内读态、整集合替换,`FDAY=2026-09-09`;MU201 新航班 → 先插主行再写明细 | 两航班状态就位 |
| 差删 | 删 CZ300 主行 + 全部明细行 | CZ300 消失 |
| CAS 推版本 | `SCHD_GEN` 版本 7 → 8;不符则整体回滚 `FAILED(INFRA)` | 版本推进 |
| 事件 | CA100、MU201 各一条 `KAFKA_SCHD` 入 outbox | |
| 终态 | `PROC_STATE``SUCCEEDED`,提交 | 提交后回填共享信箱 |
此后 Dispatcher 按 FLID 聚合这批 `KAFKA_SCHD` 发出(见第 8 节)。
**覆盖范围与未决。** 本流程的设计范围当前仅覆盖 DNLD 子类型;RESP 与 ADFT 的 Handler、「请求 → 应答 → 标记完成」闭环为未决设计项,未覆盖类型的处理按 `FAILED(UNSUPPORTED)` 对齐。参考数据请求(RQRD;参考数据事件见规范 §3.1–3.15)同样登记于 `REQ_TRACK`,但应答落参考库、由 RequestCoordinator 处理,与航班状态无关。
### 4.3 动态线:运行事件的落库(以 GTDT 为例)
1. **Handler 纯函数决策。** 输入 = 受影响航班当前态 + 报文,输出 = 字段变更与待发事件;此步不算账、不落库。每类资源的语义在 Handler 内固定,如 GTDT 为登机门集合整体替换(GTNO=0 表示清除)。
2. 进入事务(先锁 `PIPELINE_LOCK` 行):
- 每个受影响航班:锁内重读最新态 → 合并字段变更 → 整集合替换写入,保留既有 `FDAY`(保留该行所属的运营日/代;动态事件不携带也不推断运营日,跨运营日的归属更正只由请求/下载线的快照接管完成,见 §3.2)。
- 事件(`KAFKA_MSG` 变更通知 + `KAFKA_SCHD` 状态推送)入 outbox`PROC_STATE``SUCCEEDED`;提交。
3. 提交后回填共享信箱(同上)。
**示例:一条 GTDT 报文如何变更登机门。** 报文(FLOP 增量,仅携带登机门,其余字段未出现):
```text ```text
META: SNDR=AODB, TYPE=FLOP, STYP=GTDT, SEQN=42 SCHD_GEN 保存当前快照版本和最近消息 ID
FLOP: FLID=CA100 SCHD_GEN_FLID 保存当前快照的完整 FLID 名单
GTDT GTNO=1 GATE=A1 GTYP=D
GTDT GTNO=3 GATE=B2 GTYP=I
``` ```
库内 CA100 当前登机门为 G28(序号 1)、G33(序号 2)。处理过程: `SCHD_GEN.VERSION` 是运营日快照版本;`FLIGHT_SCHD.STATE_VERSION` 是单航班状态版本。FLOP 只推进后者,不修改快照账本。
1. **解析字段集**GTDT = [(GTNO=1, GATE=A1, GTYP=D), (GTNO=3, GATE=B2, GTYP=I)]。其余键未出现 = 不动——延误、机位、时间一概不碰 同一运营日可以接收多份快照。每次成功提交都会替换名单并将快照版本加一;失败或最近快照重放不加版本
2. **翻译命令**GTDT 出现 → Replace,整组替换为这 2 条。
3. **锁内合并**:读当前态 → 整组替换 → 版本 +1、记 `LAST_MESSAGE_ID`
4. **写库**`flight_gate` 中 CA100 旧 2 行删除,按输入顺序插入 2 行(ORDINAL=1,2SOURCE_SEQ=1,3);GTDT 无标量列,主表仅推进版本/消息/更新时间。
5. **事件**`KAFKA_MSG` 变更通知;`KAFKA_SCHD` 载荷 `"GTDT":[{"GATE":"A1","GTNO":"1","GTYP":"D"},{"GATE":"B2","GTNO":"3","GTYP":"I"}]`——JSON 数组、不做二次字符串编码,键按字典序。
6. **提交后**:回填共享信箱。
读回时按 ORDINAL 组装主行与 `flight_gate` 2 行为权威态;展示视图取前两个登机门 A1、B2。 ## 5. 完整快照处理
**清除与拒绝语义示例:** 设本次快照的 Operation Day 为 D,消息 ID 为 M,新名单为 N,账本旧名单为 O。以下步骤在同一事务完成:
- `<GTDT GTNO="0"/>` 单条且全 0 → Clear`flight_gate` 行删空,`KAFKA_SCHD` 中 GTDT 键消失 1. 锁定 `PIPELINE_LOCK`,读取 D 的账本和旧名单 O
- `[{"GTNO":"1","GATE":"A1"},{"GTNO":"0"}]`:0 与正常条目混排 → 非法结构,整条消息失败,不猜测、不截断 2. 若 M 等于 `SCHD_GEN.LAST_MESSAGE_ID`,按最近快照重放处理,不改状态、版本和事件
- 字段未出现 ≠ 清除:仅收到一条 DELY 报文时登机门不受影响 3. 校验完整性:`RECS` 必须等于实收航班数,范围为 0–9999
- 异常键的空值才构成 Clear:FDIV 出现为 `{}` → Clear → `FDIV_*` 列全部置 NULLSRVT 为空串/null 目前不算 Clear(见第 6 节),只有显式 Clear 命令能清列 4. 为 N 中每个航班生成完整新状态,写入主表和明细表,设置 `OPERATION_DAY=D`
5. 计算候选删除集合 `O N`
6. 只删除候选集合中当前仍满足 `FLIGHT_SCHD.OPERATION_DAY=D` 的航班,并登记删除事件。
7. 用 N 替换 `SCHD_GEN_FLID` 的旧名单。
8. 通过 CAS 将 `SCHD_GEN.VERSION` 加一,并记录 M。
9. 登记状态事件和 `PROC_STATE=SUCCEEDED`,提交。
## 5. 状态变更实现(两条线共用) 任一步失败,状态、名单、版本、处理结果和事件全部回滚。
所有状态变更统一经加工链:**报文 → 命令 → 合并出新状态 → 写库**。 ### 5.1 差删为什么检查 Operation Day
### 5.1 数据统一形态 旧名单只能说明“上一份 D 快照包含该航班”,不能说明航班现在仍属于 D。例如 C 先在 9 月 9 日名单中,随后被 9 月 10 日快照接管。新的 9 月 9 日快照不含 C 时,不能删除已经属于 9 月 10 日的 C。
内存中一个航班的状态即一个模型(`FlightNextState`):一组标量键值 + 10 类集合(每类为一组条目,每条目一组键值)+ 版本/消息追踪字段。主表存标量、明细表存集合,列映射固定(如 GTDT 的 GATE/GOTM/GCTM → `flight_gate` 对应列);读回时按 ORDINAL 组装集合,数据库权威态 = 主行 + 明细行。 最终删除集合为:
### 5.2 第一步:报文到命令 ```text
(旧名单 O 新名单 N)
∩ 当前 OPERATION_DAY 仍等于 D 的航班
```
当前 Handler 返回字段集,`commandsFromFields` 将其翻译为命令;仓储仍会解析异常/文本 JSON,显式命令尚未贯通全部边界 这就是名单账本和主表 Operation Day 必须同时存在的原因
| 命令 | 标量 | 集合 | ### 5.2 Operation Day 回退风险
差删检查只能防止误删,不能防止迟到的旧日快照把 `OPERATION_DAY` 从新日改回旧日,也不能防止旧字段覆盖新状态。
**开放项**:运营日迁移的时序规则尚未定案;当前不承诺 Operation Day 只向前迁移(ACM2-31#9)。
### 5.3 重放边界
- `identity_key` 负责入口消息去重。
- `SCHD_GEN.LAST_MESSAGE_ID` 只识别该运营日最近一份快照的重复执行。
- 事务失败后的同消息重试会重新执行完整事务。
- 更早的历史快照不会被 `LAST_MESSAGE_ID` 拦截。
人工重放历史快照必须带明确的版本和 Operation Day 策略,否则可能覆盖新状态。
## 6. 动态事件处理
FLOP 不建立快照,也不修改快照名单:
1. 根据 `FLID` 读取完整当前态。
2. 合并本次变化;未出现字段保持不变。
3. 保留当前 `OPERATION_DAY`;无法取得时写 `NULL`
4.`STATE_VERSION` 加一,写入主表和明细表。
5. 同事务登记 `KAFKA_MSG``KAFKA_SCHD` 和处理结果。
**已知偏差**:GTDT 当前按登机门集合 Replace 处理,但 FLOP 集合的完整/增量语义尚未确认。动态事件还在锁外生成事件预览、事务内再次计算状态;目标是只在事务内生成一次新状态,供落库和事件共用。
## 7. 请求与提交后处理
### 7.1 请求匹配
请求通过 `REQ_TRACK` 登记,通过 `COUTMSGS` 发送。同类请求只保留一条有效记录,新请求将旧请求置为 `EXPIRED`
RESP 按(Operation Day、发送方、请求类型)完成最新一条 `PENDING` 请求。迟到或重复应答仍可更新快照,但不重复完成请求。DNLD 仅在 Operation Day 匹配时完成请求。协议没有请求关联号,因此该匹配规则仍需验收。
### 7.2 信箱回填
业务事务提交后回填共享 MySQL 信箱。回填失败不得把已提交的 `SUCCEEDED` 改回 `FAILED`
**已知偏差**:当前只在回填失败后写 `BACKFILL_TODO`。若提交后、写待办前崩溃,任务无法恢复。目标是在业务事务中预先登记回填意图。
### 7.3 Kafka 投递
`KAFKA_SCHD` 是航班整态,不是补丁。Dispatcher 合并同一 `FLID` 的未发事件,读取数据库当前态并按最新 `STATE_VERSION` 输出;中间版本不逐条发送。
- 消费端按 `(FLID, STATE_VERSION, UPDATED_AT)` 拒绝旧状态覆盖新状态。
- `KAFKA_MSG` 只通知变化,不承载完整状态。
- 两个主题不保证跨主题顺序。
- 整态中键缺失表示该数据不存在,消费端必须删除旧值。
差删使用自定义 tombstonevalue 非 null,载荷只有 `FLID``DELETED`。它与删除操作同事务登记,投递失败持续重试。
**开放项**:删除事件没有版本号;迟到删除、删除后恢复和 `STATE_VERSION` 连续性尚未解决(ACM2-31#11)。
## 8. 生命周期与历史
本模块只保存当前态:不保存状态历史,不逐条投递中间版本,不提供历史查询,也不承诺保存原始报文。
`OPERATION_DAY=NULL` 的航班不参与快照差删。定期作业可清理超过 N 天且未被请求或快照名单引用的记录;N 默认 7 天,启用前须由消费方确认。
**开放项**:历史运营日账本和航班没有清理规则。清理不能破坏差删依赖的旧名单和 Operation Day。
## 9. 失败与读取
### 9.1 失败处理
| 错误 | 处理 | 队头行为 |
|---|---|---| |---|---|---|
| Unchanged(未出现) | 不修改 | 不修改 | | 非法报文、数量不符、容量超限 | `DEAD`,不重试 | 立即释放 |
| Set(value)(出现) | 覆盖 | — | | 未支持类型 | `FAILED(UNSUPPORTED)` | 达到阈值后转 `DEAD` |
| Clear | 删键,物理列置 NULL | 删除全部明细行 | | 数据库或内部故障 | `FAILED(INFRA)`,退避重试 | 成功或超限前阻塞 |
| Replace(list) | — | 同事务 DELETE + 批量 INSERT 整集合替换 | | 快照 CAS 冲突 | 回滚并重读重试 | 成功前阻塞 |
| Apply(item, sourceSeq) | — | 引擎内预留:按首个 SOURCE_SEQ 匹配更新,否则追加;无生产调用方,尚非已确认协议 |
规则: 最大阻塞时限、旁路规则和告警阈值仍是开放项(ACM2-31#13)。SEQN 的唯一范围、重置和回绕策略也未定义(ACM2-31#12)。
- **字段未出现 = 不动。** 增量报文只携带变化部分,其余不受影响。 ### 9.2 读取
- **标量出现 = Set(覆盖)。** 异常键(FDIV/FRET/FLAB)载荷为空串、null、空对象时 = Clear。
- **集合出现 = Replace。** 整组替换为报文给定条目。条目序号属性(如 GTNO)为 "0" 是协议显式清除标记:仅当集合内**全部**条目均为 0 时翻译为 Clear;与正常条目混排属非法结构,fail fast 拒绝。
- **空数组 `[]` = Replace(空集)**,明细行清空——区别于字段缺失的 Unchanged。
- DELY 无协议序号属性,不支持逐条 Apply;清除走空数组替换。
**验收目标。** 非法 JSON、未知形状、超容输入不得在截断后成功。当前 `parseCollection` 未校验数组元素必须是对象,仓储会忽略未知属性;该目标尚未完成 完整航班状态必须在一致性读事务中读取主表和全部明细表。当前逐航班读取会产生 N+1 查询,且主表和明细表可能不在同一数据库快照
### 5.3 第二步:命令合并进当前态(纯函数) **待补**:按 FLID 批量加载明细,并为查询和 Dispatcher 建立一致性读边界。`GET /all/flights` 尚未交付。
`FlightStateEngine.apply` 从当前态拷贝一份并逐条应用命令:Set 覆盖键值;Clear 删键并记入 clearedKeys(被清除的标量键清单,供第三步写库时据此将物理列置 NULL);Replace 整组替换。随后版本 +1、记 `LAST_MESSAGE_ID`,产出 `FlightNextState`。纯函数:不碰库、不发事件,同样输入恒得同样输出。 ## 10. 当前范围与关键开放问题
### 5.4 第三步:新状态写库(唯一写入口 `persistNextStates` 当前只有 PostgreSQL 链路和 DNLD、GTDT 的部分流程接通。Oracle 11g、RESP、ADFT、请求出站、完整解析、回填崩溃恢复和一致性读取仍未完成。
- **写库写的是合并后完整态,不是报文字段。** 主表与集合写入的输入是锁内合并后的完整 `FlightNextState`;「未提字段一律不动」发生在引擎合并层(5.3),不在写库层。快照线整行替换(`FDAY`=该快照覆盖的运营日)写出的同样是合并后状态——快照省略的字段以合并流程结束时的当前值持久化。例:航班当前态含 `ESTT=0900`(预计时间)与登机门 {G28},快照给出 `STD``ACTT=1012`(实际时间,进港语义 ATA/出港 ATD)与登机门 {A1},省略 `ESTT` → 输出 = `STD`/`ACTT`/登机门改为快照值,`ESTT` 保留 0900。若快照显式给空(Clear/空数组)才构成清空。`FDAY` 由快照 Set 显式推进,是保留规则唯一的结构性例外 关键开放问题由 ACM2-31 跟踪:FLID 生命周期、快照与动态事件顺序、Operation Day 回退、FLOP 集合语义、删除事件版本、幂等键生命周期、FIFO 参数、一致性读取、快照完整性校验和 Oracle 适配
- **动态线**仅 UPDATE 本次出现的列,clearedKeys 对应物理列显式置 NULL;新航班先插一行 `FDAY=NULL`——动态消息不携带运营日、不触发换代,新行尚未被任何日计划代收录(并非"无运营日"),待后续被某运营日整包快照收录时由快照赋值(清理见第 7 节「未归属行」)。
- **明细集合**:每次写航班,9 张明细表对该 `FLID` 先 DELETE 再按合并后集合整组 INSERT——即使某集合本次未变也重写,以保证库内与内存状态严格一致。此即"整集合替换"的落地:ORDINAL 按输入顺序 1..NSOURCE_SEQ 存协议序号,RECORD_VERSION 记写入时版本。
- `last_message_id` / `state_version` / `updated_at` 一并更新。
### 5.5 事件由状态序列化而来 ### 规则 M:同日快照与动态事件的优先级
`KAFKA_SCHD` 载荷 = `FlightNextState` 序列化(`FlightFieldsJson`):键按字典序输出保证同状态字节级稳定;集合键输出为 JSON 数组/对象,不做二次字符串编码。快照线的事件与落库共用同一份事务内状态;动态线的事件目前来自 Handler 的锁外预览(见 5.6),偏差收敛前动态事件载荷不承诺与提交状态逐字节一致——对外状态的最终一致性由投递聚合按当前整态投影兜底(第 8 节) 当前候选规则是:同一 Operation Day 内,完整快照覆盖此前动态事件写入的重叠字段;快照未出现字段按完整快照语义清除
### 5.6 已知偏差(待收敛) 该规则要求快照与动态事件的投递顺序等于上游发送顺序。协议尚未证明这一点,因此规则 M 不是已成立的不变量,也不能作为对外承诺(ACM2-31#8)。
GTDT Handler 在事务外先算一遍预览并构造事件,主泵在事务内再算一遍;该偏差收敛前,不能宣称动态事件载荷与提交状态一致(见 5.5,投递侧以整态投影兜底)。目标状态为 Handler 只表达业务变化,事务内产出唯一一份 nextState,落库与 `KAFKA_SCHD` 共用——尚未实现,列为可靠性验收项。
## 6. 字段语义矩阵
### 6.1 16 类结构映射
| 集合键 | 存储载体 | 序号属性 | 出现语义 | 显式清除 | Apply |
|---|---|---|---|---|---|
| GTDT 登机门 | `flight_gate` | GTNO | Replace | GTNO=0 全清 | 引擎预留(未启用;GTNO 定位) |
| CKDT 值机柜台 | `flight_checkin` | CKNO | Replace | CKNO=0 全清 | 引擎预留(未启用);同 CHKC 多舱位分配按 CKNO 区分,不按资源号去重 |
| CLDT 行李转盘 | `flight_belt` | CLNO | Replace | CLNO=0 全清 | 引擎预留(未启用) |
| PSDT 计划机位 | `flight_stand_plan` | PSNO | Replace | PSNO=0 全清 | 引擎预留(未启用) |
| CHDT 行李滑槽 | `flight_chute` | CHNO | Replace | CHNO=0 全清 | 引擎预留(未启用) |
| DELY 延误 | `flight_delay` | 无 | Replace(仅保存本次集合,不追加历史) | `[]` 清空 | 不支持(协议无定位键) |
| ABTM 靠撤桥 | `flight_bridge_op` | ASNO | Replace(操作全集) | ASNO=0 全清 | 引擎预留(未启用;ASNO 定位,多次靠/撤桥各占一行) |
| CHOT 轮挡 | `flight_chock_op` | CSNO | Replace | CSNO=0 全清 | 引擎预留(未启用;CHID=ON/OFF 为业务属性,非序号) |
| ROUT 计划航路 | `flight_route_point(route_kind=ROUT)` | RTNO | Replace | RTNO=0 全清 | 引擎预留(未启用) |
| ERUT 扩展航路 | `flight_route_point(route_kind=ERUT)` | RTNO | Replace | RTNO=0 全清 | 引擎预留(未启用) |
| FDIV 备降 | 主表前缀列 `FDIV_*` | — | Set(对象) | `"null"`/`{}`/空串 → Clear | — |
| FRET 返航 | 主表前缀列 `FRET_*` | — | Set | 同上 | — |
| FLAB 中止 | 主表前缀列 `FLAB_*` | — | Set | 同上 | — |
| SRVT 服务 | 主表 `SRVT_TEXT` | — | Set | 当前字段转换器不将 null/{}/空串转 Clear;显式 ScalarCommand.Clear 可清列 | — |
| VIPF 贵宾 | 主表 `VIPF_TEXT` | — | Set | 同 SRVT | — |
| MAFL 共享航班 | 主表 `MAFL_TEXT` | — | Set | 同 SRVT | 无查询需求时维持现有载体 |
公共明细列:`FLID`(逻辑归属,当前无数据库外键)、`ORDINAL`(输入顺序,1 起)、`SOURCE_SEQ`(报文序号属性,如 GTNO=3;仅记录,不参与定位、不唯一)、`RECORD_VERSION``CREATED_AT/UPDATED_AT`(整组替换为物理重写:`CREATED_AT` 反映最近一次重建时刻,不是业务记录时间);当前主键 `(FLID, ORDINAL)`。路线表尚需把 ROUTE_KIND 纳入主键以避免 ROUT/ERUT 冲突。相同资源号不代表同一条分配记录,禁止按资源号去重。**表中"引擎预留(未启用)"的 Apply 仅指引擎分支存在,生产只生成 Replace/Clear。**
**集合全集语义:协议依据与待确认项。** 快照线(SCHD)可按全集理解:规范注释明确日计划是「AODB 最新数据的完整快照」,可选字段/段未出现即 AODB 无此信息、子系统应删除本地已有值(§3.16.1 注释 4),DELY 亦明确「该标签对所有已记录延误重复出现」。动态线各事件每次携带全量集合还是仅本次变更,规范未逐条写明——CHOT 的 CSNO 实为机位序号(非轮挡序号),每段记录一次上/下轮挡移动(§3.22.2),若事件仅携带本次移动,Replace 会丢失既有操作记录;各集合「序号=0 全清」的协议出处也需逐类核实。动态线集合语义确认前,Replace 全集语义仅对快照线可视为定案(ACM2-31 未决)。
### 6.2 清除的库内与线上表达
- 库内:标量/异常/文本键 Clear → 对应列置 `NULL`;集合键 Clear/Replace(空集) → 明细行删除。
- 线上(`KAFKA_SCHD`):清空的表达**定为键缺失**——Clear 与 Replace(空集) 事件同语义,`KAFKA_SCHD` 载荷不输出该集合键;下游不得把「键缺失」与「`[]`」作两种解释。库内读回(无键)与 wire 表达按此收敛,黄金样例按此口径固定(投递聚合与 tombstone 见第 8 节)。
- **两个方向的「缺失」语义相反**,消费契约必须写明:上游输入报文中字段缺失 = Unchanged(保留原值);下游 `KAFKA_SCHD` 整态投影中字段/键缺失 = 当前态无该数据,消费端应移除本地旧值。下游接收的是整态替换,不得按补丁合并。
- 异常对象 Set 在增量 SQL 中仅写出现的属性;旧对象被省略的属性可能残留,与对象整体替换目标不一致,待回归修正。
### 6.3 时区与空值
- `FDAY` 只记录日期(该行所属代覆盖的运营日,见 §3.2),其来源与日界都按「快照覆盖的运营日」口径:由日计划快照决定,不由报文接收/落库时刻推算——跨午夜接收不改变归属;动态事件不更新 `FDAY`
- SIS 时间串(`DDMONYYHHMM`,机场当地时)原值保存;类型化提升按实际查询需要另行定案。
- PG 当前保留字符串空值,集合 JSON null 属性会被解析为空串。Oracle 11g 对零长度字符值按 NULL 处理是官方文档明确的既定行为,不再是待确认的数据库事实;待验证的是本模块在该行为下能否维持字段语义——空串往返、SRVT/VIPF/MAFL「空串不算 Clear」的判断、显式命令的持久化保真,均列为 Oracle 适配的交付门槛验证项。
## 7. 失败规则与不变量(两条线共用)
事务内任一步失败 → 整体回滚,消息记 `FAILED` 退避重试。已提交的成功不受后续失败影响:回填与投递失败仅落补偿待办,不降级 `SUCCEEDED`,不重放业务。
**不变量:**
- **状态、事件、终态同事务**:库内可见的必是完整提交结果。
- **计算是合并不是覆盖;写库是一致不是增量。** 报文未提字段一律不动(加工链见第 5 节);写入库的永远是锁内合并后的完整 `FlightNextState`,其中包括快照线整行替换写出的合并后状态(混合规则与例见 5.4)。快照线更新 `FDAY`(置为该快照覆盖的运营日),动态线保留 `FDAY`(不改变归属、不推断运营日),动态/ADFT 新行 `FDAY=NULL`(尚未被任何运营日代收录,不等于无运营日)。
- **快照是同日最高权威**:重叠字段上快照覆盖动态更新(见 4.2 规则 M 及其成立条件标注);动态报文更新当前状态,与快照的冲突按规则 M 处理。
- **快照重放仅一道防线**:凭 `schd_gen.last_message_id`(见 3.3)识别最近一次快照的重放;消息去重凭 `identity_key`;版本号只是顺序令牌。更早历史快照的重入是否安全,依赖人工重放的版本/业务日期策略(见 3.3 重放三种去向)。
- **未归属行(`FDAY=NULL`)的生命周期**:动态线先于快照到达或从未上日计划的 `FLID``FDAY=NULL` 建行,不隶属任何代、差删不触达,不引用 `SCHD_GEN` 名单。其清理由定期历史化作业执行:条件 = 最后更新时间超过 N 天(默认 7,可配置)且未被任何待答请求/名单引用;N 的默认值须有真实消费方复核后启用。已归属历史日的账本行与航班清理为另一独立未决项(见 §3.6),不适用本规则。
**错误分类与队列行为(目标矩阵):**
| 错误类别 | 示例 | 终态 | 重试 | 队头行为 |
|---|---|---|---|---|
| 协议结构非法(解码乱码/0 与正常条目混排/未知形状/缺身份键) | §5.2 非法结构 | `DEAD` | 不重试 | 立即释放 |
| 容量超限(单包 >10000 航班等) | §4.2 超限 | `DEAD` | 不重试 | 立即释放 |
| 无注册 Handler | 路由不到 Handler4.1 | `FAILED`,不写终态 | 不自动重试,可人工重放 | 队头滞留至超限 |
| 未定义 SCHD 子类型 | 未在子类型规则表中的子类型 | 现状 `FAILED(UNSUPPORTED)`;目标终态待定案(其余行为以本行现状标注为准) | 不自动重试 | 队头滞留至超限 |
| 解码器缺陷/内部错误 | 事务 DB 故障 `FAILED(INFRA)` | `FAILED` → 退避 | 超限转 `DEAD` | 队头滞留直至超限 |
| 并发版本 CAS 冲突 | 日代推进版本不符 | 事务回滚(无终态) | 同事务或下一轮重读自动重试 | 队头滞留直至成功 |
| 人工重放 | — | 仅 `DEAD` 消息可人工重放,须携带版本/业务日期策略(见 4.2/3.3) | — | — |
矩阵中「既有」:`DEAD`/`FAILED` 两类终态、CAS 自动回滚、队头失败退避;「待补校验」:超容与结构非法的显式拒绝(§5.2 验收目标)、未定义子类型的终态归属、人工重放的幂等身份策略(失败前已绑定的 `identity_key` 与重放身份的关系未定义)。每类错误有且只有一个默认路径,以本矩阵行为准。
字段缺失、清除、空数组、异常对象的精确行为见第 6 节。
## 8. 提交之后:回填与事件
提交成功后有两件外部副作用:回填共享 MySQL 信箱、异步投递 Kafka,二者各自独立重试。
回填恢复依赖一条持久待办:
- **现状**:仅在回填失败后写 `BACKFILL_TODO`;提交后立即崩溃的窗口内没有待办,无法恢复。
- **目标**:在业务终态同一事务内登记回填意图,提交后回填成功即删待办。崩溃后自然有账可查,无需额外"重启对账框架"。
- **红线**:登记待办后再失败,不得把已提交的 `SUCCEEDED` 降级为 `FAILED`
**`KAFKA_SCHD` 投递语义(按 FLID 聚合,第 8 节的主体)。** Dispatcher 聚合未发出的同 `FLID` 事件,按该 `FLID` **当前整态投影**输出——不是逐事件补丁流:
-`FLID` 多条未发事件合并为一次按最新 `STATE_VERSION` 生成的整态载荷;中间版本的载荷不逐条补发。
- 旧批次的投递重试可晚于新批次发出;消费端以 `(FLID, STATE_VERSION, UPDATED_AT)` 去重,晚到的旧投影不应覆盖新状态。
- `KAFKA_MSG` 仅作变更通知,不承载状态;两个主题之间不承诺顺序。
- 未归属行约定(第 7 节)与 tombstone(下)也是事件身份的一部分:`FLID` 不重复重发。
- **tombstone(差删的对外删除语义;自定义删除事件,非 Kafka 原生墓碑)**:被差删航班的最后一类 `KAFKA_SCHD` 事件以 tombstone 表达——载荷仅含 `FLID``DELETED` 标记(无状态字段;value 非 null,Kafka 不会按日志压缩语义处理它);与差删同事务登记,投递失败照常重试、不过期,下游接到后删除该 `FLID` 本地数据。删除通知不携带版本号:迟到删除、删除后恢复与旧更新事件的冲突消解由消费端负责;主行物理删除后 `STATE_VERSION` 的连续性(删除前与重建后的事件可能同号)未定义——删除事件的版本/世代标识为开放项(ACM2-31)。
## 9. 读取路径
当前读一个航班 = 主行 1 次 + 10 类集合各 1 次,共 11 次查询;全量读 N 个航班约 1+10N 次(N=航班数,主行批量 1 次 + 每航班 10 类集合各 1 次)。多次读不一定落在同一数据库快照,主子状态不保证一致。
后续在仓储边界按 FLID 批次加载明细,并明确一致性读事务;投递侧 Dispatcher 的整态投影读取受同一缺口影响,一并纳入验收。不引入缓存权威或通用查询框架。`GET /all/flights` 仍是 Controller TODO——仓储方法与展示视图存在不等于接口已交付。
`KAFKA_SCHD` 输出结构化 JSON,集合禁止双重编码为字符串。清空的 wire 表达已按第 6 节定案(键缺失);未知属性、异常局部替换与空值往返仍存在验收缺口(见第 6 节);单个全字段样例通过并不等于整个 SIS 协议无损。
+5 -6
View File
@@ -8,13 +8,12 @@
> 本文正式确立选项 C 为最终方案:运营航班权威 `FLIGHT_SCHD` 与代版本 `SCHD_GEN` 提前至阶段 A 落自有 PostgreSQL;Redis 彻底退出动态权威与写路径。 > 本文正式确立选项 C 为最终方案:运营航班权威 `FLIGHT_SCHD` 与代版本 `SCHD_GEN` 提前至阶段 A 落自有 PostgreSQL;Redis 彻底退出动态权威与写路径。
> 交叉引用:[architecture.md](../architecture.md)、[design.md](../design.md)、[user-stories.md](../user-stories.md)、ACM2-10FS1FS8 实施批次)。 > 交叉引用:[architecture.md](../architecture.md)、[design.md](../design.md)、[user-stories.md](../user-stories.md)、ACM2-10FS1FS8 实施批次)。
> **2026-09-08 复核入口**:详细证据与重构提案见 [运营航班状态设计评审](../review-flight-state-2026-09-08.md)。 > **2026-09-08 复核入口**:详细证据与重构提案见 [运营航班状态设计评审](../flight-state.md)。
> 本轮确认的部署边界:现场提供 Oracle 时使用 Oracle 11g,否则自行部署 PostgreSQL。 > 本轮确认的部署边界:现场提供 Oracle 时使用 Oracle 11g,否则自行部署 PostgreSQL。
> 评审保留单库原子提交方向,但发现 §9 固定资源槽位与本仓库三登机门样例冲突, > 评审保留单库原子提交方向,但发现 §9 固定资源槽位与本仓库三登机门样例冲突,
> 并指出集合往返、迁移与快照 CAS 的风险。**§9 全宽表·零子表方案已按 v2 设计撤销**, > 并指出集合往返、迁移与快照 CAS 的风险。**§9 全宽表·零子表方案已按 v2 设计撤销**,
> 现行权威口径见 [§10 v2 落地](#10-acm2-29-v2-无损明细表方案落地2026-09-08)、 > 现行权威口径见 [§10 v2 落地](#10-acm2-29-v2-无损明细表方案落地2026-09-08)、
> [flight-state-design-v2.md](../flight-state-design-v2.md) 与 > 现行设计见 [flight-state.md](../flight-state.md);§1–§9 保留为历史记录。
> [flight-state-semantics.md](../flight-state-semantics.md);§1–§9 保留为历史记录。
## 1. 问题 ## 1. 问题
@@ -167,8 +166,8 @@ Oracle 11g 的 `MERGE INTO` 方言、Flyway 11.2 支持版本和 ojdbc 认证组
## 10. ACM2-29 v2 无损明细表方案落地(2026-09-08) ## 10. ACM2-29 v2 无损明细表方案落地(2026-09-08)
按 [评审报告](../review-flight-state-2026-09-08.md)F1F9)与 按 [评审报告](../flight-state.md)F1F9)与
[flight-state-design-v2.md](../flight-state-design-v2.md) 评审定案,撤销 §9「全宽表·零子表」 [flight-state.md](../flight-state.md) 评审定案,撤销 §9「全宽表·零子表」
作为权威存储形态,落地无损模型;本节为当前有效规则索引: 作为权威存储形态,落地无损模型;本节为当前有效规则索引:
- **存储**`FLIGHT_SCHD` 保留标量/异常前缀/无界文本列(V1.4.0 已 DROP 槽位、里程碑与紧凑 - **存储**`FLIGHT_SCHD` 保留标量/异常前缀/无界文本列(V1.4.0 已 DROP 槽位、里程碑与紧凑
@@ -183,4 +182,4 @@ Oracle 11g 的 `MERGE INTO` 方言、Flyway 11.2 支持版本和 ojdbc 认证组
- **读模型**:明细表为权威读;`flight_schd_display` 视图提供首/次资源与总数投影。 - **读模型**:明细表为权威读;`flight_schd_display` 视图提供首/次资源与总数投影。
- **双库**`SqlDialect` 接缝(PG 实装;`Oracle11gDialect` 为编译级交付), - **双库**`SqlDialect` 接缝(PG 实装;`Oracle11gDialect` 为编译级交付),
11g 激活门控见 `db/migration/oracle11g/README.md`——现场 11.2 实测通过前不切换。 11g 激活门控见 `db/migration/oracle11g/README.md`——现场 11.2 实测通过前不切换。
- **字段语义唯一索引**[flight-state-semantics.md](../flight-state-semantics.md)。 - **现行设计**[flight-state.md](../flight-state.md)。
-409
View File
@@ -1,409 +0,0 @@
# 运营航班状态设计评审与重构建议
> 历史评审:以下结论针对 cd5de56,不是当前状态。最新核查见
> [ACM2-29 完成度与简化计划](acm2-29-audit-and-simplification.md),现行设计见
> [航班状态设计](flight-state-design-v2.md)。保留原文用于追溯,不重复维护其阶段清单。
评审日期:2026-09-08。代码基线:`cd5de56`。评审对象:[decision-flight-state.md](decision-flight-state.md)、相关 SQL、仓储、快照流程、消息处理和测试。
本文件是有证据的设计评审和可实施提案,**不代表下述重构已经实装,也不自动取代 ACM2-28/29 的历史决策**。本次只新增评审文档及原决策文档的评审入口,没有修改生产代码、执行数据库迁移或更新 Plane 状态。
## 1. 结论与部署边界
**保留“自有关系数据库内原子提交航班状态、处理终态和 outbox”的架构;建议撤销“全宽表、零子表、超界丢弃”的硬性限制,改为航班标量主表、无损重复明细、按需展示视图。**
两项决策必须分开:选择一个事务内的权威存储,解决的是一致性;如何存放重复资源,解决的是信息保真和查询模型。前者正确,不足以证明后者正确。
本轮用户已明确:**现场提供 Oracle 时一定是 Oracle 11g;否则自行安装 PostgreSQL。** 因此实际产品边界是两种互斥部署配置,不能继续把 Oracle 当作遥远的假设,也不应让一个部署同时把 PG 和 Oracle 都作为动态状态权威。共享 MySQL 信箱边界保持原样。
评审建议分为三个层次:
1. 立即修正会丢数据或破坏事务不变量的实现与验收口径。
2. 用最小的领域结构承载 SIS 已有数据,完成一个 DNLD→资源增量→查询/通知的纵向闭环。
3. 分别验收 Oracle 11g 和 PostgreSQL 适配;完整 AODB、资源优化调度、票务和历史分析不纳入本次重构。
## 2. 当前决策的合理部分与证据不足部分
| 原设计 | 评审结论 | 理由 |
|---|---|---|
| 航班状态迁回自有库,与 `PROC_STATE``MSG_EVENT` 同事务 | 保留 | 消除原 Redis 状态与 PG 终态之间的跨存储提交窗口。 |
| Redis 退出状态权威 | 保留 | 本项目已依赖自有关系数据库,增加另一个状态权威不带来必要收益。 |
| Handler 纯函数,基础设施通过仓储隔离 | 保留 | 可以独立验证 KEEP/FIX 业务规则;物理表不应决定 Handler 的业务表达能力。 |
| 增量按字段合并,DNLD 全量替换 | 保留总体方向 | 需进一步区别字段未出现、显式清空、集合全量、操作记录增量。 |
| `FDAY` 域化差删、ADFT 不随旧代误删 | 保留 | `FDAY` 是快照所有权,不应冒充航班运营日期。 |
| `PIPELINE_LOCK` 行锁 | 有条件保留 | 是写事务互斥工具,还不是完整的双实例 FIFO 或主备协议。 |
| 全字段字符串保持 legacy 兼容 | 保留原始值,补类型化查询面 | 时间字符串不适合排序/范围查询;Oracle 空串语义也会影响保真。 |
| 固定 2/3/2/2/2 槽位,超界丢弃 | 否决作为权威表示 | 项目自带三登机门样例已能触发丢失,不只是 XSD 理论边界。 |
| 多次操作只留一次、按资源号去重 | 否决作为通用规则 | 分配记录与物理资源不是同一实体;操作序号和资源身份有业务含义。 |
| 零 CLOB、零子表、零方言成本 | 撤销硬性约束 | Oracle 11g 支持关系明细和 CLOB;串行事务与多表原子写入并不冲突。 |
| “重启即恢复,RPO=0” | 限定故障模型 | 本地事务关闭双写窗口,不等于数据库介质损坏、异步副本切换也零丢失。需单独定义备份、恢复与提交持久性要求。 |
| “单行 upsert 亚毫秒,无新增往返” | 改为待测目标 | 本轮未见支撑数据。当前增量路径实际包含父行 upsert 和字段 UPDATE,快照还涉及批量、差删、版本、事件。 |
文档也有演进失配:`decision-flight-state.md` §6/8/9 分别保留 JSONB、集合文本、固定槽位三个阶段,`design.md` §2 仍描述集合序列化文本;`architecture.md` 同时有行锁互斥与运行期排他尚未完成的说明。应为“当前有效规则”和“历史被替代规则”设置明确索引。
## 3. 互联网方案:哪些可以借鉴,哪些不能照搬
本次使用 TinyFish 搜索并读取一手资料。连接器未提供 `batch_create`,因此资料逐个 URL 读取,未使用异步浏览自动化。没有获得商业 AODB 的生产物理 DDL;不能把产品介绍或信息交换标准描述为厂商数据库表结构。
| 来源与性质 | 已核实内容 | 对本项目的适用性与限制 |
|---|---|---|
| IATA《AIDX Implementation Guide》v3.02014,基于 14.1 schema;行业信息模型 | 航段是核心结构;重复元素保留顺序;文档包括资源、共享航班、运行时刻和不正常运行身份。 | 用于检验模型是否丢失领域信息。它不是本项目 SIS 的替代协议,也不是最新版本认证。 |
| EUROCONTROL A-CDM Specification2025-01-30;运行流程模型 | 将进港与出港关联,通过里程碑推进周转和离港估计。 | 支持把进出港记录、周转关系和操作时刻分清;不要求本项目实现完整 A-CDM。 |
| SITA Airport Management;商业产品功能边界 | 区分航班管理、固定资源、移动资源、协同决策,并涉及多机场。 | 可借鉴职责分离;公开页不披露其物理表,不能推断“它采用某种子表”。 |
| Postgres Professional Airlines 演示库,版本 10 文档;公开关系 schema | `flights` 有独立 `flight_id`、计划/实际时刻和机场引用;通过 `flights_v` 提供附加展示信息。 | 是能阅读字段、键和约束的数据库示例,但侧重订票,不是机场生产 AODB。 |
| Oracle Database 11g SQL Reference;平台约束 | SQL `VARCHAR2` 有 4000 字节上限;字符长度为零按 NULL 处理。 | 必须影响长度、空值、测试和驱动适配,不能仅替换 upsert 语法。 |
来源:[IATA AIDX 指南](https://www.iata.org/contentassets/2db667ac37c045dbbb537eeb446dd9b2/aidx-xml-implementation-guide.pdf)、[EUROCONTROL 2025 A-CDM](https://www.eurocontrol.int/publication/eurocontrol-specification-airport-collaborative-decision-making-cdm)、[SITA Airport Management](https://www.sita.aero/solutions/sita-at-airports/sita-operations-at-airports/sita-airport-management/)、[Postgres Professional schema](https://postgrespro.com/docs/enterprise/10/apjs04.html)、[Oracle 11g 数据类型](https://docs.oracle.com/cd/E11882_01/server.112/e41084/sql_elements001.htm)、[Oracle 11g NULL](https://docs.oracle.com/cd/E11882_01/server.112/e41084/sql_elements005.htm)。
具体可借鉴之处:
- AIDX §3.1.9 要求重复数据保持可追踪的顺序,更新一个重复元素时携带其余元素;附录 H 中登机门可重复三次、值机可表达不同类型和不连续范围、延误原因可有多项。这足以反驳“两登机门、一延误是通用航空事实”,但本项目每个消息类型的最终行为仍应以 SIS 和现场协议为准。[IATA 指南](https://www.iata.org/contentassets/2db667ac37c045dbbb537eeb446dd9b2/aidx-xml-implementation-guide.pdf)
- 进港和出港关联有运营价值,但不应因为是同一架飞机就合成同一条记录;周转关系应显式表达,不能从航班号或机尾号临时猜测。[EUROCONTROL A-CDM](https://www.eurocontrol.int/publication/eurocontrol-specification-airport-collaborative-decision-making-cdm)
- 展示可以是视图,权威表不必复制屏幕布局。演示库的 `flights_v` 提供本地时刻及机场附加信息,展示了这种分离方式;其票务实体和简化自然键不应直接搬入 SIS 中间件。[Postgres Professional schema](https://postgrespro.com/docs/enterprise/10/apjs04.html)
以下表设计是结合这些资料和项目代码得出的**本次建议**,不是宣称行业标准强制规定了这些表名或范式。
## 4. 关键发现:契约保真
### F1 — 高:当前两个登机门槽位直接违反本仓库样例
证据:
- [SIS §3.34.3](legacy/SIS_AODB_RMS-V0.1.md#sec-3-34-3),源文件约 8321–8340 行,同时给出 `GTNO=1/G28``GTNO=2/G33``GTNO=3/G23`
- `JdbcPgRepositories.kt:367` 定义 GTDT 两槽位;`:466``flattenSlots` 使用 `.take(c.slots.size)`
- `decision-flight-state.md` §9 却称已通过该规范样例核验,并允许超界丢弃。
当前转换实际把三条变成两条,G23 消失。对于合法输入,系统仍可能提交 `SUCCEEDED`,不会因丢失而自动重试。这是权威存储的损失性转换,不只是展示不全。
**建议**:完整保存记录,界面需要两列时仅在视图投影前两项,并显示总数/更多信息。仅把上限从 2 改成 3 不能解决其他集合及将来的合法输入。
### F2 — 高:资源编号、分配身份和数组位置被混为一谈
`flattenSlots` 并不按 `GTNO/CKNO` 选槽,而是按去重后的数组顺序赋槽;`slotView` 再用下标合成序号。因此只输入 `GTNO=3`,读回就是 `GTNO=1`。这也与文档“槽位号即报文序号属性”不符。
同一函数对五类集合都按资源号去重。两个 `CHKC=01``CCLS=F/Y` 的分配会丢掉后一条;机位不同时间段、相同资源不同属性也存在同类风险。没有证据证明这些是可无条件抹平的冗余。
**建议**:分别保存 `ordinal`(报文出现顺序)、`source_seq`(源序号字符串)和资源号;资源号本身不设“每航班唯一”的约束。是否允许重复源序号应由协议矩阵决定,不能由 SQL 唯一键替上游作决定。
### F3 — 高:操作历史被不充分地压缩为单一里程碑
- SIS 约 51275157 行明确 ABTM 按每次靠桥/离桥重复,`ASNO` 标识操作序号,`ABDG` 标识实际桥资源。
- XSD `BRIDGEDATA`(约 1714 行)也包含 `ABDG/ABOP/AOTM/ASNO`
- `flattenAirbridge` 只取首个 A 和首个 D,读回时合成 1/2,完全不输出原条目的 `ABDG`
- `flattenChocks` 只保留首次 ON/OFF,丢掉 `CSNO/CHST``chocksView` 给两者都生成 `CSNO=1`
- `flattenDelay` 总取首条;XSD 的 DELY 为 `maxOccurs="unbounded"`(约 2251 行),不能从“一次只显示一条原因”推出只存一条。
**建议**:原始操作列表和“当前/首次/最近一次里程碑”分开。后者可以派生,但必须命名清楚,并定义按源序号、发生时刻还是到达顺序选择;现在的 `firstOrNull` 不具备“当前有效”的证明。
### F4 — 高:显式清空与缺失混用,异常对象处理又不一致
`FlightFields``Map<String,String>`,通过键是否存在、字符串中嵌套 JSON 表达不同含义。当前存在:
- GTDT 的 `[]`/`null` 会清空槽位,读回没有 GTDT 键;如果下游以“键缺失=保持旧值”消费,就不能代表清空。
- `flattenExceptions``FRET="null"`、空对象、非对象或非法 JSON 返回,不写清空列;旧异常可能保留。
- 异常缺失属性不清空,与文档“集合键出现即整体替换”的统称不一致。
- 自由文本在 `REMC/RSN/value/#text` 中猜一个键,读侧固定输出另一个键,无法承诺原格式往返。
`FRET="null"` 不产生清空写入已由转换诊断复现。是否在每一种真实 XML 清除消息中触发,还需 Codec/Handler 的实际映射;不能把诊断输入误称为已经上线的报文路径。
**建议**:消息解释层显式使用 `Unchanged / Clear / Set``Unchanged / Clear / Replace / 按协议定义的操作`。编解码层负责将 XML 删除标记翻译成操作;仓储不要靠字符串内容猜业务命令。
### F5 — 高:出站载荷与数据库当前态可能在同一次提交后不同
`SnapshotFlow.kt:99` 从未压缩的 `normalized` 构造事件;仓储在写入时截断/去重。三门快照由此可能产生“Kafka 有三门,数据库点查只有两门”。普通路径也分别接受 `flightChanges` 和已序列化 `schdPush`,一致性依赖 Handler 自觉。
事务原子性保证两者一起提交,**不保证它们表达同一状态**。
**建议**:统一生成完整的 `nextState`,无损持久化和线格式序列化都使用它。增加 `readAfterWrite == nextState``wirePayload == wire(nextState)` 两道断言;不要只测试事件数量与事务提交。
## 5. 关键发现:迁移、并发与恢复
### F6 — 高:V1.2.0 不是有数据的安全升级迁移
[V1.2.0 SQL](../src/main/resources/db/migration/V1.2.0__flight_resources.sql) 第 97–104 行直接删除 16 个旧集合列,前面只有 ADD COLUMN,没有数据转换或回填。若 V1.1.0 数据库有集合值,升级后旧值丢失,新列仍为 NULL。
空库首次安装不会出现这个损失;本轮未访问现场,不能声称现场已发生丢失。但当前脚本不具备带数据升级能力。
**建议**:扩展表结构→回填→逐字段核验→切换读写→观察→最后删旧列。已应用的 Flyway 版本不要直接改写;尚未升级但会经过 V1.2.0 的实例,必须在它执行前做无损导出或另行制定受控升级路线。后续新增迁移无法凭空恢复已被删除的数据。
### F7 — 高:CAS 失败后版本相同,不能证明是同一个快照重放
`SnapshotFlow.kt:59` 在锁外读取 generation`:69` 先覆盖数据,`:8287` 在 CAS 失败时只要 `again.version == expected + 1` 就继续成功。
可由代码推导的交错:
1. 处理者 A、B 在锁外都读到版本 5,但携带不同快照。
2. A 拿锁,写入集合 A,把 generation 推到 6 并提交。
3. B 随后拿锁,按旧预期写入集合 B 和旧差集。
4. B 的 CAS 失败,看到版本恰为 6,于是继续提交事件和 SUCCEEDED。
5. generation 成员仍可能是 A,航班内容却被 B 改写;这不是 no-op。
生产严格只有一个活动处理者时,此交错不会自然出现;它揭示的是文档宣称的交叉实例保护不能成立,也不能以“已有行锁”作为开放多实例的依据。本轮未做真实双连接复现。
现有 `FlightSchdInvariantTest:223` 的 mock 在 CAS 失败后仍返回版本 5,没有覆盖恰好为 6 的分支。
**建议**:锁内读 generation 和有效处理身份;CAS 失败统一回滚。若需要重放短路,必须在任何状态写入前,使用已成功的消息身份/明确的快照身份识别,同内容摘要只能作为补充,不能仅凭版本号。为 generation 增加 `last_snapshot_message_id`(或同等可核验身份)与可选摘要,保留版本只作为顺序令牌。
### F8 — 高(双实例场景):行锁没有覆盖“读状态→决策”
`Pump.kt``txManager.inTransaction` 之前读航班视图并调用 Handler。两实例可以基于同一旧状态分别计算,然后串行覆盖。`PIPELINE_LOCK` 也不自动保证较小信箱 ID 的处理者先取得锁。
**建议**:继续单活动实例部署。要增强防错,应在锁内复核处理终态与队头、读取当前态、应用纯决策并提交;XML 解析等不依赖当前态的工作可在锁外。切勿使用 `SKIP LOCKED` 跳过较小业务消息破坏严格 FIFO。数据库隔离级别也不能替代处理身份与顺序协议。
### F9 — 高(提交后异常):回填失败可能落回处理失败路径
`Pump.kt:235``backfillOnSuccess` 位于状态事务提交之后;异常会被外层 `processOne` 归为 INFRA 并交给 `procFailure`。快照回填也处于提交后。若再次进入完整处理流程,有重复事件、重复快照推进或覆盖新状态的风险。
**建议**:成功终态不可被回填错误降回业务失败。单独持久化回填待办,失败只重试回填;业务重试入口复核数据库中已提交终态。恢复能力应围绕“事务提交前/后”和“外部投递前/后”分别测试。
## 6. Oracle 11g 兼容性:不是把 ON CONFLICT 换成 MERGE
| 维度 | 当前风险 | 设计要求 |
|---|---|---|
| 空串 | PG 可保存空字符串,Oracle 字符零长度按 NULL`Map` 的键存在性也会变化。 | 对有业务意义的空值保留 presence 标记或无损文档;不要未经协议认可全局将空串、NULL、缺失视为同义。[Oracle NULL](https://docs.oracle.com/cd/E11882_01/server.112/e41084/sql_elements005.htm) |
| 长度 | 现有 `*_TEXT VARCHAR(512/2000)` 是序列化后的整列表;XSD 不限条数的列表可合法超长。 | 明确 BYTE/CHAR 与数据库字符集;11g SQL VARCHAR2 仍受 4000 字节边界,使用子表或 CLOB 承接无界集合。[Oracle 数据类型](https://docs.oracle.com/cd/E11882_01/server.112/e41084/sql_elements001.htm) |
| 时间 | `SODT/ESTT/ACTT` 是源字符串;数据库审计时间使用另一套类型。 | 区分业务发生时刻、收到时刻、提交时刻;原值和标准化时间并存。 |
| DDL | PG 迁移含 `BIGSERIAL``TEXT``ADD COLUMN` 等,整个辅助管道也需适配。 | 分 PG/Oracle migration location;定义 NUMBER、序列、长文本、时间与约束的映射。 |
| DML | 不只有 upsert,还包括生成键、分页、锁、批量和查询日期处理。 | 方言留在适配层,业务层不按数据库类型分支。 |
| 事务 | 行锁概念可共用,连接绑定和失效恢复仍须实测。 | 明确同一事务同一数据源;`JdbcOps` 当前 ThreadLocal 未按数据源区分,未来多个自有连接源不得误复用。 |
| 运行组合 | 当前依赖有 PG 驱动与 PG Flyway 模块,没有可据此验收的 Oracle 组合。 | 在目标 11.2 补丁级别上验证 JDK 25、具体 JDBC 驱动、迁移工具和连接池组合。不能凭“ojdbc8”名称认定支持。 |
11g 缺少现代 SQL/JSON 能力,不等于不能存储 JSON 文本。当前 V1.2.0 自己还把 SRVT/VIPF/MAFL 保存为 JSON 数组字符串;“废除 JSON 存储”因此也不是准确描述。真正需要选择的是:哪些字段要求 SQL 查询/约束,哪些原始结构只需无损保存。
超长 fail fast 优于静默截断,但如果合法消息仅因本地任意长度限制进入反复重试,严格 FIFO 会被持续阻塞,最终 DEAD 后仍缺失有效更新。容量问题应在投产前按契约和观测量解决;永久验证错误与暂态基础设施错误也应分开分类。
## 7. 建议的领域边界
本项目是外部 AODB 报文的有状态接收与分发系统。这里的“权威”指**本系统已处理消息所形成的当前态及下游输出**,不表示取得上游 AODB 的业务编辑权。
| 概念 | 建议定义 | 不要混用 |
|---|---|---|
| 航班记录 | 当前源系统用 FLID 标识的进/离港运营记录 | 航线模板、每天重复的航班号、航空器、旅客行程 |
| 航段/周转关系 | 对现有源关系作明确映射,需要时关联两个记录 | 不能先假定本地 FLID 与 AIDX 航段完全同义,也不能仅按机尾号自动配对 |
| 资源分配记录 | 某航班某一序号的门/柜台/转盘/计划机位等分配 | 资源主数据;相同资源号不一定是同一分配 |
| 操作记录 | 一次靠桥、撤桥、上/下轮挡等源记录 | 当前资源、首次/最近里程碑 |
| 运营日期 | 按明确机场时区及业务口径定义的日期 | `FDAY` 快照归属日;不要直接改现有主键 |
| 消息身份 | 源消息去重与处理恢复标识 | 快照版本、FLID、发生时刻 |
| 当前态、历史、展示 | 当前态由消息合并;历史单独留痕;展示可派生 | outbox 的合并通知不是完整运行事件历史 |
现有单机场/单 AODB 数据域可继续以 FLID 作主键。双流/天府若以后汇入一个实例,先核实跨机场和跨源 FLID 唯一性;只有证实会冲突才引入 `(source_system, airport_scope, external_flid)` 唯一映射及内部 ID。当前没有证据证明已经发生碰撞,不把这个潜在需求扩大为立即全表换键。
## 8. 推荐关系模型
### 8.1 主表与明细的分工
```mermaid
erDiagram
FLIGHT_SCHD ||--o{ FLIGHT_GATE : contains
FLIGHT_SCHD ||--o{ FLIGHT_CHECKIN : contains
FLIGHT_SCHD ||--o{ FLIGHT_BELT : contains
FLIGHT_SCHD ||--o{ FLIGHT_BRIDGE_OP : records
FLIGHT_SCHD ||--o{ FLIGHT_CHOCK_OP : records
FLIGHT_SCHD ||--o{ FLIGHT_ROUTE_POINT : contains
SCHD_GEN ||--o{ SCHD_GEN_FLID : owns
```
图只列主要关系;计划机位、通道、延误的明细见下表。`SCHD_GEN_FLID` 是代成员历史元数据,不强行对当前航班加级联外键,避免清场或跨代迁移改变既有差删语义。
`FLIGHT_SCHD` 保留 FLID、FDAY、标量业务字段、created/updated 时间。新增字段按实际查询和恢复需求选择:
- `LAST_MESSAGE_ID`:追踪最后影响该航班的消息,关联审计,不代替业务去重。
- `STATE_VERSION`:需要一致查询或恢复校验时使用;每次有效变更推进,不因无副作用重放推进。
- `SCHEDULED_AT` 等类型化时间:从原始 SODT 等派生,保留原值。
- presence/解析状态:只在需区分空/缺失或解析失败的字段上引入,不滥加全表状态。
### 8.2 16 类结构的建议映射
| 现有集合 | 建议存储 | 必须保留的关键含义 |
|---|---|---|
| GTDT | `FLIGHT_GATE` | ordinal、GTNO、GATE、PGOT/PGCT/GOTM/GCTM、GTYP |
| CKDT | `FLIGHT_CHECKIN` | ordinal、CKNO、CHKC、CCLS、计划/实际开放关闭、CTYP;不按 CHKC 去重 |
| CLDT | `FLIGHT_BELT` | ordinal、CLNO、BELT、BCLS、计划时刻、首末行李时刻、BTYP |
| PSDT | `FLIGHT_STAND_PLAN` | ordinal、PSNO、PSST、开始/结束时刻 |
| CHDT | `FLIGHT_CHUTE` | ordinal、CHNO、CHUT、等级、计划/实际时刻、类型 |
| DELY | `FLIGHT_DELAY` | ordinal、CODE、STRT、DURA、原文说明;不以“唯一当前原因”覆盖列表 |
| ABTM | `FLIGHT_BRIDGE_OP` | ordinal、ASNO、ABDG、ABOP、AOTM |
| CHOT | `FLIGHT_CHOCK_OP` | ordinal、CSNO、CHST、CHID、CHTM |
| ROUT、ERUT | 共用 `FLIGHT_ROUTE_POINT`,区分 route_kind | ordinal、原 RTNO、APCD、SCAT、SCDT,避免自定义分隔符和重编号 |
| FDIV、FRET、FLAB | 主表前缀列可保留 | 单值符合 XSD;明确文本键、空值、清除规则即可,不必强拆子表 |
| SRVT、VIPF、MAFL | 第一阶段允许无损文本旁表;查询需求明确后再结构化 | JSON 数组原有顺序、全部属性和长度;Oracle 使用 CLOB,PG 使用 TEXT;应用验证结构 |
低频集合旁表可定义为 `FLIGHT_COLLECTION(FLID, COLLECTION_KIND, PAYLOAD, SCHEMA_VERSION)`,主键是 `(FLID, COLLECTION_KIND)`。这不是用于任意核心字段的 EAV 表,只承接暂未需要 SQL 过滤的完整集合。MAFL 的共享航班关系一旦用于关联查询,应提升为明确关系,而不是在 SQL 中解析文本。
明细表数量由独立属性和读写语义决定,既不追求“零子表”,也不要求所有 16 类各建一张。1:N 表只存实际 N 行;支持 XSD 上限并不等于每航班预建 99 行。
### 8.3 单个明细表的可评审形状
以下是逻辑字段设计,非可直接运行的双数据库 DDL:
```text
FLIGHT_GATE
FLID FK -> FLIGHT_SCHD
ORDINAL positive integer; input order
SOURCE_SEQ source GTNO string
GATE_CODE source GATE
PLANNED_OPEN_RAW source PGOT
PLANNED_CLOSE_RAW source PGCT
ACTUAL_OPEN_RAW source GOTM
ACTUAL_CLOSE_RAW source GCTM
RESOURCE_TYPE source GTYP
[typed timestamps / presence data as required]
PK (FLID, ORDINAL)
INDEX (GATE_CODE, FLID) when resource lookup is required
```
`SOURCE_SEQ` 不强转整数,以免丢掉契约/legacy 需要保留的形式。`ORDINAL` 是当前集合内部位置,不承诺跨版本稳定身份。若某报文是按序号逐项变更,必须由其业务规则使用源序号定位,并单独验证重复序号。
子表 FK 可随主表删除级联,以保证删除完整;对资源主数据的引用建议先做软校验和未知代码可观测性,避免参考数据刷新滞后使整个 FIFO 卡住。引用完整性是否升级硬约束需结合上游保证。
### 8.4 标量类型与时区
SIS 的时间示例是 `DDMONYYHHMM` 当地时间(例如 GTDT 的定义),不能因为 AIDX 使用 UTC 就直接给 SIS 字符串加 Z。应使用明确的 `Asia/Shanghai` 和英文月份解析,定义两位年份的世纪窗口,保留原字符串;测试跨月、跨年、午夜和非法值。其他机场接入时必须重新绑定时区。
高频查询使用标准化时间列;原始 SODT 字符串的字典序不等于时间序。PG 使用相应时区时间类型,Oracle 使用经过验证的 TIMESTAMP 类型与 JDBC 绑定;保留机场时区配置,不能认为数据库保存了瞬间就保存了源业务时区。
FTSS、取消、异常、资源操作里程碑也不要合成一个强制单向状态机。来自外部的更正消息可以修正历史值,哪些允许更正由 KEEP/FIX 矩阵决定。
## 9. 写入与消息语义
建议将模块边界调整为:
```text
SIS XML -> 契约解码与校验 -> 类型明确的命令
|
当前态 + 纯 Handler -> nextState / 通知意图
|
同一自有库事务:主表 + 明细 + generation + outbox + SUCCEEDED
|
提交后回填补偿 / 至少一次投递
```
### 9.1 明确操作,不让仓储推测
| 输入情况 | 标量动作 | 集合动作 |
|---|---|---|
| 字段/集合未出现 | 不修改 | 不修改 |
| 合法值出现 | Set(value) | 对被确认是集合快照的类型,Replace(完整列表) |
| 明确删除标记 | Clear | Clear,按线协议输出明确的清除表达 |
| 操作型消息 | 按该类型规则 | 按序号更新、追加或替换由逐类型契约决定 |
| 非法对象、未知属性、超容量 | 可诊断失败 | 禁止成功后悄悄丢弃;是否可扩展接收由版本策略决定 |
GTDT/CKDT 的“0=全部未分配”在本地 SIS 文档有依据;不能直接推广为所有类型的序号 0 都有同样含义。ABTM/CHOT 尤其需要确认报文携带操作全集还是本次变化。AIDX 的重复元素约定可帮助设计,但不能替代 SIS 实测。
`Clear` 后数据库与 wire 是否输出 `[]``null` 或协议专属标记,必须按下游契约固定;本轮不擅自改变 Kafka 线格式。
### 9.2 保持原子性与 FIFO 的提交顺序
1. 锁外解析并验证完整消息,生成不依赖当前态的命令;快照整包校验重复 FLID、日域和集合边界。
2. 开自有库事务,取得 writer 行锁。
3. 锁内复核队头与消息终态;已成功的同一消息直接结束业务应用,不增加版本/事件。
4. 读取当前航班与 generation,调用纯函数计算下一状态。
5. 写主表和受影响的明细;快照进行 FDAY 域化差删、版本和成员推进。CAS 失败即回滚。
6. 从同一个完整状态产生 outbox;记录必要身份和版本;置 SUCCEEDED。
7. 提交后独立执行回填/投递;其失败不回退业务终态。
集合替换可在同一事务内 `DELETE WHERE FLID=?` 后 JDBC batch INSERT。小集合无需先实现复杂行级 diff;其他连接只能看到已提交状态,但多条 SELECT 的组装仍需一致读取策略。
DNLD 删除某航班后,下游如何得知删除也需要验收:显式删除通知、整批替换标记或重新拉取必须选定一种。当前 `SnapshotFlow` 只为新集合构造 schd 事件,不能只验收数据库少了一行。
## 10. 查询与展示:避免把屏幕列数写成业务上限
项目当前没有前端,现有输出是 `/all/flights` 与 Kafka。所谓运营“显示”应落实为它们的读模型,而非凭空增加 UI。
兼容 API 可以继续提供原来的 FLTR/字段结构,仓储聚合主表和明细。DBA 需要首个/第二个登机门时,提供 `FLIGHT_SCHD_DISPLAY` 查询视图,额外给出 `GATE_COUNT`,并保留完整明细查询。主表无需新增任意数量的 `GATE3...99`
实现时注意:
- 不把多个 1:N 表直接 JOIN 成一个巨大的结果;三个门乘四个柜台再乘两个转盘会重复行。
- 主表分页后,按 FLID 批量读取各类明细并组装,避免每航班 N+1 查询。
- 父子表分多次 SELECT 时使用同一一致快照,或读取版本并重试;否则可能混合更新前的主表与更新后的明细。PG/Oracle 分别验收隔离行为。
- `MAID` 过滤保留现有共享航班查询契约。共享记录、实际承运信息和营销航班关系不要合并成单一文本标签。
- 资源反查索引通常从 `(resource_code, flid)` 出发;航班列表按实际日期/进离港筛选建立组合索引。用真实查询计划确定,不是把所有宽表列都加 B-tree。
- `/all/flights` 的兼容全量接口先保留;需要新增分页接口时另定 API 契约,不能悄悄把原接口改成只返回第一页。
性能验收应测报文峰值、最大 DNLD、每类集合 p95/p99/最大值、事务 p95/p99、锁等待、提交日志量、查询组装成本和消息积压。只有生产样本分布,不能证明业务硬上限;同时要有协议边界测试。
## 11. 分阶段迁移与回退
| 阶段 | 具体工作 | 完成证据 |
|---|---|---|
| P0:契约和阻断项 | 建立 16 类集合的缺失/清空/替换/操作矩阵;修 CAS、提交后回填边界;恢复完整集合承载能力。 | 三门、多舱柜台、多次桥/轮挡、异常清除、两快照冲突都通过。 |
| P1:无损扩展 | 增加明细/过渡旁表;保留现有展示列;建立版本和来源追踪;增量写同库同事务。 | 新旧读取与源完整状态对比,所有合法输入无静默损失。 |
| P2:数据回填与切换 | 按已部署版本选择回填来源,完整校验后切读;保留旧结构一个回退窗口。 | 数量、属性、顺序、空值、状态及 outbox 载荷核对;无未解释偏差。 |
| P3:双数据库验收 | 分别提供 PG/Oracle DDL/DML、迁移与集成环境;业务契约用同一组测试。 | 两套隔离环境均通过;11g 驱动/JDK/迁移组合已实际验证。 |
| P4:查询优化与收尾 | 按负载加索引/展示视图,移除确认不再使用的损失性列与转换函数。 | 查询计划和时延达标;回退窗口关闭、数据备份可恢复。 |
不同安装状态必须区别处理:
- **空库**:可新建正确结构,但仍需通过升级链的空库安装测试。
- **V1.1.0 有数据、尚未到 V1.2.0**:必须在 DROP 之前保存完整集合;由原 `*_TXT` 回填并对拍。
- **V1.2.0 已运行**:固定槽位里缺失的第三个门、源序号等不可从现表推算回来。需要旧库备份、完整历史输入或可信的完整快照。新的 DNLD 只能恢复它包含的当前态,不能保证恢复全部历史操作。
- **回放恢复**:进入隔离库、隔离投递目标;先重建并核对水位,再受控切换。不能把旧消息直接灌回实时 FIFO 覆盖新状态。
PG→Oracle 切换不做两个权威库之间的实时应用双写。以停写水位、待投递事件状态、完整导出、目标导入对拍和单活动实例切换为基本路线;具体停机窗口与回退点待部署资料确定。
## 12. 验证结果与还缺什么
### 12.1 本轮已执行
使用 JDK 25 执行:
```sh
GRADLE_USER_HOME=/home/windy/project/msgexchange-v2/.gradle-home \
TMPDIR=/home/windy/project/msgexchange-v2/.gradletmp \
./gradlew test \
--tests '*FlightSchdInvariantTest' \
--tests '*FlightStoreDiffToolTest' \
--tests '*FlightFieldsJsonTest'
```
结果:**13 tests0 failures0 errors0 skipped**。分别为 6 个状态不变量测试、5 个对拍测试、2 个序列化测试。初次沙箱运行因 Gradle 本机网络绑定失败;离线重试缺依赖;授权的在线运行成功。编译存在既有弃用/注解警告,没有据此修改依赖。
另以临时 Java 诊断直接调用本次编译产物中的 `flattenForStorage``mapFlightRow`。DataSource 代理禁止任何数据库调用,ResultSet 为内存字段映射;这是转换级诊断,不是 JDBC 集成测试。复现结果:
| 输入 | 实际观察 |
|---|---|
| 三条 GTDTG28/G33/G23 | 读回两条,G23 丢失 |
| 单条 GTNO=3 | 读回 GTNO=1 |
| 同 CHKC 的 F/Y 两条分配 | 读回一条 |
| 两次靠桥、不同 ASNO/ABDG | 只余一次,原桥号和源序号消失 |
| GTDT=[] | 读回没有 GTDT 键 |
| FRET=null | 没有生成任何清空列 |
| DELY 两条原因 | 读回首条 |
这解释了为什么既有测试全绿不足以支持“100% 保真”:相关测试未覆盖上述反例,stub 也没有模拟 JDBC 的损失性表示。
### 12.2 尚未执行及原因
- 未执行全部测试及现有 `FlightSchdJdbcPgTest`。其 `setUp` 固定连接本机 `msgx``cleanup:70` 删除 `TEST_%` 航班及 `2026-09-01` 以后的 generation;未确认隔离性,不应在设计评审中直接运行该清场逻辑。
- 该 JDBC 测试在连接不可用时会 `assumeTrue` 跳过,当前构建没有以 Testcontainers 提供隔离 PG 的证据;因此文档中的集成验收措辞不能只看总任务绿色。
- 未连接 Oracle 11g;没有驱动/JDK/迁移工具兼容验收,没有带真实数据迁移、跨连接并发和恢复性能结果。
- 没有现场真实流量分布或双机场 FLID 唯一性材料。文档声称的现场规律与本轮可核验事实分开记录。
### 12.3 实施时必需的回归矩阵
| 类别 | 至少覆盖 |
|---|---|
| 契约往返 | 三门、99 条允许集合、重复资源不同舱位、非连续源序号、顺序、每个可选属性、中文长文本 |
| 空值 | 未出现、空串、NULL、空数组、0 删除标记、异常对象清除;PG 与 Oracle 同语义 |
| 操作 | 多次靠撤桥/轮挡、更正原操作、不同桥/机位、多个延误、路线空属性 |
| 快照 | 同 FLID 重复、跨日归属、ADFT 存活、空快照、非法快照整包拒绝、响应匹配 |
| 事务 | 任意明细/outbox 写失败全回滚;generation 内容与版本一致;不同消息争同一下一版本必须冲突 |
| 恢复 | 提交成功后回填失败不重做业务;重放不加版本和事件;下游已接收可去重 |
| 多连接 | 锁等待、断连释放、读后写冲突、不能跳过 FIFO;不能只用内存 mock 证明 |
| 迁移 | 有数据 V1.1→目标、V1.2 损失检查、故障恢复、回退窗口、两数据库新建/升级 |
| 查询/投递 | 父子状态一致读取、全量接口保兼容、无 N+1/笛卡尔积、删除可见、DB 与 wire 等价 |
对拍工具继续保留严格默认:不通过忽略 GTNO、忽略第三个资源、忽略 ABDG 等方式把有损转换“洗绿”。任何合法偏离必须有业务依据、明确范围和黄金样例。
## 13. Plane workflow 对齐与后续落点
已按 `plane-workflow` 读取 ACM2-28 与 ACM2-29,确认属于 ACM2 项目;并搜索已有航班主题工作项。没有新建任务、修改状态或发表评论。
- **ACM2-28** 的单库权威决策可以保留;RPO、完成清单和当前平台措辞应按本评审限定。
- **ACM2-29** 直接覆盖本次问题,但其中“全系统零子表”“100% 吻合”“零方言包袱”等验收标准需要修订为可测的不变量。
- 建议将后续工作拆为:契约保真与安全存储、快照/回填事务修正、有数据迁移、Oracle 11g 适配、读模型与查询验收。工作项依赖应在获授权写入时建立 Plane relation,不靠标题排序替代依赖。
- 本轮用户已明确的部署条件应写入后续平台方案,不再重复询问是否可能是 Oracle 12c/19c;仍需的是实际 11.2 补丁、字符集、权限和可测试环境。
**评审意见:ACM2-28 的一致性方向通过;ACM2-29 的损失性宽表方案不建议作为生产权威形态验收。优先完成无损表示和事务恢复,再扩展批量 Handler。**
+1 -1
View File
@@ -10,7 +10,7 @@
- 所有内部迁移只落自有 PG;共享 MySQL 不建表、不增列、不写历史表。本文用 `DATE_PROCESSED / STATUS` 表示逻辑字段,实际列名以库方契约为准。 - 所有内部迁移只落自有 PG;共享 MySQL 不建表、不增列、不写历史表。本文用 `DATE_PROCESSED / STATUS` 表示逻辑字段,实际列名以库方契约为准。
- 验收条目可按 `US-xx/条目号` 引用。故事较大时按下文子范围拆成小 PR,不把一个故事等同于一个提交。 - 验收条目可按 `US-xx/条目号` 引用。故事较大时按下文子范围拆成小 PR,不把一个故事等同于一个提交。
**存储基线**[单库权威决策](decision-flight-state.md) 已采纳:当前 PG 主表与明细表是状态权威,Redis 不参与动态写路径。Oracle 11g 是尚待完整适配的部署目标。本文保留验收目标,完成度以 [ACM2-29 核查](acm2-29-audit-and-simplification.md) 的证据为准 **存储基线**[单库权威决策](decision-flight-state.md) 已采纳:当前 PG 主表与明细表是状态权威,Redis 不参与动态写路径。Oracle 11g 是尚待完整适配的部署目标。航班状态设计和当前缺口见 [flight-state.md](flight-state.md)
## 2. 建议实施顺序 ## 2. 建议实施顺序
@@ -1,7 +1,7 @@
package com.gzzn.omms.msgexchange.domain.flight package com.gzzn.omms.msgexchange.domain.flight
/** /**
* 显式字段命令flight-state-design-v2 §4 * 显式字段命令docs/flight-state.md §4
*/ */
sealed interface ScalarCommand { sealed interface ScalarCommand {
data object Unchanged : ScalarCommand data object Unchanged : ScalarCommand
@@ -14,7 +14,7 @@ package com.gzzn.omms.msgexchange.infra.persistence.dialect
* Oracle 11.2 补丁级别 × JDK 25 × ojdbc 驱动 × Flywaydb/migration/oracle11g location * Oracle 11.2 补丁级别 × JDK 25 × ojdbc 驱动 × Flywaydb/migration/oracle11g location
* × 连接池组合必须在现场 11g 实测通过后才允许把生产方言切到 [Oracle11gDialect] * × 连接池组合必须在现场 11g 实测通过后才允许把生产方言切到 [Oracle11gDialect]
* MERGE 绑定顺序适配CLOBSRVT/VIPF/MAFL_TEXT与空串=NULL 语义回归一并纳入激活清单 * MERGE 绑定顺序适配CLOBSRVT/VIPF/MAFL_TEXT与空串=NULL 语义回归一并纳入激活清单
* 验收证据要求见 docs/flight-state-design-v2.md §6/§8 * 验收证据要求见 docs/flight-state.md §8
*/ */
interface SqlDialect { interface SqlDialect {
/** 航班主行快照 upsert:按 SCALAR_WRITE_COLUMNS 生成整体替换 SQL。 */ /** 航班主行快照 upsert:按 SCALAR_WRITE_COLUMNS 生成整体替换 SQL。 */
@@ -5,7 +5,7 @@ import java.time.Instant
import javax.sql.DataSource import javax.sql.DataSource
/** /**
* v2 明细表读写flight-state-design-v2 §3.2 * v2 明细表读写docs/flight-state.md §3.2
* 主表保存标量明细表保存重复集合不存在旧槽位读写回退 * 主表保存标量明细表保存重复集合不存在旧槽位读写回退
*/ */
internal object FlightDetailTables { internal object FlightDetailTables {
@@ -1,4 +1,4 @@
-- flight-state-design-v2 (ACM2-29): lossless detail tables + tracking columns -- docs/flight-state.md (ACM2-29): lossless detail tables + tracking columns
ALTER TABLE flight_schd ALTER TABLE flight_schd
ADD COLUMN IF NOT EXISTS last_message_id VARCHAR(64), ADD COLUMN IF NOT EXISTS last_message_id VARCHAR(64),
@@ -1,4 +1,4 @@
-- flight-state-design-v2 §8 (ACM2-29 P2-2): compatibility read view. -- docs/flight-state.md §8 (ACM2-29 P2-2): compatibility read view.
-- 提供「首个/第二个资源 + 总数」的运营查询投影;完整明细仍在各明细表可查。 -- 提供「首个/第二个资源 + 总数」的运营查询投影;完整明细仍在各明细表可查。
-- PG 专属语法(LATERAL);Oracle 11g 方言随 P3-B 单独提供 location。 -- PG 专属语法(LATERAL);Oracle 11g 方言随 P3-B 单独提供 location。
@@ -1,4 +1,4 @@
-- flight-state-design-v2 §5 (ACM2-29 P2-3): 提交后共享信箱回填的持久补偿待办。 -- docs/flight-state.md §5 (ACM2-29 P2-3): 提交后共享信箱回填的持久补偿待办。
-- 业务事务已提交(PROC_STATE=SUCCEEDED)后回填失败 → 落本表重试; -- 业务事务已提交(PROC_STATE=SUCCEEDED)后回填失败 → 落本表重试;
-- 回填失败不得把已成功的业务事务重新标记为失败,也不得重放业务变更。 -- 回填失败不得把已成功的业务事务重新标记为失败,也不得重放业务变更。
@@ -1,4 +1,4 @@
-- flight-state-design-v2 §7 步骤 5 (ACM2-29 P4): 旧损失性结构退场。 -- docs/flight-state.md §7 步骤 5 (ACM2-29 P4): 旧损失性结构退场。
-- v2 无损明细表已是唯一权威存储(V1.3.0 起),槽位/里程碑/紧凑航路列自 V1.3.0 起零写入、 -- v2 无损明细表已是唯一权威存储(V1.3.0 起),槽位/里程碑/紧凑航路列自 V1.3.0 起零写入、
-- P2-2 起零读取;对拍(FS7 v2 双读 + 全结构回环矩阵)与升级链验证通过后关闭回退窗口, -- P2-2 起零读取;对拍(FS7 v2 双读 + 全结构回环矩阵)与升级链验证通过后关闭回退窗口,
-- 本迁移物理删除这些列与其冗余索引。SRVT/VIPF/MAFL_TEXT 与异常前缀列为无损承载,保留。 -- 本迁移物理删除这些列与其冗余索引。SRVT/VIPF/MAFL_TEXT 与异常前缀列为无损承载,保留。
@@ -7,7 +7,7 @@ Flyway 配置**PG 路径使用 `classpath:db/migration`,两者互不混用
1. 现场 11.2 补丁级别、数据库字符集、DBA 权限清单拿到,且可提供可测试的目标库。 1. 现场 11.2 补丁级别、数据库字符集、DBA 权限清单拿到,且可提供可测试的目标库。
2. JDK 25 × ojdbc 驱动(具体版本)× Flyway Oracle 支持 × 连接池组合在目标库实测通过 2. JDK 25 × ojdbc 驱动(具体版本)× Flyway Oracle 支持 × 连接池组合在目标库实测通过
——不能以"PG 通过"代替 Oracle 验收(docs/flight-state-design-v2.md §6)。 ——不能以"PG 通过"代替 Oracle 验收(docs/flight-state.md)。
3. `SqlDialect` 切到 `Oracle11gDialect` 前,MERGE 绑定顺序适配完成并通过 3. `SqlDialect` 切到 `Oracle11gDialect` 前,MERGE 绑定顺序适配完成并通过
`FlightSchdJdbcPgTest` 同等粒度的 11g 集成测试。 `FlightSchdJdbcPgTest` 同等粒度的 11g 集成测试。
@@ -19,6 +19,6 @@ Flyway 配置**PG 路径使用 `classpath:db/migration`,两者互不混用
- `SRVT_TEXT/VIPF_TEXT/MAFL_TEXT` 映射 `CLOB`PG 为 TEXT)。 - `SRVT_TEXT/VIPF_TEXT/MAFL_TEXT` 映射 `CLOB`PG 为 TEXT)。
- `flight_schd_display` 11g 版视图(无 LATERAL,用标量子查询)。 - `flight_schd_display` 11g 版视图(无 LATERAL,用标量子查询)。
- 空串按 NULL 的语义回归:命令层 presence 信息不得被 11g 空串语义吞掉 - 空串按 NULL 的语义回归:命令层 presence 信息不得被 11g 空串语义吞掉
docs/flight-state-semantics.md §5)。 docs/flight-state.md §5)。
版本号与 PG location 各自独立推进,禁止复用版本号语义。 版本号与 PG location 各自独立推进,禁止复用版本号语义。
@@ -72,7 +72,7 @@ class FlywayMigrationTest {
/** /**
* P2-4带数据的 V1.1.0 最新版本升级链验证隔离 schema不污染 public * P2-4带数据的 V1.1.0 最新版本升级链验证隔离 schema不污染 public
* V1.2.0 删除 *_TXT 集合列是已知损失边界评审 F6标量列与结构完整存活 * V1.2.0 删除 *_TXT 集合列是已知损失边界评审 F6标量列与结构完整存活
* 集合文本不可恢复升级前必须完整导出或重放docs/flight-state-design-v2.md §7 * 集合文本不可恢复升级前必须完整导出或重放docs/flight-state.md
*/ */
@Test @Test
fun `upgrade chain from seeded V1_1_0 data keeps scalars and adds v2 tracking`() { fun `upgrade chain from seeded V1_1_0 data keeps scalars and adds v2 tracking`() {