Files
msgexchange-v2/docs/flight-state.md
T

112 lines
7.7 KiB
Markdown
Raw Normal View History

# 运营航班状态设计
本文是航班状态的唯一现行设计规范。它定义权威数据、处理语义、生命周期和对外投递;其他文档只描述管道或产品验收,不重复定义航班状态规则。
2026-09-09 11:43:34 +08:00
## 1. 目标与边界
系统从共享 MySQL 信箱接收 SIS/AODB 报文,把结果合并到自有 PostgreSQL 中的航班当前态,再通过 outbox 投递 Kafka。共享信箱和 Kafka 都不是状态权威,也不参与本地事务的分布式提交。
- `FLID` 是航班实例的唯一标识;不得由航班号、日期或资源号推断身份。
- `FLIGHT_SCHD` 及其明细表是唯一权威当前态;展示视图只读,不能作为写入或对账来源。
- 单活动主泵按信箱 FIFO 推进。事务内 `PIPELINE_LOCK` 只串行化本地状态提交,不替代选主或消息认领。
- 状态写入、outbox 事件、处理终态和回填待办在同一 PostgreSQL 事务中提交;回填与 Kafka 投递在提交后独立重试。
现场目标库为 Oracle 11g;当前已验证的实现基准是 PostgreSQL。Oracle 适配未完成前,不能把它视为可切换的运行时选项。
## 2. 权威模型
| 对象 | 职责 |
|---|---|
| `FLIGHT_SCHD` | 一行一个 `FLID`,保存标量字段、`STATE``STATE_VERSION``OPERATION_DAY`、最近消息 ID 和审计时间。 |
| 8 张资源明细表 | 登机门、值机柜台、转盘、计划机位、滑槽、延误、靠撤桥、轮挡等变长集合;主键为 `(FLID, ORDINAL)`。 |
| `FLIGHT_ROUTE_POINT` | ROUT 与 ERUT 两类路线点,使用 `ROUTE_KIND` 区分;主键应包含该列,避免两类路线的序号冲突。 |
| `PROC_STATE` | 信箱消息的处理终态和业务身份幂等记录。 |
| `MSG_EVENT` | 事务 outbox,承载整态投影、变更通知和删除 tombstone。 |
| `BACKFILL_TODO` | 共享信箱回填的可重试待办。 |
| `SCHD_SNAP_LOG` | 日计划处理留痕,只追加、可重建,不参与状态决策。 |
### 2.1 航班身份与运营日
`FLID` 是主键。`OPERATION_DAY` 从 SCHD 记录的 `SODT` 按配置的机场时区和切日规则推导;它不是消息接收日或落库日。
一旦已写入非空 `OPERATION_DAY`,同一 `FLID` 不得改到另一个运营日。遇到冲突,整包日计划按协议错误拒绝,既有状态保持不变。尚未由日计划收录的航班可以为 `NULL`;这不表示该航班没有运行日,只表示当前模型无法为它确定归属日。
### 2.2 字段与集合
标量、异常对象前缀字段和 `SRVT`/`VIPF`/`MAFL` 文本字段存于主表。变长资源完整保存到明细表,保留输入顺序和协议源序号:
- `ORDINAL` 是持久化顺序,从 1 开始;`SOURCE_SEQ` 是上游序号,允许为空或重复。
- 相同资源号不代表同一条分配,禁止按资源号去重。
- 每次持久化完整航班状态时,明细表按该 `FLID` 先删后插,以完整合并结果为准。
- ROUT 与 ERUT 是两类独立集合,不能因相同序号覆盖彼此。
## 3. 合并与写入语义
处理器保持纯粹:它根据当前完整态和已解码报文得到下一完整态与待发事件,持久化由统一事务入口执行。
### 3.1 SCHD 日计划
SCHD DNLD/RESP 在整包校验通过后,逐条将报文携带的航班写入当前态。日计划只更新或创建其携带的 `FLID`,**不会因其他航班未出现在本次报文中而删除任何记录**。
日计划在重叠字段上可以覆盖当前动态值;未携带的字段按合并规则保留,显式清空才清除。每个成功写入的航班推进 `STATE_VERSION`,并在同一事务登记 `KAFKA_SCHD``KAFKA_MSG` 事件。
消息重放由 `PROC_STATE` 的消息 ID 与 `identity_key` 控制;已成功提交的消息不得再次写入或重复登记事件。整包校验失败或运营日冲突时,整包不落地。
DNLD、RESP 和 ADFT 均已有路由入口。RESP 的请求匹配闭环、出站请求与超时重发仍是待交付项;未注册处理器不能被视为已实现。
### 3.2 动态运行事件
FLOP 事件只修改它表达的字段或资源集合,其余航班状态保持不变。每个动态子类型的语义都必须有明确 Handler 规则和回归测试,不能只因已被路由就推定其业务语义完整。
动态事件保留既有 `OPERATION_DAY`,也不基于接收时间重新推导它。未知或已删除航班的具体处理遵从对应 Handler 的幂等规则。
### 3.3 删除与重建
FDEL 是业务删除入口:仅在 `ACTIVE → DELETED` 时推进版本、保留明细并与 tombstone 同事务登记;重复 FDEL 或不存在的航班按幂等成功处理。
物理删除仅由独立历史清理在归档成功后执行。日计划报文不是删除依据。若未经 FDEL 而由生命周期清理,清理前需要登记一次 tombstone;已经 FDEL 的记录不重复发出。
ADFT 的字段缺失语义尚待上游确认。在确认前采用保守的 Set-only 规则:出现字段可更新,缺失字段不清空;不得把它当成日计划或动态全量替换。新建 ADFT 若带可解析的 `SODT`,按同一运营日规则计算 `OPERATION_DAY`;否则保留为 `NULL`
## 4. 处理事务与失败规则
所有状态处理遵循下列顺序:
1. 主泵只处理 FIFO 队头,解码并绑定业务身份。
2. 校验消息结构、声明数量、航班标识和运营日;协议错误标记 `DEAD`,不提交半包。
3. 在一个事务中取得 `PIPELINE_LOCK`,读取当前完整态,计算下一状态,写主表和明细表,登记 outbox 和回填待办。
4. 同一事务提交处理终态;任一步失败则整体回滚并按错误类别重试或终止。
5. 提交后回填共享信箱并异步投递 outbox;后续失败不得把已提交的 `SUCCEEDED` 改回失败。
`identity_key``发送方|类型|子类型|序号` 构成,用于业务重复检测。`STATE_VERSION` 是单航班的单调版本,供下游判定新旧;它不是日计划版本,也不表示运营日版本。
## 5. Kafka 与读取
`KAFKA_SCHD` 是按 `FLID` 的完整状态投影。Dispatcher 可以合并同一 `FLID` 尚未投递的中间版本,只发最新状态;消费端用 `(FLID, STATE_VERSION, UPDATED_AT)` 防止旧投影覆盖新状态。
`KAFKA_MSG` 只通知变化,不承载权威状态;两个 topic 不承诺顺序。FDEL 和必要的生命周期清理使用 tombstone:键为 `FLID`,删除记录以 null 值投递,通知下游移除旧状态。
读取完整航班需要读取主表和全部明细。当前逐航班读取有多次查询,尚未保证跨表一致性快照;批量加载与明确的一致性读边界是后续优化项。
## 6. 生命周期与开放项
运营日过去不等于航班结束。历史清理须同时满足配置保留期与终态证据或足够静默期,先成功写入历史存储,后物理删除当前态;历史存储失败时必须删除零行。
以下事项仍需确认或交付:
- Oracle 11g 的完整方言与集成验证;
- RESP 请求—应答匹配和出站请求重发;
- ADFT 缺失字段和 `FLID` 重用的上游语义;
- 未归属 `OPERATION_DAY` 航班的终止与保留策略;
- ROUT/ERUT 联合主键迁移;
- 批量一致性读取,以及动态事件仅在事务内计算一次的收敛。
## 7. 不变量
- 本地 PostgreSQL 当前态是唯一权威;信箱、Kafka、Redis 和展示视图不是权威。
- 同一时刻只有一个主泵推进 FIFO 状态;状态、事件、终态和回填意图原子提交。
- `FLID` 唯一,已确定的 `OPERATION_DAY` 不可改变。
- 报文未携带的字段不会被隐式清空;集合按完整合并结果写入。
- 缺席于某个日计划不构成删除理由;删除只由 FDEL 或受控历史清理触发。
- 外部副作用失败可重试,不回滚已提交的本地业务结果。