docs: 二轮审查文档收敛 ACM2-23~27(Kafka/依赖/ResponseDto/STATUS/格式)

This commit is contained in:
windyboy
2026-09-07 14:15:07 +08:00
parent 7ccdd00a31
commit 346929ea5d
5 changed files with 446 additions and 49 deletions
+59 -5
View File
@@ -83,6 +83,57 @@ PROC_STATE / MSG_EVENT / PUMP_JOB / REQ_TRACK / REF_MASTERPG 方言)。全
- **阶段 BFLIGHT_STATE**:缓做不落表。
- 影子对拍:自有 PG 开独立 schema;共享信箱为单信箱无法双写,影子输入=只读水位/回放口径。
## 本地开发依赖中间件栈(Podman / Docker ComposeACM2-13
仓库根目录提供兼容 Podman Compose 与 Docker Compose 的开发中间件栈 `compose.yaml`,包含:
- **共享信箱 MySQL**`mysql:8.4` LTS,端口 3306,库 `cdairport`):容器启动时自动执行 `deploy/dev/mysql-init/01-mailbox.sql` 创建本地联调所需的 `CMINMSGS``CMINMSGS_HST``COUTMSGS` 模拟表。
- **自有 PostgreSQL**`postgres:17-alpine`,端口 5432,库 `msgx`):容器提供干净数据库,应用启动时由 Flyway(`db/migration/V1.0.0__own_pg_pipeline.sql`)自动建自有表。
- **Redis / 动态与快照**(开源分叉 `valkey/valkey:8-alpine`,端口 6379):100% 兼容 Redis 7.2+ 协议与 Lua 脚本,支持 `--appendonly yes`
- **Kafka**`apache/kafka:3.8.0` KRaft 单节点,端口 9092):listener `PLAINTEXT://localhost:9092``default.replication.factor=1`,已预配幂等生产者与 acks=all 所需的单节点参数。
### 快速启动
```bash
# 1. 复制环境变量示例(可按需修改口令与端口)
cp .env.example .env
# 2. 启动开发依赖栈(支持 docker compose 或 podman compose
docker compose up -d
# 3. 检查各服务健康状态(均为 healthy)
docker compose ps
```
### 环境变量与应用映射
| 环境变量 | 说明 | 示例默认值 | 应用配置映射(application.yml |
|---|---|---|---|
| `MSGX_MAILBOX_URL` | 共享信箱 MySQL JDBC URL | `jdbc:mysql://localhost:3306/cdairport?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4` | `mailbox.shared-mysql.url` |
| `MSGX_MAILBOX_USER` | 共享信箱 MySQL 用户名 | `msgx_dev` | `mailbox.shared-mysql.username` |
| `MSGX_MAILBOX_PASSWORD` | 共享信箱 MySQL 密码 | `msgx_dev_pass` | `mailbox.shared-mysql.password` |
| `MSGX_PG_URL` | 自有 PostgreSQL JDBC URL | `jdbc:postgresql://localhost:5432/msgx` | `datasources.default.url` |
| `MSGX_PG_USER` | 自有 PostgreSQL 用户名 | `msgx_dev` | `datasources.default.username` |
| `MSGX_PG_PASSWORD` | 自有 PostgreSQL 密码 | `msgx_dev_pass` | `datasources.default.password` |
| `MSGX_REDIS_URI` | Redis/Valkey 连接串 | `redis://localhost:6379` | `redis.uri` |
| `MSGX_KAFKA_SERVERS` | Kafka bootstrap brokers | `localhost:9092` | `kafka.bootstrap.servers` |
| `MSGX_KAFKA_ACKS` | 生产确认级别 | `all` | `kafka.producers.default.acks` |
| `MSGX_KAFKA_IDEMPOTENCE` | 幂等生产者开关 | `true` | `kafka.producers.default.enable-idempotence` |
| `MSGX_KAFKA_MAX_IN_FLIGHT` | 单连接最大在途请求数 | `1` | `kafka.producers.default.max-in-flight-requests-per-connection` |
### 切流前 Kafka Broker 版本确认
生产契约以 `user-stories.md` US-07 / §7-6 为准:对接 Kafka 2.8+ / 3.x+,生产者强制 `acks=all``enable.idempotence=true``max.in.flight.requests.per.connection=1`**严禁非幂等降级**。README 不提供生产降级环境变量组合。
旧系统 `msgexchange-api` 底层依赖 `kafka-clients:0.10.1.1`;现网 Broker 确切版本须在切流前实测确认:
1. **版本探测**:切流前通过本栈工具探测目标集群 API 能力:
```bash
docker exec msgx-dev-kafka /opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server <TARGET_IP>:9092
```
2. **门禁判定**:若输出中 `InitProducerId(22)` 为 **可用** → 保持现代高可靠默认(`acks=all`、`idempotence=true`、`max.in.flight=1`)。
3. **阻塞切流**:若 `InitProducerId(22)` 为 **UNSUPPORTED**Broker &lt; 0.11)→ **阻塞切流**,须升级 Broker 或经架构豁免(ACM2-1 基础设施升级门禁);降级参数仅可作为经批准的 runbook 附录,**不得**作为生产验收口径与 README 默认配置并存。
## 构建
```bash
@@ -99,10 +150,9 @@ MICRONAUT_ENVIRONMENTS=dev ./gradlew run # dev stub 冒烟:内存 stub,无
> `micronaut.endpoints.*`(正确为顶层 `endpoints.*`/env、/beans 这才真正放开);另关闭 eureka
> discovery。当前 `MICRONAUT_ENVIRONMENTS=dev ./gradlew run` **无需任何环境变量/外部依赖**即可启动,
> compat HTTP `POST /cminmsgs/send`application/json)返回记录 ID`/health` UP。
> 已知边界:text/plain compat 写 415 属 U16 缺口(契约对拍后固化);`application-dev.yml`
> `datasources.default.url` 仍为 MySQL 占位ACM2-12 前残留,stub 下 `enabled=false` 不建连,
> U05 清理为 PG 口径);测试侧端到端语义由 PipelineSmokeTest(内嵌上下文,compat 写路径)覆盖。
> **生产主路径**JDBC 轮询 `InboxPoller`)属 U05dev stub 尚未覆盖。
> 已知边界:text/plain compat 写 415 属 U16 缺口(契约对拍后固化);`application-dev.yml` 数据源配置
> 已对齐为自有 PG 与共享 MySQL 信箱ACM2-12/13);测试侧端到端语义由 PipelineSmokeTest
> (内嵌上下文,compat 写路径)覆盖。**生产主路径**(JDBC 轮询 `InboxPoller`)属 U05dev stub 尚未覆盖。
> 注解处理:Kotlin 侧经 KSP`kotlin-ksp` + `micronaut-inject-kotlin`)生成 Micronaut
> BeanDefinitionU01);若 build 产物缺少 `*$Definition` 类,先检查 KSP 是否生效。
@@ -114,7 +164,11 @@ MICRONAUT_ENVIRONMENTS=dev ./gradlew run # dev stub 冒烟:内存 stub,无
- [docs/architecture.md](docs/architecture.md):架构速览——**中间件定位**、总体拓扑(JDBC 轮询主路径)、
上下游边界、模块职责、关键决策、数据边界、部署与安全姿态、可观测性、就绪度。
- [docs/design.md](docs/design.md):设计细节——系统边界、状态机与错误分类、数据模型、
核心流程语义(流程 1 主/compath 分述)、失败/重试/重放、不变量落点、参数表、已知缺口。
核心流程语义(流程 1 主路径/compat 分述)、失败/重试/重放、不变量落点、参数表、已知缺口。
- [docs/user-stories.md](docs/user-stories.md):阶段 A US-01US-14、延后清场 US-15、上线 Epic、
legacy HTTP 去留及逐项文档 TODO;包含验收标准、依赖、实现差距与待确认问题。
- [docs/user-stories-todo.md](docs/user-stories-todo.md)ACM2-1522 对应的文档整改清单、
已完成落点、待 Plane/产品确认事项与验证门禁。
- [docs/legacy/](docs/legacy/):外部参考/基线材料(自 legacy 仓库拷贝,非本系统文档)——
`msgexchange-api-legacy-user-stories.md`legacy 行为对拍基线,ACMA-4)、`unisysaodbsis.xsd`、
`SIS_AODB_RMS-V0.1.md`(消息结构唯一事实源;CIIMS 中间件交换模型)。
+25 -24
View File
@@ -2,10 +2,11 @@
> **系统角色**:机场 OMMS **上游报文处理中间件**——消费 CIIMS/AODB 等上游写入共享信箱的
> XML 报文,经 FIFO 管道处理后向下游投递;**非**报文源系统。
> 现行架构权威为 Plane **`airport_chengdu_msgexchange_v2`ACM2** 工作区的 **ACM2-3(综合架构 v4**
> 架构基线为 Plane **`airport_chengdu_msgexchange_v2`ACM2** 项目的 **ACM2-3(综合架构 v4**
> 脚手架跟踪 **ACM2-4**,评审与实施计划(U01U30**ACM2-10**。本文是仓库内的架构速览,
> 与代码同步维护;两者冲突时以 ACM2-3 为准并回改本文。
> **存储边界与阶段 B 以 ACM2-11/ACM2-12 决策为准**(自有 PostgreSQL + 共享 MySQL 信箱 +
> 与代码同步维护。**有效口径 = ACM2-3 未被取代的内容 + 后续决策 ACM2-12**,不能只按
> ACM2-3 的历史正文回改本文。
> **存储边界与阶段 B 以 ACM2-12 为准**ACM2-11 仅为决策史;自有 PostgreSQL + 共享 MySQL 信箱 +
> Redis 动态/gen;阶段 B 缓做)——ACM2-3 中"同库事务锚 / MySQL 六辅助表 / 阶段 B"表述
> 已被上述决策修订。
> 配套设计细节见 [design.md](design.md)。
@@ -47,9 +48,9 @@
|---|---|---|
| 语言/运行时 | Kotlin 2.3 + JDK 25 | JDK 21 不可行(Micronaut 5.1 系要求 JVM 25+ACMA-9 实测) |
| 框架 | Micronaut platform BOM **5.1.3**core 系实际解析 **5.1.13**,classpath 混用;版本重钉属 U02/U05 | 编译期 DIKSP`kotlin-ksp` + `micronaut-inject-kotlin` **5.1.3**)生成 `*$Definition` |
| 持久化 | **自有 PostgreSQL**(全部内部状态)+ 共享 MySQL 信箱 | 自有库:消息管道 PROC_STATE/MSG_EVENT + PUMP_JOB/REQ_TRACK + 21 类 REF_MASTER(迁移 `db/migration`);共享库 CMINMSGS/COUTMSGS DML。Micronaut Data JDBC 实装属 U05ACM2-12 |
| 持久化 | **自有 PostgreSQL**(全部内部状态)+ 共享 MySQL 信箱 | 自有库:消息管道 PROC_STATE/MSG_EVENT + PUMP_JOB/REQ_TRACK + 21 类 REF_MASTER(迁移 `db/migration`);共享库主契约为 CMINMSGS/COUTMSGS DML。JDBC 仓储与 InboxPoller 已有初版,事务/补偿/出站适配仍属 U05ACM2-12 |
| 权威存储 | Redis(航班动态 flightInfo + 快照 gen | 仅主泵线程写(I5);Lua 原子覆盖/版本推进(gen 协议重设计属 U09) |
| 投递 | Kafkaacks=all + 幂等) | outbox 模式,经 MSG_EVENT 表中转 |
| 投递 | Kafkaacks=all + 幂等;切流前须确认 Broker 支持 InitProducerId(22),严禁非幂等降级 | outbox 模式,经 MSG_EVENT 表中转 |
| 投影(阶段 B | Elasticsearch(历史) | **阶段 B 缓做(ACM2-12**FLIGHT_STATE 不落表,Redis 永续动态权威;历史投影链路不变 |
| 注册中心 | EurekaMicronaut 原生键) | 服务名契约 `msgexchangeapi`(影子 `msgexchangeapi-shadow`)——**U17 未落地**:当前注册名仍取 `micronaut.application.name`=msgexchange-nextgen),`msgx.service-name` 无运行时消费方(见 §8 与 design.md §9 |
| 可观测 | logstash TCPAsync 包装)+ MDC traceId + 自定义健康指示器 | 见 [design.md §8](design.md) |
@@ -75,7 +76,7 @@
│ │ │ decodeXmlCodec
│ │ │ identity 绑定(I3
│ │ │ Handler.decide(纯函数) │
│ │─Schd DNLD──▶ SnapshotFlow(流程4
│ │─Schd RESP/DNLD▶ SnapshotFlow(流程4
│ │─PUMP_JOB───▶ JobExecutor(作业窗口,决策1
│ │ │
│ ├────Redis Lua──▶ Redis flightInfoA权威) │
@@ -102,18 +103,15 @@
PG 入队失败时以共享库水位**重扫补建**(U05)。`POST /cminmsgs/send` 为现役 HTTP
**写**路径(手工/对拍),非上游报文到达的主拓扑。
- **条专用 daemon 单线程**`msgx-pump` / `msgx-dispatcher`)由 `PipelineLifecycle`
- **条专用 daemon 单线程**`msgx-inbox-poller` / `msgx-pump` / `msgx-dispatcher`)由 `PipelineLifecycle`
`ServerStartupEvent` 后拉起,不占用 Netty event loop;停机 `requestStop` +
interrupt + joinU07)。仅当 `msgx.pipeline.autostart=true` 时装配——生产默认关,
当前属**有意脚手架门禁**(生产可运行需先完成 U05 数据层实装,见 §7)。
- **单写者约束(I5)**:阶段 A 全部 Redis 写集中在主泵线程;实例数必须为 1
(运行期租约/选主保护属 U26,尚未实装,当前靠部署拓扑约束)。
- **统一 FIFO(决策 1)**:定时作业(cron → PUMP_JOB 入队)与消息同队列,
作业产物不绕过队头顺序。**当前实现**:`jobBefore`**head-state 近似**(队头为空或
FAILED 时作业先行),非入队时间排序,与「统一 FIFO」注释存在已知偏离(U15 未实装,
见 design.md §9)。
**ACM2-12 修订**PUMP_JOB 在自有 PG,作业不插队、仅在消息队头空闲/退避窗口由主泵执行
(作业窗口语义定稿属 ACM2-12 Checks ④)。
- **消息严格 FIFO,作业采用窗口语义(决策 1 经 ACM2-12 修订)**:消息按最小未完成
`CMINMSGS_ID` 保序;PUMP_JOB 不与消息构成统一全序,仅在无消息队头或队头处于退避窗口时执行。
当前 `jobBefore` 的 head-state 判定已近似该语义,但窗口、饥饿边界和测试仍待 U15 固化。
- **存储边界(ACM2-12)**:本框图内事务库为**自有 PostgreSQL**PROC_STATE/MSG_EVENT/
PUMP_JOB/REQ_TRACK/21 类);CMINMSGS/COUTMSGS 在**共享 MySQL 信箱**(上游外部写、
本系统 JDBC 轮询读 + 处理回填写);Redis 除 flightInfo 还承载快照 gen。图中跨库步骤
@@ -125,7 +123,7 @@
| 包 | 职责 | 对应 ACMA-8 | 主要类 |
|---|---|---|---|
| `ingress/` | 收报:JDBC 轮询共享信箱发现新信 → 自有 PG 建 PENDING+ 补偿重扫;HTTP 写路径 compat);不解析报文 | 流程 1I3 | `InboxPoller`U05`InboxController` `InboxService` |
| `processing/` | 主泵:FIFO 领取、解码、identity 绑定、纯函数决策、自有 PG 事务2 | 流程 2/4I1/I2/I5 | `Pump` `MessageProcessor` `SnapshotFlow` `Identity` `Handler(Registry)` |
| `processing/` | 主泵:FIFO 领取、ignoreMsg、identity 绑定、纯函数决策、RESP/DNLD 快照、自有 PG 事务2 | 流程 2/4I1/I2/I5 | `Pump` `MessageProcessor` `SnapshotFlow` `Identity` `Handler(Registry)` |
| `delivery/` | 投递:每 target 严格 FIFO、schd 聚合 | 流程 3 | `Dispatcher` `SchdAggregation` |
| `jobs/` | 泵作业:清场/归档/投影重建(PUMP_JOB 自有 PG,作业窗口执行) | 流程 4/5/7,I4 | `JobExecutor` `HistorySweepJob` `ArchiveJob` `ProjectionRebuildJob` |
| `codec/` | XML 解码 + 失败分类(MALFORMED vs CODEC_ERROR | 决策 4 前置 | `XmlCodec` `DecodeResult` |
@@ -137,7 +135,7 @@
| # | 决策 | 落点 |
|---|---|---|
| D1 | 作业与消息同队列(cron 只经 PUMP_JOB 入队,产物不绕过队头)——**ACM2-12 修订**PUMP_JOB 迁自有 PG 后不插队,仅队头空闲/退避窗口执行 | `Pump.tick` / `JobExecutor` |
| D1 | 消息保持严格 FIFO;PUMP_JOB 为独立持久队列,不与消息组成统一全序,仅在无消息队头或队头退避窗口执行 | `Pump.tick` / `JobExecutor` |
| D2 | 阶段 B(缓做,ACM2-12):ES 投递成功后同线程同步 enqueue 删除事件(不轮询 ack | `Dispatcher.tick`(定案 2;阶段 B 评估后启用) |
| D3 | schd 唯一出口是 flushSchd 批量聚合(逐条循环显式排除 KAFKA_SCHD | `Dispatcher.tick`U06/N03 |
| D4 | 未实装 ≠ 非法:无 handler / staging 未实装 → FAILED(UNSUPPORTED) 可重放,绝不写终态 | `MessageProcessor` `SnapshotFlow`U10/N21 |
@@ -157,25 +155,28 @@
出站写 `COUTMSGS`(他人系统读取发送)。与信箱的交互是**外部副作用**,非本系统事务的一部分:
收报主路径=JDBC 轮询发现新信 → 自有 PG 建 PENDING 入队;compat HTTP 写=
信箱 insertRaw 成功(返回 CMINMSGS_ID)→ PG 入队;PG 建行失败以共享库
`DATE_PROCESSED IS NULL` 重扫补建;回填 = 处理成功后异步/补偿,最终一致(ACM2-12 影响面)。
`DATE_PROCESSED IS NULL` 重扫补建;回填 = 处理成功后 PG 本地事务外异步/持久化补偿,最终一致(ACM2-12 / ACM2-19)。
- **Redis**:航班动态 flightInfo(阶段 A 权威,I5+ 快照 **SCHD_GEN**Lua 内原子
「覆盖+按代差删+版本推进」,协议重设计属 U09)。
- **阶段 BFLIGHT_STATE):缓做,不落表**;ES 仍承载历史航班(判史/查询),
相关投影/删除事件链待阶段 B 重评估后启用。
- legacy 旧表 schema 归 legacy 仓库维护;CMINMSGS/CMINMSGS_HST/COUTMSGS 结构与保留策略
由共享库方管理,本系统只按契约读写(权限边界需与库方确认)。
- legacy 旧表 schema 归 legacy 仓库维护;CMINMSGS/COUTMSGS 结构与保留策略由共享库方管理。
**ARCHIVE 归档定案(ACM2-17**:严禁向共享 MySQL 写入 `CMINMSGS_HST`(共享库严格保持 CMINMSGS 读/回填、
COUTMSGS 写入两表契约);终态入站消息归档目标确定为自有 PG `PROC_STATE_HST`
## 7. 权威(阶段 A Redis;阶段 B 缓做)与当前就绪度
| 阶段 | 权威 | 投递目标 | 状态 |
|---|---|---|---|
| A`msgx.phase=A` | Redis flightInfo+ gen | KAFKA:msg、KAFKA:schd | 管道骨架+重试闭环已实装;自有 PG 数据层/信箱适配层属 U05gen→Redis 协议属 U09 |
| B`msgx.phase=B` | —(缓做,ACM2-12) | ES 历史投影 | 不落表、不启用;Redis 永续动态权威,阶段 B 价值重评估后再定 |
| A`msgx.phase=A` | Redis flightInfo+ gen | KAFKA:msg、KAFKA:schd | 管道骨架+重试闭环已实装;PG/JDBC 信箱轮询已有初版,但水位、事务、回填补偿与出站信箱尚未闭环gen→Redis 协议属 U09 |
| B缓做,重新评估后命名/启用) | Redis 仍为动态权威 | ES 历史写入与成功集清场 | FLIGHT_STATE 不落表;HISTORY_SWEEP 标为 DEFERRED,不作为阶段 A 切流门禁 |
**就绪度(2026-09-07 复核口径)**可编译、37 测试全绿、**dev stub 进程级冒烟实测可端到端**
**就绪度(2026-09-07 文档审查口径)**现有 JUnit XML 报告记录 39 项测试全绿,README 记录
dev stub 进程级冒烟曾通过;
本轮因沙箱无法写用户级 Gradle 缓存,未重新证明该结果。当前已有 JDBC PG 仓储、共享信箱适配器
与 InboxPoller 初版,但没有覆盖跨库补偿和 PG 本地事务的集成验证。**dev stub 冒烟路径**可端到端
`./gradlew run` 无外部依赖启动 → compat HTTP 写路径返回 200 → `/health` UP,修复记录见 README「进程级 dev 冒烟」);
生产默认配置**不可对外服务**——`autostart=false` 且生产(stubs=false)下仓储无实装、DI 装配
即失败。生产就绪前置:U05(数据层+事务)、U07 fail-fast 定案、U09(快照恢复协议)、
生产默认配置**不会启动处理管道**`autostart=false`,数据源/信箱也默认 disabled)。生产就绪前置:
U05(事务、补偿、出站与集成验证)、U07 fail-fast 定案、U09(快照恢复协议)、
U13(投递毒丸补全)、U15(统一序号)。逐项状态见 ACM2-10「定稿实施计划」。
## 8. 部署与安全姿态
+40 -20
View File
@@ -4,7 +4,8 @@
> `CMINMSGS`)投递的 XML 报文,解析处理后维护 Redis 动态并向 Kafka / 出站信箱投递;
> **非**报文源系统。生产主路径 = **JDBC 轮询**发现新信;HTTP `POST /cminmsgs/send` = compat 写路径。
> 本文对应仓库当前实现,给出模块级设计语义与依据;架构总览见
> [architecture.md](architecture.md)权威架构为 Plane **ACM2-3**实施计划与逐项验收为
> [architecture.md](architecture.md),架构基线为 Plane **ACM2-3**其存储/事务边界由后续
> **ACM2-12** 覆盖;实施计划与逐项验收为
> **ACM2-10U01U30**。文中标注「TODO/未实装」的条目均为已知开放项,不属文档遗漏。
## 0. 系统边界速览
@@ -107,10 +108,13 @@ ErrorClass(两侧共用):
`DATE_PROCESSED IS NULL`);本系统 `ingress` 经 **JDBC 轮询**发现新信(与 legacy
`MsgExchangeRunner.getNewMsgsAfterId` 同语义,1s 节律),自有 PG 入队:
1. `pollNew()`U05`InboxPoller`):JDBC 查共享库 `CMINMSGS_ID > watermark
DATE_PROCESSED IS NULL`(及/或 PG 无对应 PROC_STATE 的补偿重扫);
1. 目标态分两路:快路径按持久化 watermark 查询 `CMINMSGS_ID > watermark AND
DATE_PROCESSED IS NULL`;补偿路径按受控周期重扫“未处理且 PG 无对应 PROC_STATE”的记录。
watermark 仅在本批 PG 入队均已确认后推进。**当前初版** `InboxPoller` 固定 `afterId=0`
每轮全量扫描未处理记录并以 PG 判重,尚未实现上述水位与补偿频控;
2. `procState.insert(id)`:自有 PG 建 PENDING 行入队;本步失败 → 下轮重扫补建;
3. 不解析报文、接收层无唯一约束(I3);`wakePump()` 为 TODO 空操作——主泵 1s 轮询兜底。
3. 不解析报文、接收层无业务 identity 唯一约束(I3);`PROC_STATE.CMINMSGS_ID` 主键负责
轮询重扫幂等。当前无显式 `wakePump()`,主泵 1s 轮询兜底。
**compat 路径(现役 HTTP 写)**`InboxService.accept``POST /cminmsgs/send`=
共享信箱 `insertRaw` + 自有 PG 入队(跨库,非同一事务);「已持久化」响应语义与现役
@@ -133,14 +137,17 @@ ErrorClass(两侧共用):
门禁无单测锁定,见 §7);否则 sleep 至 nextAttemptAt。
4. 正常队头 → `MessageProcessor.processOne`
入口守卫(FAILED 且已 exhausted → DEAD)→ `rawOf` 缺失 → DEAD(MALFORMED) →
decodeMALFORMED→DEAD / CODEC_ERROR→FAILED)→ identity 首绑
`tryBindIdentity` 失败 → SKIPPEDI3)→ Schd DNLD → `SnapshotFlow` →
decodeMALFORMED→DEAD / CODEC_ERROR→FAILED)→ ignoreMsg 匹配(LDM/REGN/RSTA/EROR
命中→SKIPPED,回填遵循 US-09;当前未实装)→ identity 首绑
`tryBindIdentity` 失败 → SKIPPEDI3)→ Schd RESP/DNLD → `SnapshotFlow`ACM2-16 定案:
DNLD 与 RESP 均走 SnapshotFlowRESP 成功后在同事务完成匹配开放 RQFD 的 `REQ_TRACK→DONE`
迟到或无匹配 RESP 严禁更新快照,直接转 SKIPPED 并审计)→
其余 → `Handler.decide(redis.hgetAllFlightInfo(), msg)` →
阶段 ARedis 先写(I2 happens-beforeTODO redisApply)→ 自有 PG 事务 2
MSG_EVENT 插入 + PROC_STATE→SUCCEEDED(同库原子,@Transactional);
CMINMSGS 回填(DATE_PROCESSED/STATUS)为共享信箱**外部回填**:目标态为 PG 提交后
异步/补偿执行,失败重试+告警(最终一致,ACM2-12);**当前实现为同步内联占位**
`insertAll → backfillOnSuccess → SUCCEEDED`,无 `@Transactional`U05 定案并改)
CMINMSGS 回填(DATE_PROCESSED/STATUS)为共享信箱**外部回填**ACM2-19 定案):
PG 事务提交后异步触发执行,持久化补偿、失败退避重试+告警、影子禁写;全部终态
SUCCEEDED / ignore SKIPPED / duplicate SKIPPED / DEAD)均必须回填,非终态禁止回填
5. 异常边界(U08):`processOne` 内 try/catch → `ProcFailure.fail(INFRA)`attempts+1、
退避、达上限 DEAD);`InterruptedException` 恢复中断位后**上抛**loop 仅 catch
`Exception` 作最后防线,`Error` 任其终止进程(异常必可见)。
@@ -157,18 +164,22 @@ ErrorClass(两侧共用):
claim、`lastFlush` 仅成功后推进;达上限整批 DEAD(DLQ)。周期/批上限取参数表
3s / 500)。
- 轮询间隔取参数表(下限 50ms,N18);无 200ms 硬编码。
- **Kafka 生产契约(ACM2-23**:对接 Kafka 2.8+ / 3.x+,生产者强制 `acks=all`、`enable.idempotence=true` 与 `max.in.flight.requests.per.connection=1`;切流前须确认 Broker 支持 `InitProducerId(22)`,严禁非幂等降级;README 不提供生产降级 env。
- 阶段 B(定案 2/D2ACM2-12 缓做):ES 投递成功 → 同线程同步 `insertSync` 删除事件
`deleteOf`Jackson 结构化序列化,refs 可空恒合法 JSON——U14)。当前不启用。
### 3.4 流程 4:日计划快照(`SnapshotFlow`
### 3.4 流程 4:日计划快照(`SnapshotFlow`RESP/DNLD
```
staging(流式解析+整包校验,TODO 阶段2;未实装→FAILED(UNSUPPORTED)
SCHD-RESP / SCHD-DNLD
→ staging(流式解析+整包校验,TODO 阶段2;未实装→FAILED(UNSUPPORTED)
→ 守卫判定:若为 SCHD-RESP,检查开放 RQFDdttm < sentAt 或无匹配/已过期 → 严禁更新快照,转 SKIPPED 并审计)
→ Redis Lua SNAPSHOT_REPLACE(同一 hash 原子「覆盖新代+按代差删」,删除集=旧代flids−新代)
→ gen 版本推进(ACM2-12gen 随 flightInfo 同在 RedisLua 内原子版本 CAS——
目标实现;现 RefDataRepository/putGenIfVersion 为过渡占位)
自有 PG SUCCEEDED
```
→ gen 版本推进(ACM2-12gen 随 flightInfo 同在 RedisLua 内原子版本 CAS
→ 自有 PG 本地事务(原子性):PROC_STATE→SUCCEEDED + RESP 匹配时)REQ_TRACK→DONE + MSG_EVENT 插入
PG 提交后异步触发信箱回填(持久化补偿,ACM2-19)
数据流说明:SCHD-RESP 处理依赖 US-08 已登记的开放 REQ_TRACKUS-06 与 US-08 为单向数据流耦合(US-06 依赖 US-08 登记能力),不构成双向故事依赖(ACM2-24)。
**已知缺口(U09,未定案,ACM2-12 后重设计为 Redis 内协议)**gen 与 Lua/SUCCEEDED 不再
分属两存储即可同原子(全部在 Redis Lua);真正跨存储的窗口收窄为「Lua 已完成、PG SUCCEEDED
@@ -180,8 +191,11 @@ staging(流式解析+整包校验,TODO 阶段2;未实装→FAILED(UNSUPPOR
- HISTORY_SWEEP3:30 清场,I4 同步链):判史 → 同步写 ES → 仅删成功集。
**占位门禁(U10/T07 修订)**ES saveSync 接线前 `pickHistory` 恒空集、删除量恒 0
禁止「全量可删」fail-open 默认;现役五条判史规则 golden 通过后才允许接线。
- ARCHIVE3:00):1 天前且仅终态(SUCCEEDED/SKIPPED/DEAD)迁 CMINMSGS_HST(共享库,
外部副作用;TODO U05 批次)
**阶段归属**:按 ACM2-12 阶段 B 缓做口径标为 DEFERRED,不作为阶段 A 切流门禁;启用前
重新确认“历史链路不变”与阶段 B 投影范围、ES/OpenSearch 产品边界
- ARCHIVE3:00):默认保留 **1 天**(接收时间早于 1 天)且仅终态(SUCCEEDED/SKIPPED/DEAD)可归档;保留期可配置为 1~7 天;
**定案口径(ACM2-17**:严禁向共享 MySQL 写入 `CMINMSGS_HST`(共享库严格保持两表 DML 契约);
归档目标为自有 PG `PROC_STATE_HST`(及 `MSG_EVENT_HST`)。非终态(PENDING/FAILED)禁止归档。
- PROJECTION_REBUILD(阶段 B 缓做,ACM2-12;重新评估后再启用)。
## 4. 失败与重试统一设计(U08)
@@ -235,8 +249,8 @@ ACM2-12)、`flyway.datasources.default.*`、`mailbox.shared-mysql.*`(共享
**范围注**:以上覆盖的是边界级(processOne/flushSchd/仓储)语义;主泵 tick 级 HOL/毒丸/退避
门禁因 Pump 未注入 Clock 而无单测锁定(§3.2 注),DispatcherTickTest 仅锁批退避与「队首未到期
不推进」。
- 现状 37 测试全绿(`./gradlew test`wrapper 钉 9.6.1 + JDK 25;内网构建设
`GRADLE_USER_HOME`/`TMPDIR` 指向可写目录)。
- 最近生成的 JUnit XML 报告为 39 项测试全绿;本轮文档审查因沙箱不能写用户级 Gradle 缓存,
未重新执行(wrapper 钉 9.6.1 + JDK 25;受限环境需将 `GRADLE_USER_HOME`/`TMPDIR` 指向可写目录)。
- **U29 门禁(规划)**:不变量清单化入 CI 红即阻塞;「新增逻辑必伴生不变量测试」入贡献约定。
## 8. 可观测性设计(U12 已落地部分)
@@ -252,7 +266,7 @@ ACM2-12)、`flyway.datasources.default.*`、`mailbox.shared-mysql.*`(共享
| 项 | 缺口 | 计划 |
|---|---|---|
| U05 | 自有 PG 数据层无实装 + **InboxPollerJDBC 轮询/重扫)** 未实装(当前仅 compat HTTP `InboxService.accept`);信箱适配层 CminmsgMailbox/OutboxMailbox`@Transactional`/allopen 未引入 → 生产 DI 装配失败;`application-dev.yml` 仍残留 MySQL datasource URL 占位(stub 下 enabled=false 不建连,U05 清理) | U05 批次(Micronaut Data JDBC on PG + InboxPoller + allopen + 信箱外部副作用与补偿回归) |
| U05 | JDBC PG 仓储、CMINMSGS 适配器与 **InboxPoller** 已有初版;但实现仍是逐操作独立连接,`MSG_EVENT + SUCCEEDED` 无本地事务,回填仍同步夹在两者之间;OutboxMailbox、可靠补偿、ARCHIVE 目标与真实双库集成测试未完成。`JdbcRefDataRepository` 仍为进程内过渡态,`JdbcFlightStateRepository` 为阶段 B 空实现 | 完成本地事务边界、回填/入队补偿、COUTMSGS 适配器与 Testcontainers 双库验收;生产启用前 fail-fast |
| U07/U26 | `autostart` 默认关=有意门禁,但生产无 fail-fast;双实例无运行期防护 | fail-fast 定案 + 租约/DB 锁拒启 |
| U09 | gen→Redis 协议未重设计(Lua 内原子版本推进;崩溃窗口=「Lua 完成/PG SUCCEEDED 未写」) | Redis 内版本 CAS + 恢复协议 + 测试(ACM2-12 |
| U13 | 投递侧无 createdAt/headDeadline 超时升级、无 DEAD 告警出口;处理侧 head-deadline 判据亦不可达(见 §3.2 注) | WP2 |
@@ -266,3 +280,9 @@ ACM2-12)、`flyway.datasources.default.*`、`mailbox.shared-mysql.*`(共享
| U27 | 专有材料(SIS md 703KB / XSD 版权头)治理决策 | WP4(ACL 核验先行) |
| ACM2-12 | 存储边界(自有 PG + 共享信箱 + Redis 动态/gen + 阶段 B 缓做):迁移 SQL/配置/接口注释已按定案调整(V1.0.0 PG);信箱适配层、gen Lua、作业窗口语义、影子重设计未实装 | ACM2-12 Checks ①–⑥ |
| ACM2-11 | **决策史(已被 ACM2-12 吸收)**:曾讨论 21 类静态独立 PG 参考库;定案为并入自有 PG `REF_MASTER`(见 ACM2-12),勿再按 `datasources.reference` 第二库规划 | 仅作决策脉络参考 |
## 10. 用户故事
面向需求优化的用户故事已集中到 [user-stories.md](user-stories.md)。该文档将目标能力、验收标准、
依赖与待确认问题分开,覆盖阶段 A US-01~US-14、延后 US-15、上线 EPIC 及 legacy HTTP 去留,
避免把当前实现、目标设计和遗留兼容行为混成同一项承诺。
+50
View File
@@ -0,0 +1,50 @@
# 用户故事文档整改 TODO
> 本清单只跟踪文档收敛;实现任务仍由对应 Plane 工作项管理。新建 Plane 汇总项会与
> ACM2-15~22 重复,因此在确认合并策略前不重复创建。
## P0:先消除范围冲突
- [x] **ACM2-16 — RESP/DNLD 路由**:在 `user-stories.md` 增加报文路由矩阵;RESP/DNLD
共用 SnapshotFlowRESP 成功后再完成匹配的 RQFD 请求;ADFT 保持增量 Handler。
- [x] **ACM2-17 — 阶段边界**ARCHIVE 独立为阶段 A US-11HISTORY_SWEEP 独立为
DEFERRED US-15,不作为阶段 A 切流门禁。
- [x] **ACM2-19 — 信箱回填**:新增 US-09,区分 PG 终态与共享信箱回填,覆盖提交后执行、
持久化补偿、告警、影子禁写和双跑写权。
- [x] 将以上目标态同步回 Plane 架构权威(ACM2-3 / ACM2-16 / ACM2-17 / ACM2-19 已裁定并回写);仓库文档与 Plane 口径一致。
## P1:补齐遗漏能力
- [x] **ACM2-15**:新增 ignoreMsg US-04,固定匹配位置、规则、SKIPPED 终态及回填依赖。
- [x] **ACM2-18**:新增实时查询 US-12、21 类 REF US-13、机位/登机桥 US-14;明确
admin-api 21 类、现役 2 类与 AODB 15 类请求是不同集合。
- [x] **ACM2-20**:增加五个 legacy HTTP 端点的 KEEP/修正/不做矩阵。
- [x] **ACM2-21**US-03 仅依赖 US-01;补 FAILED 占队头、毒丸与作业窗口,业务例外移至 US-05。
## P2:统一证据与可验收性
- [x] US-01 定义 watermark 快路径和补偿重扫,并注明当前 `afterId=0` 实现差距。
- [x] US-07(原请求故事)依赖补入采集和处理;重编号后为 US-08。
- [x] Kafka 旧 Broker 降级移至待确认,不再作为无权威出处的验收条件。
- [x] MALFORMED 重放统一为“跳过并返回逐项结果”,与 `ReplayService` 语义一致。
- [x] 将原 US-10 拆成 OPS-1~OPS-4,不再用“满足约定/评审”作单项验收。
- [x] 产品确认 `user-stories.md §7` 的八项开放问题,并把答案写回对应故事。
- [x] 文档定案后更新 Plane 关联项状态(ACM2-15~22 全部推进至 Todo,定案评论已写回);没有发布证据时不得标记 Done。
## P3:二轮审查文档修复(ACM2-23~27)
- [x] **ACM2-23**Kafka 生产契约 vs README 降级指南对齐(README 删生产降级 envUS-07/§7-6 增补 Broker 版本确认依赖)。
- [x] **ACM2-24**:消除 US-06 ↔ US-08 循环依赖(US-08 移除 US-06 依赖,AC6 改为集成验收)。
- [x] **ACM2-25**US-02 ResponseDto 失败字段对齐 legacy`err_code`/`err_msg`,不用 `msg`)。
- [x] **ACM2-26**US-09 STATUS 值域定案(单值矩阵 + 库方确认依赖 + §7-9)。
- [x] **ACM2-27**:分区键 SNDR 固化、US-11/design §3.5 默认 1 天可配 1~7 天、小节末空行。
- [x] **ACM2-14**:取消并 relates_to ACM2-22(与 ACM2-22/2327 重叠收束)。
- [x] **ACM2-22**:写入二轮审查闭环评论(已验证属实 + 新发现 1~5 + ACM2-2327)。
## 验证清单
- [x] `architecture.md``design.md``user-stories.md` 的 RESP/DNLD 和阶段表述一致。
- [x] README 指向新的故事范围。
- [x] Markdown 变更通过 `git diff --check`
- [ ] Plane ACM2-2327 已 commit 后转 Done。
- [ ] 实现尚未随本文修改;后续代码 PR 必须补 FIFO、快照、回填和双库集成测试。
+272
View File
@@ -0,0 +1,272 @@
# msgexchange-v2 用户故事草案
> 依据 `architecture.md`、`design.md`、Plane ACM2-3/5/6/7/12/1522 与 legacy 基线整理。
> 本文描述目标能力和明确保留的兼容行为,不代表当前代码已完成。
## 1. 约定
- “信箱已落信”“PG 已入队”“业务处理成功”“共享信箱已回填”“下游已投递”是不同事实。
- `KEEP` 表示兼容现役;`FIX` 表示修复 legacy 缺陷;`DEFERRED` 表示不属于阶段 A。
- 依赖只表示前置能力,不形成循环;待决策内容不得伪装成验收标准。
## 2. 阶段 A 用户故事
### US-01 可靠采集共享信箱报文
**作为** 平台运维人员,**我希望** 持续采集 `CMINMSGS` 未处理报文,**以便** 上游无需改变投递方式。
**验收标准**
1. 按配置周期、ID 升序和有限批次采集 `DATE_PROCESSED IS NULL`;这里只采集入队,不代表业务已处理。
2. 快路径使用持久化高水位增量扫描;另以受控周期补偿重扫“未处理且 PG 无状态”的记录。水位只在本批入队确认后推进。
3. 每个 ID 在 PG 至多一个 `PROC_STATE(PENDING)`;重复发现不重复入队。
4. PG 不可用时不改共享信箱标记;恢复后补偿重扫能补建遗漏状态。
5. 单条异常和数据库故障可观察,轮询线程不得静默退出。
**依赖**:共享库读权限、字段与索引契约。
**实现差距**:当前 `InboxPoller` 固定 `afterId=0` 全量扫描,无持久化水位和独立补偿频控。
### US-02 通过兼容接口注入报文
**作为** 联调人员,**我希望** 通过 `POST /cminmsgs/send` 注入 XML,**以便** 执行回放和对拍。
**验收标准**
1. 信箱落信后返回 `CMINMSGS_ID`,响应不得暗示 PG 已入队或业务已处理。
2. 落信成功但 PG 入队失败时,由 US-01 最终补建。
3. Content-Type 支持 `text/xml``application/xml``text/plain`(默认按 UTF-8 解码);空报文、超大报文(> 10MB)及畸形 XML 返回规范错误。
4. 响应结构逐字兼容 legacy `ResponseDto`:成功返回 `{"is_success": true, "body": <CMINMSGS_ID>}`,失败返回 `{"is_success": false, "err_code": "<code>", "err_msg": "<detail>"}`
5. 生产默认沿用现役内网互信免密姿态(网关限定内部 IP 网段与审计);外露或跨网络时启用 Header 认证。
**依赖**US-01。
### US-03 严格按序且幂等地执行处理管道
**作为** 航班数据消费者,**我希望** 报文严格按信箱顺序处理,**以便** 重试不会造成倒序状态。
**验收标准**
1. 选择最小未完成 ID`PENDING``FAILED` 都占队头,退避期间后续消息不得越过。
2. identity 首绑为 `SNDR|TYPE|STYP|SEQN`;冲突转 `SKIPPED` 并记录原 ID。
3. `MALFORMED` 直接 DEAD`CODEC_ERROR/UNSUPPORTED/INFRA` 退避;attempts 或 HOL deadline 耗尽后转 `DEAD(EXHAUSTED)`
4. PUMP_JOB 不与消息形成统一全序,只在无队头或队头尚在退避窗口时执行;不得让已到期消息饥饿。
5. Redis 先应用;`MSG_EVENT``PROC_STATE→SUCCEEDED` 在同一 PG 事务提交。
6. Redis 已写、PG 未提交的窗口可幂等重放。
**依赖**US-01。
**既有基线定案**:生产幂等键默认采用 `SNDR|TYPE|STYP|SEQN``include-day-boundary=false` 禁开,防止跨日重放漏判;HOL deadline 起算时间统一固化为 `PROC_STATE.CREATED_AT`(稳定入队时间戳),消除重试刷新 `updatedAt` 导致的超时不可达缺陷。
### US-04 忽略非业务报文(KEEP
**作为** 运维人员,**我希望** 已确认无需处理的报文被明确忽略,**以便** 不产生 DLQ 噪声。
**验收标准**
1. 解码 META 后、查 Handler 前,大小写不敏感匹配 `TYPE-STYP``TYPE-*`
2. 基线为 `LDM-*``REGN-*``RSTA-*``EROR-*`;统一采用 `EROR`,消除 test 的 `ERROR` 漂移。
3. 命中后进入 `SKIPPED`,记录 `ignored:<rule>`,不创建业务事件。
4. 按 US-09 的规则回填共享信箱,并保留审计计数。
**依赖**US-03、US-09。
### US-05 应用 ADFT 与 29 类 FLOP 报文
**作为** OMMS 业务,**我希望** 正确应用增量报文,**以便** Redis 动态符合 wire 契约。
**验收标准**
1. `SCHD-ADFT` 和 29 个 FLOP 子类型均有纯函数 Handler;未知类型进入 `FAILED(UNSUPPORTED)`
2. 每类覆盖输入、Redis 变化、msg、schd、处理终态五面断言。
3. 航班不存在按 legacy KEEP 语义结束且不重试:以 `SUCCEEDED` 终态结束,按 US-09 回填共享信箱 `DATE_PROCESSED = now()`, `STATUS = 'SUCCESS'`,防止死循环。
4. 共享航班默认不直接发通知,而是更新并通知主航班;FDEL 例外定案:删除共享航班时更新主航班 MAFL 列表并发出主航班通知;若删除主航班则删除其及所有子共享关联并发出删除通知;目标航班不存在时幂等成功退出。
5. ADFT/FDEL 使用值相等比较,主/共享关系作为一次原子 Redis 变更持久化(FIX)。
6. PSDT 依赖 US-14Handler 不直接调用 admin-api。
**依赖**US-03、US-14、SIS/XSD 与 KEEP/FIX 矩阵。
### US-06 导入 RESP/DNLD 日计划快照
**作为** 航班计划使用方,**我希望** RESP 与 DNLD 共用快照流程,**以便** 主动下载和请求应答获得相同终态。
| 报文 | SnapshotFlow | REQ_TRACK | Kafka msg | 迟到/无匹配处理 |
|---|---|---|---|---|
| `SCHD-DNLD` | 是 | 不更新 | 成功后通知 | 不适用(广播/全量) |
| `SCHD-RESP` | 是 | 匹配开放 RQFD 后 DONE | 成功后通知 | 严禁更快照,转 SKIPPED 并审计 |
| `SCHD-ADFT` | 否,走 US-05 | 不更新 | 按增量规则 | 增量应用 |
**验收标准**
1. RESP/DNLD 共用 staging 流式整包校验和 SnapshotFlow,不作为普通 FLOP 增量 Handler 重复实现。
2. 主泵执行先后严格服从 I2(happens-before):主泵线程先执行 Redis Lua 完成写新代、旧代差集删除及 generation 版本 CASADFT 航班存活,FIX);Redis 执行成功后进入自有 PG 本地事务。
3. 自有 PG 本地事务原子性:同一 PG 事务内原子提交 `PROC_STATE → SUCCEEDED`、匹配开放请求的 `REQ_TRACK → DONE`(记录 completed_at 与 resp_cminmsgs_id)及 `MSG_EVENT` 出站通知。
4. 整包失败保留旧快照;相同报文重放(CAS 版本一致)幂等,不重复增代或差删。
5. 迟到(`dttm < req.sentAt`)或无匹配/过期(`EXPIRED`RESP **严禁更新快照**,转入 `PROC_STATE → SKIPPED` 并记录审计与告警,防止历史快照时光倒流覆盖新状态(对齐 G12)。
6. 覆盖首次/连续发布、DNLD→ADFT→DNLD′、RESP 匹配和崩溃窗口。
**依赖**US-03、US-08、Redis gen 协议。
### US-07 可靠、有序地投递 Kafka
**作为** Kafka 消费者,**我希望** 状态可见后有序投递,**以便** 通知不指向旧状态。
**验收标准**
1. `KAFKA:msg` 按 EVENT_ID FIFO,确认后才标 SENT。
2. `KAFKA:schd` 按周期和上限领取;同一 FLID 仅发批内最新事件。
3. 失败保持队头并退避;耗尽后 DEAD,不静默丢弃。
4. 事件包含稳定去重标识,分区键和重复投递有契约测试:`KAFKA:msg``SNDR` 作为分区键;`KAFKA:schd` 严格以 `FLID` 为分区键,确保单航班有序。
5. 生产环境对接 Kafka 2.8+ / 3.x+,生产者强制 `acks=all``enable.idempotence=true``max.in.flight.requests.per.connection=1`,严禁非幂等降级;投递失败退避重试,达上限转 `DEAD(DLQ)` 告警,绝不静默丢弃。
**依赖**US-03、现网 Kafka Broker API 版本确认(切流前 `kafka-broker-api-versions.sh` 探测 `InitProducerId(22)`;未确认前不得标 US-07 实施完成)。
### US-08 发起并跟踪 15 类 AODB 请求
**作为** 业务或运维人员,**我希望** 发起请求并跟踪生命周期,**以便** 区分登记、落信、等待、完成和超时。
**验收标准**
1. 支持 14 类 RQRD 参考请求和 1 类 RQFD-NONE 日计划请求。
2. REQ_TRACK 先登记;COUTMSGS 落信后才关联 ID 并标 SENT,落信不等于对方已发送。
3. 逐类超时与并发规则定案:同类请求并发严格为 1(新请求注册时强制同类未决请求转 `EXPIRED`);`RQFD-NONE` 超时为 60s14 类 `RQRD` 超时默认为 30s。
4. 响应经 US-01/US-03 入站:优先以报文回显 `SEQN``echoSeqn`)精确匹配开放请求;无回显时降级为时序判定(仅接受 `DTTM >= SENT_AT` 的开放请求),并在完成时同 PG 事务标 `DONE`
5. 超时或被替代的请求 EXPIRED;迟到响应(`DTTM < SENT_AT`)或无匹配响应严禁更新业务状态/快照,转 SKIPPED 并留审计。
6. **集成验收**SCHD-RESP 遵循 US-0614 类响应刷新 REF_MASTER,不与 admin-api 21 类混同。
**依赖**US-01、US-03、COUTMSGS 适配器及逐类超时参数。
### US-09 补偿回填共享信箱
**作为** 上游和运维人员,**我希望** PG 终态最终反映到共享信箱,**以便** 未处理积压语义准确。
**验收标准**
1. **解耦与异步执行**:主泵 PG 事务(更新 `PROC_STATE` 终态 + 插入 `MSG_EVENT`)内写入持久化回填意图;PG 事务提交后异步触发共享信箱回填,严禁内联同步阻塞等待共享库;回填失败绝不回滚 PG 终态。
2. **持久化补偿**:未完成或失败的回填由后台补偿任务按指数退避重试;暴露待回填积压量与最老年龄指标,持续失败触发告警。回填 SQL 具备幂等性(`UPDATE CMINMSGS SET DATE_PROCESSED = :now, STATUS = :status ... WHERE CMINMSGS_ID = :id`)。
3. **各终态回填规则矩阵**(单值锁定;库方/legacy 实测前为占位,见 §7-9):
- `SUCCEEDED`**必须回填**`DATE_PROCESSED = now()`, `STATUS = 'SUCCESS'`,补齐 META 子系统列)。
- `ignore SKIPPED`(规则忽略):**必须回填**`DATE_PROCESSED = now()`, `STATUS = 'SKIPPED'`),防止上游视作未处理积压持续重扫。
- `duplicate SKIPPED`(身份键重复):**必须回填**`DATE_PROCESSED = now()`, `STATUS = 'DUPLICATE'`),确认去重终结。
- `DEAD`(毒丸/耗尽/MALFORMED):**必须回填**`DATE_PROCESSED = now()`, `STATUS = 'DEAD'`),避免共享信箱长期未处理告警或双跑旧系统死循环。运维人工重放基于自有 PG 驱动,不依赖信箱重置。
- 非终态(`PENDING``FAILED`):**绝对禁止回填**,保持 `DATE_PROCESSED IS NULL`
4. **影子与双跑隔离**:影子实例绝对禁止回填共享信箱;与 legacy 双跑时严格保持单系统持有标记写权。
5. 查询和日志分别展示 PG 终态与信箱回填状态。
**依赖**:US-03、共享库更新权限、共享库 STATUS 值域确认(库方/legacy 对拍)。
### US-10 运维重放与故障处置
**作为** 运维人员,**我希望** 查询并安全重放失败项,**以便** 修复故障而不破坏顺序。
**验收标准**
1. 可按记录、错误类和时间查询 attempts、错误及 traceId。
2.`CODEC_ERROR/UNSUPPORTED/INFRA/EXHAUSTED` 可重放;请求含 MALFORMED 时静默跳过该记录,并返回逐项结果。
3. 重放清零 attempts/nextAttemptAt,保留错误审计,仍服从 FIFO。
4. DEAD、持续回填失败、队列年龄越界产生告警;操作记录操作者、原因、范围和结果。
**依赖**US-03、鉴权与审计。
### US-11 归档终态入站报文
**作为** 平台运维人员,**我希望** 定期归档终态报文,**以便** 控制信箱规模且不丢未完成工作。
**验收标准**
1. 默认处理接收时间早于 **1 天**的 `SUCCEEDED/SKIPPED/DEAD`;保留期可配置为 17 天;不迁 PENDING/FAILED。
2. **共享库零建表与 DML 最小化定案**:严禁向共享 MySQL 写入 `CMINMSGS_HST`,共享库严格限定为信箱两表(CMINMSGS 读/回填,COUTMSGS 写入);共享 MySQL 自身历史清理交由库方自身 DBA 策略。
3. **归档迁入自有 PG**:在自有 PostgreSQL 设计 `PROC_STATE_HST`(及 `MSG_EVENT_HST`)承载历史归档数据。
4. 重复执行幂等并记录计数;迁移失败时 fail-closed,不删除源记录。
**阶段**A。
### US-12 查询实时航班
**作为** 授权调用方,**我希望** 查询实时航班,**以便** 获得与 Redis 权威态一致的数据。
**验收标准**
1. KEEP `GET /all/flights`:返回 Redis 当前航班并过滤 `MAID != NULL` 的共享航班。
2. 固定响应、空结果、排序、分页/大小上限和一致性时点。
3. 影子只查询影子 key;接口具备认证、限流和审计。
### US-13 刷新 21 类参考主数据
**作为** 业务组件,**我希望** 从 admin-api 刷新 21 类数据到 REF_MASTER,**以便** 使用可审计的本地主数据。
**验收标准**
1. 采用 ACM2-5 清单;admin-api 拉取与 US-08 的 AODB 请求是两个入口。
2.`(RTYPE,RKEY)` 幂等 upsert,记录 SOURCE、刷新时间和批次审计。
3. 单类失败不发布半批,不破坏上个可用版本;影子默认不主动刷新生产数据。
### US-14 提供机位与登机桥数据
**作为** PSDT 处理逻辑,**我希望** 获得机位和登机桥映射,**以便** 正确计算 `abdg`
**验收标准**
1. 保留 ORMS_STAND 与 ORMS_STAND_AIRBRIDGE,和新增 21 类分开统计。
2. 通过适配器拉取并缓存;Handler 不直接 HTTP。
3. 近机位生成登机桥,远机位或清空时 `abdg` 为空;多桥规则由 golden 固定。
4. admin-api 不可用时使用最后可用版本或明确失败,不写不完整缓存。
## 3. 延后故事
### US-15 历史航班清场(DEFERRED
**作为** 平台运维人员,**我希望** 历史写成功后移出实时态,**以便** 控制 Redis 规模且不丢历史。
1. 全系统统一固定基准时区为 `Asia/Shanghai`CST, UTC+8)。
2. 阶段归属与 ES 边界定案:`HISTORY_SWEEP` 延后为阶段 B 能力(DEFERRED),阶段 A 永续以 Redis 作为航班动态权威,完全不接入 ES;不作为阶段 A 切流门禁。
3. 五条判史规则与 ES 写入留在阶段 B 启用前完成 100% golden 对拍;逐条隔离坏数据;仅历史写成功的 FLID 可由主泵删除。
4. 与快照保持单写者串行并记录计数。
## 4. 上线 Epic
### EPIC-OPS 安全运行、影子验证与切流
该范围不能作为一个故事验收,拆为:
1. **OPS-1 单写者与 fail-fast**:第二写实例拒启;生产缺依赖或管道未启用时失败并说明原因。
2. **OPS-2 可观测性**:健康、队列、最老年龄、投递延迟、回填滞后、DEAD 和一致性均有指标/告警。
3. **OPS-3 影子隔离**:独立 PG、Redis 前缀、topic、服务名;禁用真实出站和回填。
4. **OPS-4 切流回滚**:影子连续稳定对拍不少于 7 天;未解释业务字段差异严格为 0;DLQ 积压为 0MSG_EVENT 最老滞留 < 5s;切流后设立 48 小时观察期,24 小时内支持按 Runbook 平滑一键回滚。
依赖按实际进入上线范围的阶段 A 故事计算,不包含 US-15。
## 5. Legacy HTTP 工具面
| 端点 | 决定 | 目标口径 |
|---|---|---|
| `POST /cminmsgs/send` | KEEP | US-02 |
| `POST /schd/sync` | KEEP,修正 | 24 小时制、非空/区间校验;只承诺 RQFD 落 COUTMSGS |
| `GET /all/flights` | KEEP | US-12 |
| `POST /kafka/topics/{name}/msgs` | 不进生产 | 若开发仍需,另建工具并限制 topic allowlist |
| `GET /flights/migrate` | 不做 | legacy 一次性 ES 迁移工具 |
## 6. 详细文档 TODO
| 顺序 | Plane | 文档动作 | 完成条件 |
|---|---|---|---|
| 1 | ACM2-16 | 固定 RESP/DNLD/ADFT 路由 | US-06、design、ACM2-6 一致,迟到/无匹配禁更快照定案 |
| 2 | ACM2-17 | 拆归档与清场并标阶段 | US-11 属 AUS-15 DEFERREDHST 禁写,自有 PG 归档定案 |
| 3 | ACM2-19 | 增补信箱回填 | 提交后执行、持久化补偿、四终态回填、影子禁写定案 |
| 4 | ACM2-21 | 消除循环依赖并补管道边界 | US-03 仅依赖 US-01,业务例外归 US-05 |
| 5 | ACM2-15 | 增补 ignoreMsg | 规则、终态、回填和拼写确定 |
| 6 | ACM2-18 | 补查询、机位、21 类数据 | US-1214 与 US-08 分界明确 |
| 7 | ACM2-20 | 声明 HTTP 工具去留 | 五端点均有决定 |
| 8 | ACM2-22 | 吸收评审剩余项 | 水位、依赖、Broker、deadline 一致 |
| 9 | — | 同步 architecture/design/README | 权威口径、索引与阶段表一致 |
## 7. 开放问题定案结论汇总
1. **【已定案·ACM2-17】归档存储目标**:共享库 CMINMSGS_HST 严禁写入,共享库严格限定信箱两表;归档目标确认为自有 PG `PROC_STATE_HST`
2. **【已定案·ACM2-21】SEQN 重置与时钟锚点**:生产默认保持 `include-day-boundary=false`HOL deadline 锚点固化为 `PROC_STATE.CREATED_AT`(稳定入队时间戳)。
3. **【已定案·ACM2-20/25】compat 接口契约**:支持 text/xml、application/xml 与 text/plain;逐字兼容 legacy `ResponseDto`(成功 `is_success`+`body`;失败 `is_success`+`err_code`+`err_msg`,不用 `msg`);生产保持内网信任姿态。
4. **【已定案·ACM2-21】航班不存在与 FDEL**:航班不存在按 KEEP 正常结束且回填信箱;FDEL 共享航班更新主航班 MAFL 并通知,主航班删除清空关联。
5. **【已定案·ACM2-16/18】15 类请求生命周期**:同类并发严格为 1;RQFD 超时 60sRQRD 超时 30s;优先 SEQN 回显,无回显退化 DTTM 时序判定;迟到/无匹配禁更快照。
6. **【已定案·ACM2-22/23】Kafka 生产契约**:强制 `acks=all``idempotence=true`,分区键按 FLIDschd)/SNDR(msg)固化;严禁非幂等降级;现网 Broker API 版本未确认前 US-07 不得标实施完成;README 不提供生产降级 env。
7. **【已定案·ACM2-17】时区与判史边界**:统一 `Asia/Shanghai` 时区;HISTORY_SWEEP 延后至阶段 B,阶段 A 不依赖 ES,Redis 永续动态权威。
8. **【已定案·ACM2-22】对拍与回滚阈值**:影子对拍至少 7 天;业务差异 0 容忍;DLQ 积压为 0;切流后 48 小时保驾、24 小时可平滑回滚。
9. **【已定案·ACM2-26】信箱回填 STATUS 值域**`SUCCEEDED``SUCCESS`ignore `SKIPPED``SKIPPED`duplicate `SKIPPED``DUPLICATE``DEAD``DEAD`(库方/legacy 实测前为占位;若库方禁新值则仅用 legacy 已用集合)。