From 346929ea5d276cf95682769d86fed3e9ab337faf Mon Sep 17 00:00:00 2001 From: windyboy Date: Mon, 7 Sep 2026 14:15:07 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=BA=8C=E8=BD=AE=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E6=94=B6=E6=95=9B=20ACM2-23=EF=BD=9E27?= =?UTF-8?q?=EF=BC=88Kafka/=E4=BE=9D=E8=B5=96/ResponseDto/STATUS/=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 64 ++++++++- docs/architecture.md | 49 +++---- docs/design.md | 60 ++++++--- docs/user-stories-todo.md | 50 +++++++ docs/user-stories.md | 272 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 446 insertions(+), 49 deletions(-) create mode 100644 docs/user-stories-todo.md create mode 100644 docs/user-stories.md diff --git a/README.md b/README.md index d515f30..9a2287b 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,57 @@ PROC_STATE / MSG_EVENT / PUMP_JOB / REQ_TRACK / REF_MASTER(PG 方言)。全 - **阶段 B(FLIGHT_STATE)**:缓做不落表。 - 影子对拍:自有 PG 开独立 schema;共享信箱为单信箱无法双写,影子输入=只读水位/回放口径。 + +## 本地开发依赖中间件栈(Podman / Docker Compose,ACM2-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 :9092 + ``` +2. **门禁判定**:若输出中 `InitProducerId(22)` 为 **可用** → 保持现代高可靠默认(`acks=all`、`idempotence=true`、`max.in.flight=1`)。 +3. **阻塞切流**:若 `InitProducerId(22)` 为 **UNSUPPORTED**(Broker < 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`)属 U05,dev stub 尚未覆盖。 +> 已知边界:text/plain compat 写 415 属 U16 缺口(契约对拍后固化);`application-dev.yml` 数据源配置 +> 已对齐为自有 PG 与共享 MySQL 信箱(ACM2-12/13);测试侧端到端语义由 PipelineSmokeTest +> (内嵌上下文,compat 写路径)覆盖。**生产主路径**(JDBC 轮询 `InboxPoller`)属 U05,dev stub 尚未覆盖。 > 注解处理:Kotlin 侧经 KSP(`kotlin-ksp` + `micronaut-inject-kotlin`)生成 Micronaut > BeanDefinition(U01);若 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-01~US-14、延后清场 US-15、上线 Epic、 + legacy HTTP 去留及逐项文档 TODO;包含验收标准、依赖、实现差距与待确认问题。 +- [docs/user-stories-todo.md](docs/user-stories-todo.md):ACM2-15~22 对应的文档整改清单、 + 已完成落点、待 Plane/产品确认事项与验证门禁。 - [docs/legacy/](docs/legacy/):外部参考/基线材料(自 legacy 仓库拷贝,非本系统文档)—— `msgexchange-api-legacy-user-stories.md`(legacy 行为对拍基线,ACMA-4)、`unisysaodbsis.xsd`、 `SIS_AODB_RMS-V0.1.md`(消息结构唯一事实源;CIIMS 中间件交换模型)。 diff --git a/docs/architecture.md b/docs/architecture.md index e1295f5..f06d1f6 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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**,评审与实施计划(U01–U30)**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) | 编译期 DI:KSP(`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 实装属 U05(ACM2-12) | +| 持久化 | **自有 PostgreSQL**(全部内部状态)+ 共享 MySQL 信箱 | 自有库:消息管道 PROC_STATE/MSG_EVENT + PUMP_JOB/REQ_TRACK + 21 类 REF_MASTER(迁移 `db/migration`);共享库主契约为 CMINMSGS/COUTMSGS DML。JDBC 仓储与 InboxPoller 已有初版,事务/补偿/出站适配仍属 U05(ACM2-12) | | 权威存储 | Redis(航班动态 flightInfo + 快照 gen) | 仅主泵线程写(I5);Lua 原子覆盖/版本推进(gen 协议重设计属 U09) | -| 投递 | Kafka(acks=all + 幂等) | outbox 模式,经 MSG_EVENT 表中转 | +| 投递 | Kafka(acks=all + 幂等;切流前须确认 Broker 支持 InitProducerId(22),严禁非幂等降级) | outbox 模式,经 MSG_EVENT 表中转 | | 投影(阶段 B) | Elasticsearch(历史) | **阶段 B 缓做(ACM2-12)**:FLIGHT_STATE 不落表,Redis 永续动态权威;历史投影链路不变 | | 注册中心 | Eureka(Micronaut 原生键) | 服务名契约 `msgexchangeapi`(影子 `msgexchangeapi-shadow`)——**U17 未落地**:当前注册名仍取 `micronaut.application.name`(=msgexchange-nextgen),`msgx.service-name` 无运行时消费方(见 §8 与 design.md §9) | | 可观测 | logstash TCP(Async 包装)+ MDC traceId + 自定义健康指示器 | 见 [design.md §8](design.md) | @@ -75,7 +76,7 @@ │ │ │ decode(XmlCodec) │ │ │ │ identity 绑定(I3) │ │ │ │ Handler.decide(纯函数) │ - │ │─Schd DNLD──▶ SnapshotFlow(流程4) │ + │ │─Schd RESP/DNLD▶ SnapshotFlow(流程4) │ │ │─PUMP_JOB───▶ JobExecutor(作业窗口,决策1) │ │ │ │ │ ├────Redis Lua──▶ Redis flightInfo(A权威) │ @@ -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 + join(U07)。仅当 `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);不解析报文 | 流程 1,I3 | `InboxPoller`(U05)`InboxController` `InboxService` | -| `processing/` | 主泵:FIFO 领取、解码、identity 绑定、纯函数决策、自有 PG 事务2 | 流程 2/4,I1/I2/I5 | `Pump` `MessageProcessor` `SnapshotFlow` `Identity` `Handler(Registry)` | +| `processing/` | 主泵:FIFO 领取、ignoreMsg、identity 绑定、纯函数决策、RESP/DNLD 快照、自有 PG 事务2 | 流程 2/4,I1/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)。 - **阶段 B(FLIGHT_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 数据层/信箱适配层属 U05;gen→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. 部署与安全姿态 diff --git a/docs/design.md b/docs/design.md index e2bfb90..80f5600 100644 --- a/docs/design.md +++ b/docs/design.md @@ -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-10(U01–U30)**。文中标注「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) → - decode(MALFORMED→DEAD / CODEC_ERROR→FAILED)→ identity 首绑 - (`tryBindIdentity` 失败 → SKIPPED,I3)→ Schd DNLD → `SnapshotFlow` → + decode(MALFORMED→DEAD / CODEC_ERROR→FAILED)→ ignoreMsg 匹配(LDM/REGN/RSTA/EROR, + 命中→SKIPPED,回填遵循 US-09;当前未实装)→ identity 首绑 + (`tryBindIdentity` 失败 → SKIPPED,I3)→ Schd RESP/DNLD → `SnapshotFlow`(ACM2-16 定案: + DNLD 与 RESP 均走 SnapshotFlow;RESP 成功后在同事务完成匹配开放 RQFD 的 `REQ_TRACK→DONE`; + 迟到或无匹配 RESP 严禁更新快照,直接转 SKIPPED 并审计)→ 其余 → `Handler.decide(redis.hgetAllFlightInfo(), msg)` → 阶段 A:Redis 先写(I2 happens-before,TODO 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/D2,ACM2-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,检查开放 RQFD(dttm < sentAt 或无匹配/已过期 → 严禁更新快照,转 SKIPPED 并审计) → Redis Lua SNAPSHOT_REPLACE(同一 hash 原子「覆盖新代+按代差删」,删除集=旧代flids−新代) - → gen 版本推进(ACM2-12:gen 随 flightInfo 同在 Redis,Lua 内原子版本 CAS—— - 目标实现;现 RefDataRepository/putGenIfVersion 为过渡占位) - → 自有 PG SUCCEEDED -``` + → gen 版本推进(ACM2-12:gen 随 flightInfo 同在 Redis,Lua 内原子版本 CAS) + → 自有 PG 本地事务(原子性):PROC_STATE→SUCCEEDED + (RESP 匹配时)REQ_TRACK→DONE + MSG_EVENT 插入 + → PG 提交后异步触发信箱回填(持久化补偿,ACM2-19) + +数据流说明:SCHD-RESP 处理依赖 US-08 已登记的开放 REQ_TRACK;US-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_SWEEP(3:30 清场,I4 同步链):判史 → 同步写 ES → 仅删成功集。 **占位门禁(U10/T07 修订)**:ES saveSync 接线前 `pickHistory` 恒空集、删除量恒 0, 禁止「全量可删」fail-open 默认;现役五条判史规则 golden 通过后才允许接线。 -- ARCHIVE(3:00):1 天前且仅终态(SUCCEEDED/SKIPPED/DEAD)迁 CMINMSGS_HST(共享库, - 外部副作用;TODO U05 批次)。 + **阶段归属**:按 ACM2-12 阶段 B 缓做口径标为 DEFERRED,不作为阶段 A 切流门禁;启用前 + 重新确认“历史链路不变”与阶段 B 投影范围、ES/OpenSearch 产品边界。 +- ARCHIVE(3: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 数据层无实装 + **InboxPoller(JDBC 轮询/重扫)** 未实装(当前仅 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 去留, +避免把当前实现、目标设计和遗留兼容行为混成同一项承诺。 diff --git a/docs/user-stories-todo.md b/docs/user-stories-todo.md new file mode 100644 index 0000000..177eda5 --- /dev/null +++ b/docs/user-stories-todo.md @@ -0,0 +1,50 @@ +# 用户故事文档整改 TODO + +> 本清单只跟踪文档收敛;实现任务仍由对应 Plane 工作项管理。新建 Plane 汇总项会与 +> ACM2-15~22 重复,因此在确认合并策略前不重复创建。 + +## P0:先消除范围冲突 + +- [x] **ACM2-16 — RESP/DNLD 路由**:在 `user-stories.md` 增加报文路由矩阵;RESP/DNLD + 共用 SnapshotFlow,RESP 成功后再完成匹配的 RQFD 请求;ADFT 保持增量 Handler。 +- [x] **ACM2-17 — 阶段边界**:ARCHIVE 独立为阶段 A US-11;HISTORY_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 删生产降级 env;US-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/23~27 重叠收束)。 +- [x] **ACM2-22**:写入二轮审查闭环评论(已验证属实 + 新发现 1~5 + ACM2-23~27)。 + +## 验证清单 + +- [x] `architecture.md`、`design.md`、`user-stories.md` 的 RESP/DNLD 和阶段表述一致。 +- [x] README 指向新的故事范围。 +- [x] Markdown 变更通过 `git diff --check`。 +- [ ] Plane ACM2-23~27 已 commit 后转 Done。 +- [ ] 实现尚未随本文修改;后续代码 PR 必须补 FIFO、快照、回填和双库集成测试。 diff --git a/docs/user-stories.md b/docs/user-stories.md new file mode 100644 index 0000000..1ca9843 --- /dev/null +++ b/docs/user-stories.md @@ -0,0 +1,272 @@ +# msgexchange-v2 用户故事草案 + +> 依据 `architecture.md`、`design.md`、Plane ACM2-3/5/6/7/12/15~22 与 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": }`,失败返回 `{"is_success": false, "err_code": "", "err_msg": ""}`。 +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:`,不创建业务事件。 +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-14,Handler 不直接调用 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 版本 CAS(ADFT 航班存活,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` 超时为 60s,14 类 `RQRD` 超时默认为 30s。 +4. 响应经 US-01/US-03 入站:优先以报文回显 `SEQN`(`echoSeqn`)精确匹配开放请求;无回显时降级为时序判定(仅接受 `DTTM >= SENT_AT` 的开放请求),并在完成时同 PG 事务标 `DONE`。 +5. 超时或被替代的请求 EXPIRED;迟到响应(`DTTM < SENT_AT`)或无匹配响应严禁更新业务状态/快照,转 SKIPPED 并留审计。 +6. **集成验收**:SCHD-RESP 遵循 US-06;14 类响应刷新 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`;保留期可配置为 1~7 天;不迁 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 积压为 0;MSG_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 属 A,US-15 DEFERRED;HST 禁写,自有 PG 归档定案 | +| 3 | ACM2-19 | 增补信箱回填 | 提交后执行、持久化补偿、四终态回填、影子禁写定案 | +| 4 | ACM2-21 | 消除循环依赖并补管道边界 | US-03 仅依赖 US-01,业务例外归 US-05 | +| 5 | ACM2-15 | 增补 ignoreMsg | 规则、终态、回填和拼写确定 | +| 6 | ACM2-18 | 补查询、机位、21 类数据 | US-12~14 与 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 超时 60s,RQRD 超时 30s;优先 SEQN 回显,无回显退化 DTTM 时序判定;迟到/无匹配禁更快照。 +6. **【已定案·ACM2-22/23】Kafka 生产契约**:强制 `acks=all` 与 `idempotence=true`,分区键按 FLID(schd)/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 已用集合)。