From b4ef787e93bbb92ba249571bbddb0306b7aae036 Mon Sep 17 00:00:00 2001 From: windyboy Date: Mon, 21 Sep 2026 07:54:55 +0800 Subject: [PATCH] =?UTF-8?q?docs(acm2):=20=E9=87=8D=E6=9E=84=20spec=20?= =?UTF-8?q?=E5=B9=B6=E6=95=B4=E7=90=86=20Q=20=E7=BC=96=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - specification:直白说法、编号一览、合并 Q 台账、补 Q2/Q15 - interface-contract:POST /cminmsgs/send 待确认项指向 Q15 - 代码:SRVT/VIPF 清空语义 Q11 → Q2 --- docs/contracts/interface-contract.md | 2 +- docs/specification.md | 277 +++++++++--------- .../omms/msgexchange/codec/SisMessageBody.kt | 4 +- .../omms/msgexchange/codec/SisWireMapper.kt | 2 +- .../infra/metrics/PipelineCounters.kt | 2 +- .../infra/metrics/PipelineMetrics.kt | 2 +- .../gzzn/omms/msgexchange/processing/Pump.kt | 2 +- 7 files changed, 145 insertions(+), 146 deletions(-) diff --git a/docs/contracts/interface-contract.md b/docs/contracts/interface-contract.md index 2c95783..632ca88 100644 --- a/docs/contracts/interface-contract.md +++ b/docs/contracts/interface-contract.md @@ -10,7 +10,7 @@ | 接口 | 请求契约 | 成功响应契约 | 失败契约 | 尚需确定 | |---|---|---|---|---| -| `POST /cminmsgs/send` | 请求体是 XML 原文;接受 `text/xml`、`application/xml`、`text/plain`,默认 UTF-8;仅限内网,网络层限制来源。 | 报文写入 `CMINMSGS` 后返回信箱编号;写入的报文与上游投递走同一条处理路径、效果一致;该响应只证明已落信,不证明业务处理或下游投递(`US-02`)。 | 空报文、超大小上限、非法 XML 不落信并返回错误;写信失败不返回编号;XML 解析禁用外部实体和外部资源访问。 | 大小上限、请求编码与 `Content-Type` 的精确处理规则、HTTP 状态码、成功/失败响应体字段及样例。 | +| `POST /cminmsgs/send` | 请求体是 XML 原文;接受 `text/xml`、`application/xml`、`text/plain`,默认 UTF-8;仅限内网,网络层限制来源。 | 报文写入 `CMINMSGS` 后返回信箱编号;写入的报文与上游投递走同一条处理路径、效果一致;该响应只证明已落信,不证明业务处理或下游投递(`US-02`)。 | 空报文、超大小上限、非法 XML 不落信并返回错误;写信失败不返回编号;XML 解析禁用外部实体和外部资源访问。 | 大小上限、请求编码与 `Content-Type` 的精确处理规则、HTTP 状态码、成功/失败响应体字段及样例(`Q15`)。 | | `POST /schd/sync` | 触发一次 `RQFD` 日计划请求;请求字段尚未定义,登记与落信规则见「在途与作废」。 | 响应内容尚未定义;无论表示登记还是落信,都不代表 AODB 已收到。 | 错误响应尚未定义。 | 请求字段与时间格式、成功响应表示已登记还是已落信(`Q16`)、状态码、响应字段及样例。 | | `GET /all/flights` | 无已定义的请求字段;从 Redis 投影读取当前全部动态航班,不含共享航班,与网页客户端同源(`US-12`)。 | 返回查询到的全部航班,不分页;JSON 由消息文档中的 XML 结构转换而来(`Q21`)。 | Redis 异常时返回错误,不能返回空列表伪装成功。 | 状态码和错误响应样例;外层包装是否沿用旧 `ResponseDto`。 | diff --git a/docs/specification.md b/docs/specification.md index 6be814b..8b55f5b 100644 --- a/docs/specification.md +++ b/docs/specification.md @@ -1,195 +1,194 @@ -# 规范:术语、约定、前提、不变量与能力边界 +# 规范:术语、约定、前提、不变量与承诺范围 -本文整理 msgexchange-v2 对外接口约定(`C-x`)、前提条件(`PRE-x`)、系统不变量(`INV-x`)、能力边界(`CLM-x`)、待确认事项(`Qn`)与已知偏差(`G-NAME`),并统一给出术语。条款依据 [requirements.md](requirements.md) 与 [architecture.md](architecture.md) 编写;字段联调草案另见 [contracts/interface-contract.md](contracts/interface-contract.md),对外承诺以本文件为准。 +本文列出 msgexchange-v2 与外部系统的约定(`C-x`)、运行前提(`PRE-x`)、必须始终成立的规则(`INV-x`)、能对外承诺的内容(`CLM-x`)、待定事项(`Qn`)和已知缺口(`G-NAME`)。依据 [requirements.md](requirements.md) 与 [architecture.md](architecture.md);HTTP/Kafka/Redis 字段草案见 [contracts/interface-contract.md](contracts/interface-contract.md)。 -`C-x`、`PRE-x`、`INV-x`、`CLM-x`、`Qn`、`G-NAME` 只在本文件定义,编号规则见 [README.md](README.md)。 +各编号只在本文件定义;编号稳定、不重排,规则见 [README.md](README.md)。 -约定条目的标记: +标注 `(待确认 Qn)` 需对方确认;标注 `(本系统承诺)` 不依赖对方同意;未标注视为已定。字段未定另起「待确认:」并指向 `Qn`。 -- 需要对方确认的,末尾标 `(待确认 Qn)`。 -- 内容已定、个别字段未定的,另起一行以「待确认:」列出并指向 `Qn`。 -- 本系统单方遵守、不依赖对方认可的,末尾标 `(本系统单方承诺)`。 -- 未标注的,表示需求或架构已定,本系统按此执行。 +## 编号一览 + +| 族 | 所在小节 | 含义 | +|---|---|---| +| `C-1`~`C-11` | 约定 | 对外约定 | +| `PRE-x` | 前提 | 运行前提 | +| `INV-1`~`INV-11` | 不变量 | 必须始终成立的规则 | +| `CLM-1`~`CLM-7` | 声明边界 | 能否对外承诺 | +| `Q1`~`Q25` | 待确认事项台账 | 待定事项(`Q19`、`Q20` 未分配) | +| `G-*` | 当前已知偏差 | 已知实现缺口 | ## 1. 术语 | 术语 | 含义 | |---|---| -| 上游 | 产生报文的 AODB,报文经 CIIMS adapter 写入信箱。 | -| 信箱 | 共享 MySQL 的 `CMINMSGS`(入站)与 `COUTMSGS`(出站),归库方所有;边界见 `C-2`。 | -| 库方 | 信箱所在的共享 MySQL 管理方,即 CIIMS adapter 方。 | -| 处理标记 | 即信箱行上的处理时间:为空表示未处理,处理完成时写入完成时刻。 | -| 落信 | 报文写入信箱成为一行;入站由 CIIMS adapter 或兼容入口写入,出站由本系统写入 `COUTMSGS`;入站报文写进信箱后可能尚未登记。 | -| 登记 | 本系统在自有 PG 为这条报文建立处理记录,排队等待处理。 | -| 处理完成 | 这条消息的结果已经确定——业务改动生效,或明确跳过、进入死信;之后才回填信箱标记、发 Kafka。 | -| 回填 | 处理完成后,把完成时刻写回信箱行的处理时间字段,告知上游该消息已处理。 | -| 投递 | 读待发事件,发往 Kafka。 | -| Redis 投影 | 供网页客户端(`GET /all/flights`)查询的航班投影,内容来自自有 PG 当前态;读写边界见 `INV-11`。 | -| 自有 PG | 本系统唯一的业务数据库;航班当前态、管道记录与静态参考数据都在这里。 | -| 权威 | 航班当前态以自有 PG 为准(`INV-5`)。 | -| 出站请求 | 经 `COUTMSGS` 发向 AODB 的 `RQRD` 参考数据请求与 `RQFD` 日计划请求,消费方为 CIIMS adapter。 | -| 运营航班显示界面 | 需求所称网页客户端:Kafka 侧称运营航班显示界面,查询侧称网页客户端(`GET /all/flights` / Redis)。 | -| 航班历史 | 已结束航班写入 Elasticsearch 后的副本;写成功后才从实时数据删除。 | -| 静态参考数据 | 13 类基础数据与资源状态,写自有 PG 的独立数据表组(逻辑视图 `REF_MASTER`,物理形态由内部迁移确定),admin-api 直接只读。 | +| 上游 | 发报文的 AODB;报文经 CIIMS adapter 进信箱。 | +| 信箱 | 共享 MySQL 的 `CMINMSGS`(入站)与 `COUTMSGS`(出站);归库方管理,边界见 `C-2`。 | +| 库方 | 管理共享 MySQL 信箱的一方(CIIMS adapter)。 | +| 处理标记 | 列 `CMINMSGS_DATE_PROCESSED`:空=未处理,有值=已处理时间。 | +| 写入信箱 | 报文存成信箱里的一行。入站由 adapter 或 `POST /cminmsgs/send` 写入;出站由本系统写 `COUTMSGS`。写入后未必马上开始处理。 | +| 建立处理记录 | 本系统在自有 PG 记一条待处理记录并排进队列。 | +| 处理完成 | 结果已定(已改库、明确跳过、或无法处理并归档);之后才写处理标记并发 Kafka。 | +| 写回处理时间 | 处理完成后,把时刻写入 `CMINMSGS_DATE_PROCESSED`。 | +| 发到 Kafka | 把待发送的变更事件写入 Kafka。 | +| 网页客户端 | 读 Kafka(`msg`/`schd`)或调 `GET /all/flights`(Redis)的前端。 | +| Redis 航班快照 | 网页客户端查询用的航班数据,从自有 PG 同步;规则见 `INV-11`。 | +| 自有 PG | 本系统主库:航班、处理记录、静态参考数据。 | +| 出站请求 | 本系统经 `COUTMSGS` 向 AODB 发 `RQRD`(参考数据)或 `RQFD`(日计划)。 | +| 航班历史 | 已结束航班写入 Elasticsearch;写入成功后才从实时库删除。 | +| 静态参考数据 | 13 类基础数据与资源状态;存自有 PG,admin-api 只读。 | ## 2. 约定 ### 2.1 共享信箱(库方) -- **C-1** 本系统清理已处理的入站信箱行(MySQL `CMINMSGS`):回填完成且超过保留期后才删;保留期可配置,按约 1 个月(`Q9`)。处理未完成或回填未完成的行不删。 -- **C-2** 共享 MySQL 不做 Schema 变更,本系统只读写 `CMINMSGS` 与 `COUTMSGS` 两张表。 +- **C-1** 删除已处理完毕的入站行(`CMINMSGS`):处理标记已写入,且超过保留期(可配置,默认约 1 个月)。未处理完或未写回处理时间的行不删。 +- **C-2** 共享 MySQL 不改表结构;本系统只读写 `CMINMSGS` 与 `COUTMSGS`。 ### 2.2 上游(AODB / SIS) -- **C-3** 业务身份由 `SNDR`、`TYPE`、`STYP`、`SEQN` 四字段组合;`SEQN` 自增,极少重置(消息服务器重启),重置后不会与旧消息冲突。 -- **C-4** 出站请求写入 `COUTMSGS`;交付承诺止于落信。编码:`SNDR=OMMS`;`SEQN` 本系统自建序列;`DTTM` 北京时间 `YYYYMMDDHHMMSS`;`RQRD` 子类型以 SIS 为准共 14 类;`RQFD` 为 `STYP=NONE`,全量同步不带 `STDB`/`STDE` 等筛选(`Q13`)。 -- **C-5** 航班删除为标记删除,主航班与共享航班各自独立标记。 -- **C-6** 日计划快照里没有携带的字段视为 AODB 已删除该值,本地同步清除(`US-07` AC3)。 +- **C-3** 一条报文的身份 = `SNDR` + `TYPE` + `STYP` + `SEQN`。`SEQN` 自增,极少在消息服务器重启时重置;重置后不与历史冲突。 +- **C-4** 出站请求写入 `COUTMSGS`;本系统只保证写入信箱,不保证 AODB 收到。编码:`SNDR=OMMS`;`SEQN` 本系统生成;`DTTM` 北京时间 `YYYYMMDDHHMMSS`;`RQRD` 共 14 种子类型(以 SIS 为准);`RQFD` 的 `STYP=NONE`,全量不带 `STDB`/`STDE` 筛选。 +- **C-5** 删航班只打删除标记;主航班与共享航班各自独立标记。 +- **C-6** 日计划快照里没带的字段,视为 AODB 已删掉该值,本地也清掉(`US-07` AC3)。 ### 2.3 HTTP 入口 -- **C-7** `POST /cminmsgs/send`:把 XML 写进入站信箱,与 adapter 投递走同一套处理。成功返回消息编号(仅表示已入库、未处理);失败不返回编号。支持 `text/xml`、`application/xml`、`text/plain`(UTF-8);空、超长、格式错的不写;解析不拉外部资源。内网,访问由网络配置控制。仅内部联调。大小上限、HTTP 状态码、成功/失败响应体字段及样例属接口契约「HTTP」,尚未确定。 -- **C-8** `POST /schd/sync`:登记一次 `RQFD` 日计划请求;同子类型已有在途时新请求不落信,待在途结案后再写入 `COUTMSGS`(架构「主流程」、`US-09` AC1)。请求参数与 HTTP 响应待 `Q16`。 +- **C-7** `POST /cminmsgs/send`:XML 写进入站信箱,与 adapter 走同一套处理。成功返回消息编号(只表示已写入、尚未处理);失败不返回编号。接受 `text/xml`、`application/xml`、`text/plain`(UTF-8);拒收空报文、超长、非法 XML;解析禁止访问外部资源。内网访问由网络层控制;仅供内部联调。 + - 待确认:大小上限、HTTP 状态码与响应体 → `Q15`。 +- **C-8** `POST /schd/sync`:登记一次 `RQFD` 日计划请求。同类型若还有未处理完的请求,新请求先不写信箱,等旧的处理完再写 `COUTMSGS`(`US-09` AC1)。 + - 待确认:请求参数与 HTTP 响应 → `Q16`。 -### 2.4 下游(admin-api、网页客户端与运营航班显示界面) +### 2.4 下游(admin-api、网页客户端) -- **C-9** 航班变更发到 Kafka 主题 `msg`(单条)和 `schd`(批量);`schd` 沿用旧系统粒度:两次 tick 之间积累的变化序列化成一个 `SCHD.FLTR` 数组 JSON,整批作为单条 record 发出(`Q1`),空字段不输出,没有变化就不发;删除航班不进 `schd`,只由 `msg` 发一条删除通知;不设消息键。同一条可能发多次。 -- **C-10** 静态参考数据写入本系统数据库,供 admin-api 只读;本系统不调用 admin-api。 -- **C-11** 航班投影写入 Redis,供读取全体动态航班;`GET /all/flights` 与网页客户端读同一份;返回 JSON 由消息文档中的 XML 结构转换而来(`Q6`、`Q21`)。 +- **C-9** 航班变更发 Kafka:`msg` 一条变更一条消息;`schd` 把两轮采集之间积累的变化合成一条消息,内容是 `SCHD.FLTR` JSON 数组。空字段不输出,没变化不发;删航班只走 `msg`;不设 message key;可能重复投递(待确认 `Q1`)。 +- **C-10** 静态参考数据写本系统库,admin-api 只读;本系统不调用 admin-api。 +- **C-11** 航班快照写 Redis;`GET /all/flights` 与网页客户端读同一份;JSON 由报文 XML 转换。 + - 待确认:`MAFL` 是否一并提供(`Q6`);状态码与响应包装(`Q21`)。 ## 3. 前提 -前提失效时不变量必须整体重估。 +前提不成立时,下文规则需整体重评。 -| 编号 | 前提 | 若不成立的影响 | 状态 | -|---|---|---|---| -| PRE-1 | 测试环境与生产隔离:独立的数据库、Redis 与 Kafka 主题,不连接生产信箱 | 测试结论与隔离验收(`OPS-3`)失效 | 部署与配置约束 | +| 编号 | 前提 | 若不成立 | +|---|---|---| +| PRE-1 | 测试与生产隔离:各自独立的 DB、Redis、Kafka 主题,测试不连生产信箱 | `OPS-3` 验收无效 | ## 4. 不变量 -### 4.1 管道 +### 4.1 消息处理 -- **INV-1** 处理时间仍为空的信箱行会登记到本地;同一编号只登记一次。重扫或重启不会重复登记,也不会漏掉未登记的行。 -- **INV-2** 只有本地处理结束后,才写信箱里的处理时间;本地记「已结束」时,必须同时记下还要去更新信箱,两笔记在同一次写入里。回填只把空标记写成完成时刻,不覆盖已有值(本系统单方承诺)。 -- **INV-3** 航班增量与删除:改状态和记「待发 Kafka」同一次写入;记「已结束」和「还要更新信箱」另一次写入,且须在 Redis 写成功之后。处理未结束前不发 Kafka(`US-03` AC4)。日计划与静态参考数据见架构「主流程」。 -- **INV-4** 日计划整份报文校验不过时,本地航班数据一律不改(`US-07` AC1)。 +- **INV-1** 处理标记为空的信箱行会被建立处理记录;同一编号只建一次。重扫或重启不重复、不遗漏。 +- **INV-2** 只有本地处理结束后才写回处理时间;标记「处理结束」时必须同一次记下「还要写回信箱」。只填空的处理标记,不覆盖已有值(本系统承诺)。 +- **INV-3** 航班增量与删除:改库与记「待发 Kafka」同一次提交;「处理结束」与「还要写回信箱」在 Redis 写成功后再记一次。处理完成前不发 Kafka(`US-03` AC4)。日计划与静态参考数据见架构「主流程」。 +- **INV-4** 日计划整报校验失败时,本地航班数据不改(`US-07` AC1)。 -### 4.2 航班域 +### 4.2 航班 -- **INV-5** 航班当前态只以自有 PG 为准;信箱、Redis、Kafka、展示视图都不是。 -- **INV-6** `FLID` 唯一。 -- **INV-7** 日计划:快照里没有的航班标记删除、登记删除事件并从 Redis 去掉;报文没带的字段本地清掉(`US-07` AC2/AC3)。 -- **INV-8** 航班标成已删除后,要从 Redis 去掉;去掉成功才算这条消息处理完(`US-06` AC1)。 -- **INV-9** 日计划可以分批写库,但整份写完并且 Redis 按这份结果刷完,才算处理完;失败就整份重来(`US-07` AC4/AC5)。 +- **INV-5** 航班当前数据以自有 PG 为准。 +- **INV-6** `FLID` 全局唯一。 +- **INV-7** 日计划:快照里没有的航班打删除标记、记删除事件、从 Redis 删掉;快照没带的字段本地清掉(`US-07` AC2/AC3)。 +- **INV-8** 已打删除标记的航班必须从 Redis 删掉;删掉才算这条消息处理完成(`US-06` AC1)。 +- **INV-9** 日计划可以分批写 PG,但整份 PG 写完且 Redis 按快照刷完才算完成;失败则全部重来(`US-07` AC4/AC5)。 -### 4.3 Redis 投影 +### 4.3 Redis 航班快照 -- **INV-10** Redis 写成功,这条消息才算处理完;写失败就还没处理完,不回填、不发 Kafka,下轮再写(`US-05` AC4)。本地已经写进库的,不因为 Redis 失败而撤掉(`US-03` AC3)。 -- **INV-11** Redis 航班投影只由本系统维护;查询与网页客户端读同一份;Redis 异常时报错,不交空列表(`US-12`)。 +- **INV-10** Redis 写成功才算处理完成;失败则不写回处理时间、不发 Kafka、下轮重试(`US-05` AC4)。已写入 PG 的不因 Redis 失败而撤销(`US-03` AC3)。 +- **INV-11** Redis 航班快照只由本系统维护;查询与网页客户端同源;Redis 出错时返回错误,不能用空列表假装成功(`US-12`)。 ## 5. 声明边界 | 编号 | 承诺 | 依赖 | 现在能否作出 | 限制或原因 | |---|---|---|---|---| -| CLM-1 | 同一消息再处理不会多出一份业务效果 | `US-03` | 不能 | 各类报文的细则未补齐(`G-FLOP-IDEMPOTENT`) | -| CLM-2 | 按信箱编号升序处理 | `US-03` AC1 | 能 | — | -| CLM-3 | 主题 `msg` 上同一 `FLID` 内按发送顺序保序 | `US-08` AC2、`C-9` | 能 | `msg` 单分区;至少一次投递时重发可能重复(`D2`) | -| CLM-4 | 出站请求写入共享出站表 `COUTMSGS` | `C-4` | 能 | 只保证写入信箱,不保证 AODB 收到 | -| CLM-5 | 消息在固定时限内处理完 | — | 不能 | 需求未定完成时限 | -| CLM-6 | 容量与吞吐量级 | — | 不能 | 需求未给日量与峰值,有数据后再估 | +| CLM-1 | 同一条消息重复处理不会重复生效 | `US-03` | 不能 | 各类报文细则未写完(`G-FLOP-IDEMPOTENT`) | +| CLM-2 | 按信箱编号从小到大处理 | `US-03` AC1 | 能 | — | +| CLM-3 | 主题 `msg` 上,同一 `FLID` 内按发送顺序排列 | `US-08` AC2、`C-9` | 能 | `msg` 单分区;至少一次投递时可能重复(`D2`) | +| CLM-4 | 出站请求写入 `COUTMSGS` | `C-4` | 能 | 只保证写入信箱,不保证 AODB 收到 | +| CLM-5 | 消息在固定时间内处理完 | — | 不能 | 需求未定时限 | +| CLM-6 | 容量与吞吐量级 | — | 不能 | 需求未给日量与峰值 | | CLM-7 | 配置不完整时拒绝启动 | `OPS-1` | 能 | — | ## 6. 待确认事项台账 -### 6.1 待对方确认 +`Q1`~`Q25` 按编号排列。答复后更新对应 `C-x` 或条款,步骤见 [README.md](README.md)「维护清单」。 -| 编号 | 事项 | 当前假定 | 影响 | +| 编号 | 状态 | 事项 | 说明 | |---|---|---|---| -| Q1 | `schd` 的 Kafka record 粒度 | 已定原则:按旧系统,两次 tick 之间积累的整批序列化成一个 JSON 数组作为单条 record 发出(`C-9`) | 消费方未确认按此格式解析 | -| Q3 | 现场会发但 SIS 未定义的子类型(靠桥、延误等)的报文形态与逐类终态 | 按现有处理逻辑延续,不得因 SIS 未记载就丢掉(`US-05` AC1) | 逐类终态与幂等规则未定(`G-FLOP-SEMANTICS`) | -| Q4 | 上游重发时是否可能改发正文 | 报文不可变,绑定身份后不比对内容(implementation.md「消息、身份与决策」) | 改发正文的重发会被判为重复并跳过 | -| Q5 | `msg` 的 value 字段与类型、编码方式、变更与删除的区分方式 | 沿用旧系统:value 是 `MSG` 的 JSON(`META` 加对应业务体),日计划到达通知只有 `META`(接口契约「Kafka」) | 消费方读取契约无法定稿 | -| Q6 | 网页客户端读取 Redis 投影的约定 | 读写边界见接口契约「Redis:航班查询投影」与 `C-11`;`MAFL` 是否随投影提供仍未定 | 消费方读取契约无法定稿 | +| Q1 | 待对方 | `schd` 一条 Kafka 消息里装多少变更 | 见 `C-9` | +| Q2 | 待对方 | FLOP 里 `SRVT`/`VIPF` 等集合段缺席是否等于删除 | 见 `G-SRVT-VIPF` | +| Q3 | 待对方 | SIS 没写明的 FLOP 子类型怎么解析、处理完算啥 | 按现有逻辑处理(`US-05` AC1);见 `G-FLOP-SEMANTICS`、`CLM-1` | +| Q4 | 待对方 | 上游重发时会不会改正文 | 假定内容不变,只认身份;改正文会被当重复跳过 | +| Q5 | 待对方 | `msg` 消息体格式、变更与删除怎么区分 | 沿用旧系统 JSON(接口契约「Kafka」) | +| Q6 | 待对方 | 网页客户端怎么读 Redis 快照 | 见 `C-11`;`MAFL` 是否提供未定 | +| Q7 | 已定 | 信箱编号只增不减、不重用 | `INV-1`、`CLM-2`、`US-01` AC3 | +| Q8 | 已定 | 入站处理时间列名 `CMINMSGS_DATE_PROCESSED` | 术语「处理标记」、`US-10` | +| Q9 | 已定 | 入站行删除条件与保留期 | `C-1` | +| Q10 | 已定 | `SEQN` 重置与身份规则 | `C-3` | +| Q11 | 已定 | 日计划没带字段是否删除 | `C-6` | +| Q12 | 已定 | 主航班与共享航班删除联动 | `US-06` AC2 | +| Q13 | 已定 | `RQRD` / `RQFD` 编码字段 | `C-4` | +| Q14 | 本系统 | 生产用 PostgreSQL 还是 Oracle 11g | Oracle 未验证前不作支持承诺 | +| Q15 | 本系统 | `POST /cminmsgs/send` HTTP 约定 | 大小上限、状态码、响应体;见 `C-7` | +| Q16 | 本系统 | `POST /schd/sync` 请求与响应 | 见 `C-8` | +| Q17 | 本系统 | 人工发 `RQRD` 的入口 | `US-09` 要求能人工发,HTTP 清单里没有 | +| Q18 | 本系统 | 回退时正在处理的消息怎么办 | 见 `OPS-4` | +| Q19 | — | (未分配) | — | +| Q20 | — | (未分配) | — | +| Q21 | 本系统 | `GET /all/flights` HTTP 约定 | 见 `C-11` | +| Q22 | 本系统 | 静态参考数据对应哪些表 | 见 `C-10`、接口契约 | +| Q23 | 本系统 | Elasticsearch 历史怎么写、保留多久 | 见 `US-14`、`G-FLIGHT-HIST-RETENTION` | +| Q24 | 本系统 | `REQ_TRACK` 已结案记录保留多久 | 见 `G-REQ-TRACK-RETENTION` | +| Q25 | 本系统 | 季度计划从哪来、什么格式 | SIS 只有日计划;旧系统读 Oracle 季度表 | -答复就地更新结论,并按 [README.md](README.md)「维护清单」落到对应条款。 - -### 6.2 已确认 - -| 编号 | 事项 | 结论 | -|---|---|---| -| Q7 | 信箱编号的单调、不复用、不回退 | 已定案(`US-01` AC3、`CLM-2`) | -| Q8 | 入站处理时间列 | 已定案:列名 `CMINMSGS_DATE_PROCESSED`(`US-10`) | -| Q9 | 入站信箱行(MySQL `CMINMSGS`)清除 | 已定案(`C-1`) | -| Q10 | 上游 `SEQN` 的重置周期与身份是否加日期边界 | 已定案(`C-3`) | -| Q11 | 日计划未携带字段的删除语义 | 已定案:未携带字段本地清除(`C-6`) | -| Q12 | 主/共享删除顺序 | 已定案(`C-5`) | -| Q13 | `RQRD` / `RQFD` 编码字段 | 已定案(`C-4`) | - -### 6.3 本系统与需求方待决 - -以下事项不出自对接方,由本系统与需求方决定: - -| 编号 | 事项 | 当前假定 | 影响 | -|---|---|---|---| -| Q14 | 生产库选型 | 自有 PG 是唯一权威;生产环境用 PostgreSQL 还是 Oracle 11g 不能从三份依据确定,Oracle 适配验证通过前不作支持承诺 | 生产部署验收 | -| Q16 | `POST /schd/sync` 的请求字段与时间格式、成功响应表示已登记还是已落信、状态码与错误响应 | 交付承诺止于落信(`C-4`);旧系统线索为 `{startDate, endDate}` 与 12 小时制时间(接口契约「HTTP」) | 响应契约无法定稿(`C-8`) | -| Q17 | 人工发起 `RQRD` 的方式 | `US-09` 要求人工发起,HTTP 接口清单没有对应入口 | 参考数据请求无法人工触发 | -| Q18 | 回退时在途消息(已提交业务变更、未到处理完成)的处置 | 回填了结后切换(`OPS-4`);在途消息无跨系统幂等保障 | 回退演练的验收口径(`OPS-4`) | -| Q21 | `GET /all/flights` 的状态码、错误响应样例、外层包装是否沿用旧 `ResponseDto` | 返回体是消息文档中的 XML 结构转换出的 JSON;读 Redis、返回全体动态航班、不分页(`C-11`) | 查询契约无法定稿 | -| Q22 | 13 类报文与资源状态如何映射到 admin-api 实体对齐的基础数据表组(联调栈 `basicdata` schema 为承载) | 表与列直接取 admin-api 实体注解,不改名、不合并(接口契约「自有 PostgreSQL:内部存储与 admin-api 只读」);类别码与识别标签见接口契约「静态参考数据类别与编号来源」;表组物理形态属内部迁移 DDL,不作待决 | admin-api 读取契约无法定稿(`C-10`) | -| Q23 | Elasticsearch 历史索引、文档 ID、字段映射、成功判据、保留期与容量上限 | 文档 ID 沿用旧系统 `SODT + FLID` 幂等 upsert,写入结果不明时重试同一写入(接口契约「Elasticsearch 航班历史写入」);历史写入确认成功才删实时数据(`D1`、`US-14` AC3) | 历史链路无法验收(`US-14`;`G-FLIGHT-HIST-RETENTION`) | -| Q24 | `REQ_TRACK` 已结案记录的保留期取值 | 到期清理没有可依据的窗口(`G-REQ-TRACK-RETENTION`) | 请求历史清理无法实现(`US-09`) | -| Q25 | 季度计划的数据来源与归属:admin-api 从本系统库读季度计划,而 SIS 只有日计划事件(`SIS_AODB_RMS-V0.1.md`「AODB daily flight schedule event」「AODB daily schedule response to RMS event」「RMS daily schedule request event」) | 应由 AODB 下发,报文形态待确认;旧系统读 Oracle 的 `FIMS_FLIGHTSCHD_SEASON` | admin-api 的季度计划查询没有供数方 | +状态说明:**待对方** = 需对接方确认;**已定** = 结论已写入 `C-x`/`INV-x`;**本系统** = 由本系统与需求方决定;**—** = 编号保留未用。 ## 7. 当前已知偏差 -本表是偏差标记的唯一出处,其他文档只写 `G-NAME`。闭合时删除本行与全仓引用。 +偏差标记只在本表定义;其他文档只写 `G-NAME`。解决后删本行及全仓引用。 -| 偏差 | 缺口(交付状态见 Plane) | 影响 | +| 偏差 | 缺什么(交付进度见 Plane) | 影响 | |---|---|---| -| `G-REQ-TRACK` | 出站请求跟踪未交付 | `US-09` | -| `G-REQ-OPEN-UNIQUE` | 同类型在途唯一限制未交付 | `US-09` | -| `G-REQ-TRACK-RETENTION` | `REQ_TRACK` 已结案保留期取值未定(`Q24`) | `US-09` | -| `G-FLIGHT-HIST-RETENTION` | 历史保留期与容量上限未定(`Q23`) | `US-14`;实时数据删除后历史是唯一副本 | -| `G-FLOP-IDEMPOTENT` | 逐类幂等规则未补齐 | `US-03`;`CLM-1` | -| `G-FLOP-SEMANTICS` | 动态报文语义与 `US-05` 的差距未闭合 | `US-05` | -| `G-FLOP-UNMAPPED` | [XSD](legacy/unisysaodbsis.xsd)「FLOP 元素」字段映射缺口未闭合 | `US-05` | -| `G-MAFL` | 主航班共享列表未交付 | `US-06` AC2;`C-5` | -| `G-SRVT-VIPF` | `SRVT`、`VIPF` 持久化明细未交付 | `US-05` | -| `G-SCAN-PREDICATE` | 按「处理时间为空」读取未交付 | `US-01`;`INV-1` | -| `G-REDIS-PROJECTION` | Redis 投影写/移除未交付 | `US-05`、`US-06`、`US-07`、`US-12` | -| `G-SCHD-SNAPSHOT` | 日计划快照删除/清空/分批未交付 | `INV-7`;`INV-3`;`INV-9` | -| `G-RESP-GUARD` | `SCHD-RESP` 过期判断未交付 | `US-07` | -| `G-PROC-CLEANUP` | 处理记录到期清理未交付 | `US-11` | -| `G-REF-DATA` | 静态参考数据处理与 admin-api 直读未交付 | `US-13`;`US-03` | +| `G-FLIGHT-HIST-RETENTION` | 历史保留期与容量上限未定(`Q23`) | `US-14` | +| `G-FLOP-IDEMPOTENT` | 各类 FLOP 重复处理规则未写完 | `US-03`;`CLM-1` | +| `G-FLOP-SEMANTICS` | 动态报文处理与 `US-05` 要求不一致 | `US-05` | +| `G-FLOP-UNMAPPED` | [XSD](legacy/unisysaodbsis.xsd) FLOP 字段映射不全 | `US-05` | +| `G-MAFL` | 主航班共享列表未做 | `US-06` AC2 | +| `G-PROC-CLEANUP` | 处理记录到期清理未做 | `US-11` | +| `G-REDIS-PROJECTION` | Redis 快照写入与删除未做 | `US-05`、`US-06`、`US-07`、`US-12` | +| `G-REF-DATA` | 静态参考数据处理与 admin-api 直读未做 | `US-13` | +| `G-REQ-OPEN-UNIQUE` | 同类型未处理完时不允许再发,未做 | `US-09` | +| `G-REQ-TRACK` | 出站请求跟踪未做 | `US-09` | +| `G-REQ-TRACK-RETENTION` | `REQ_TRACK` 已结案保留期未定(`Q24`) | `US-09` | +| `G-RESP-GUARD` | `SCHD-RESP` 过期判断未做 | `US-07` | +| `G-SCAN-PREDICATE` | 按「处理时间为空」扫描未做 | `US-01`;`INV-1` | +| `G-SCHD-SNAPSHOT` | 日计划快照删除、清空、分批未做 | `INV-7`、`INV-9` | +| `G-SRVT-VIPF` | `SRVT`、`VIPF` 明细未入库(`Q2`) | `US-05` | ## 8. 验证映射 -| 不变量 | 需求验收 | 要观察的结果 | +按条款编号排列。 + +| 条款 | 需求验收 | 要观察的结果 | |---|---|---| -| INV-1 | `US-01` AC1/AC2/AC4 | 扫描重来与重启后登记数不变、行不丢;同一编号重复出现时处理记录数不增加 | -| INV-1、C-7 | `US-02` AC1~AC5 | 兼容入口接受三种媒体类型、默认 UTF-8;空报文、超上限、非法 XML 不落信并返回错误,解析禁用外部实体与外部资源;成功返回编号且只表示落信;内网来源由网络层配置核对;写入的行与上游投递同路径被发现、登记 | -| CLM-2(升序) | `US-01` AC3、`US-03` AC1 | 按编号升序处理;队头未完成时后面的消息不被处理 | -| INV-2 | `US-10` AC1/AC2 | 终态后才回填;重启后继续,写不上的有记录与告警 | -| C-9 | `US-08` AC1/AC2/AC3 | `msg` 单条变更、`schd` 批量;失败重试后仍能投出;一直失败的记录保留可查并告警;`msg` 同一 `FLID` 按发送顺序保序(`CLM-3`);同一条可能多发(`C-9`) | -| INV-5 | 架构「数据归属与一致性」 | 航班当前态的权威写入只在自有 PG | +| C-5、`US-06` AC2 | `US-06` AC2 | 删共享联动主航班;删主级联删共享 | +| C-7 | `US-02` AC1~AC5 | 三种 Content-Type、UTF-8;空/超长/非法 XML 不写入;禁外部资源;成功编号只表示已写入;与 adapter 同路径建记录 | +| C-8、CLM-4 | `US-09` AC1~AC3 | 出站写入 `COUTMSGS`;HTTP 触发先登记,旧的处理完才写入信箱 | +| C-9、CLM-3 | `US-08` AC1~AC3 | `msg` 单条、`schd` 批量;失败可重试;`msg` 同 `FLID` 保序;同条可能发多次 | +| C-10 | `US-13` AC1~AC4 | 全量替换、增删改逐条;空值表示无值不是删除;单类校验失败只停该类 | +| INV-1(建立处理记录) | `US-01` AC1/AC2/AC4 | 重扫与重启后记录数不变、行不丢;同一编号重复出现时不新增记录 | +| INV-2 | `US-10` AC1/AC2 | 处理结束后才写回;重启后继续;一直失败有记录与告警 | +| INV-3、INV-10 | `US-03` AC4、`US-05` AC4、`US-06` AC1 | Redis 失败时不记「处理结束」与「还要写回」;完成前不发 Kafka | +| INV-4 | `US-07` AC1 | 校验失败后 PG 航班数据不变 | +| INV-5 | 架构「数据归属与一致性」 | 航班数据只写入自有 PG | | INV-6 | implementation.md「权威模型」 | `FLID` 唯一 | -| INV-7 | `US-07` AC2/AC3 | 缺席的航班在 PG 标为已删除并从 Redis 投影移除;未携带的字段被清空 | -| US-04(ADFT 更新) | `US-04` AC2 | 未携带的字段保持原值 | -| INV-3 | `US-03` AC4、`US-05` AC4、`US-06` AC1 | 投影写失败时,没有终态与回填意图落库;处理完成前事件不可投递 | -| INV-4 | `US-07` AC1 | 校验失败后本地数据不变 | -| INV-9 | `US-07` AC4/AC5 | 分批失败后整包重处理收敛到同一目标(`G-SCHD-SNAPSHOT` 闭合前无法验证);Redis 在整包写入完成后按快照刷新,成功即与快照一致 | -| CLM-1(幂等收敛) | `US-03` AC2/AC3/AC5 | 死信与跳过留档且无业务副作用;错误在对应处理记录上可查;失败回滚后消息仍在未完成;已提交结果不被写信箱处理时间或发 Kafka 失败回滚;同一消息不产生两次效果须等 `Q3`、`G-FLOP-IDEMPOTENT`、`G-FLOP-SEMANTICS` 闭合 | -| INV-8 | `US-06` AC1 | 航班标记删除后从投影移除;移除失败时下轮重做 | -| US-14、D1 | `US-14` AC3/AC4 | 历史写入成功才删实时数据;删除事件在实时数据删除前登记(`D1`);清理跳过正在被消息处理的航班 | -| C-5 | `US-06` AC2 | 删共享联动主航班;删主航班级联删共享 | -| INV-10 | `US-05` AC4、`US-06` AC1 | 投影写失败的消息下轮仍被处理 | -| INV-11 | `US-12` AC1/AC2 | 返回全部非共享航班,且与 Redis 一致;Redis 故障时返回错误 | -| G-PROC-CLEANUP(未完成不删) | `US-11` AC1/AC2 | 未完成的记录不被删除 | -| C-10 | `US-13` AC1~AC4 | 全量整体替换、增删改逐条;空值是「没有值」不是删除;一类校验失败只停该类;参考数据落独立表组供 admin-api 只读 | -| CLM-4(止于落信) | `US-09` AC1~AC3 | 出站请求写入 `COUTMSGS`;接口触发先登记,在途结案后才落信(`C-8`) | -| CLM-5(不承诺完成时限) | — | 需求未定完成时限 | -| CLM-6(容量) | — | 需求未给容量数据 | -| OPS-4(切换与回退) | `OPS-4` | 回退演练:回填了结后切回,旧系统不重处理已产生业务效果的消息;在途窗口处置见 `Q18` | -| CLM-7(启动拒绝) | `OPS-1` | 配置错误启动失败测试可验收 | -| PRE-1(测试隔离) | `OPS-3` | 测试环境配置核对:独立的数据库、Redis 与 Kafka 主题,不连接生产信箱 | +| INV-7 | `US-07` AC2/AC3 | 快照缺席航班标删并从 Redis 删;未带字段清空 | +| INV-8 | `US-06` AC1 | 标删后从 Redis 删;失败下轮重做 | +| INV-9 | `US-07` AC4/AC5 | 分批失败全部重来(`G-SCHD-SNAPSHOT` 做完前不可验);PG 整份写完后再刷 Redis | +| INV-11 | `US-12` AC1/AC2 | 返回全部非共享航班且与 Redis 一致;Redis 故障返回错误 | +| CLM-1(重复不重复生效) | `US-03` AC2/AC3/AC5 | 跳过/无法处理留档且无业务改动;失败可查;PG 已提交不因写回或 Kafka 失败撤销;待 `Q3`、`G-FLOP-IDEMPOTENT`、`G-FLOP-SEMANTICS` | +| CLM-2(升序) | `US-01` AC3、`US-03` AC1 | 按编号从小到大;最前面未完成时后面的不处理 | +| CLM-7(启动拒绝) | `OPS-1` | 配置错误时启动失败 | +| US-04(ADFT 更新) | `US-04` AC2 | ADFT 未带字段保持原值 | +| US-14、D1 | `US-14` AC3/AC4 | 历史写入成功才删实时数据;删事件先于实时删除登记 | +| G-PROC-CLEANUP(未完成不删) | `US-11` AC1/AC2 | 未处理完的记录不清理 | +| OPS-4(切换与回退) | `OPS-4` | 写回处理时间后再切回;在途消息见 `Q18` | +| PRE-1(测试隔离) | `OPS-3` | 测试环境独立 DB、Redis、Kafka,不连生产信箱 | diff --git a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisMessageBody.kt b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisMessageBody.kt index 45bca6f..b04e5fc 100644 --- a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisMessageBody.kt +++ b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisMessageBody.kt @@ -23,7 +23,7 @@ data class ScheduleBody( * 只保留在 wire/domain、尚未映射到持久化明细的集合键(`[G-SRVT-VIPF]`)。 * * 它们不参与合并、不进快照、不落库;保留的目的是不让入站事实在解码层被静默抹平,并让真实 - * 流量里的出现情况可观测——决定"缺席是否等于删除"的 `Q11` 需要真实报文才能定案。 + * 流量里的出现情况可观测——决定"缺席是否等于删除"的 `Q2` 需要真实报文才能定案。 */ private val UNPERSISTED_COLLECTION_KEYS: Set = setOf("SRVT", "VIPF") @@ -132,7 +132,7 @@ data class FlightRecordXml( @param:JacksonXmlElementWrapper(useWrapping = false) @param:JacksonXmlProperty(localName = "ROUT") val rout: List = emptyList(), @param:JacksonXmlElementWrapper(useWrapping = false) @param:JacksonXmlProperty(localName = "ERUT") val erut: List = emptyList(), // SRVT/VIPF 用可空表达"段是否出现":null = 未出现;出现即为列表(空元素得到一行空行)。 - // 两者都不落明细表、不参与合并,清空语义待 `Q11`(`[G-SRVT-VIPF]`)。 + // 两者都不落明细表、不参与合并,清空语义待 `Q2`(`[G-SRVT-VIPF]`)。 @param:JacksonXmlElementWrapper(useWrapping = false) @param:JacksonXmlProperty(localName = "SRVT") val srvt: List? = null, @param:JacksonXmlElementWrapper(useWrapping = false) @param:JacksonXmlProperty(localName = "VIPF") val vipf: List? = null, ) diff --git a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisWireMapper.kt b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisWireMapper.kt index 5d6513e..5ba0390 100644 --- a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisWireMapper.kt +++ b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/SisWireMapper.kt @@ -38,7 +38,7 @@ internal object SisWireMapper { * `filterValues` 去掉的正是"缺席",避免整包凭空清空本地明细(合并语义见 docs/implementation.md「SCHD 日计划」)。 * * `SRVT`/`VIPF` 尚未有明细表(`[G-SRVT-VIPF]`):只用"键是否存在"表达段是否出现,保留原始 - * 内容与顺序,不参与合并、不判断清空语义(`Q11`)——出现(哪怕为空)与缺席不再被抹平。 + * 内容与顺序,不参与合并、不判断清空语义(`Q2`)——出现(哪怕为空)与缺席不再被抹平。 */ private fun FlightRecordXml.collections(): Map>> { val mapped = linkedMapOf( diff --git a/src/main/kotlin/com/gzzn/omms/msgexchange/infra/metrics/PipelineCounters.kt b/src/main/kotlin/com/gzzn/omms/msgexchange/infra/metrics/PipelineCounters.kt index 5ba7c23..d3235f7 100644 --- a/src/main/kotlin/com/gzzn/omms/msgexchange/infra/metrics/PipelineCounters.kt +++ b/src/main/kotlin/com/gzzn/omms/msgexchange/infra/metrics/PipelineCounters.kt @@ -21,7 +21,7 @@ class PipelineCounters { * 入站记录里出现 `SRVT`/`VIPF` 段的条数(`[G-SRVT-VIPF]`)。 * * 这两个集合目前只保留在 wire/domain,不落明细表、不参与合并;计数是"真实报文有没 - * 有在用"的唯一取证渠道(清空语义 `Q11` 需要真实样例才能定案)。> 0 表示确有流量携带该段。 + * 有在用"的唯一取证渠道(清空语义 `Q2` 需要真实样例才能定案)。> 0 表示确有流量携带该段。 */ fun unpersistedCollectionSeenAdd(hits: Map) { hits["SRVT"]?.let { srvtSeen.addAndGet(it.toLong()) } diff --git a/src/main/kotlin/com/gzzn/omms/msgexchange/infra/metrics/PipelineMetrics.kt b/src/main/kotlin/com/gzzn/omms/msgexchange/infra/metrics/PipelineMetrics.kt index 1612c40..be4ded1 100644 --- a/src/main/kotlin/com/gzzn/omms/msgexchange/infra/metrics/PipelineMetrics.kt +++ b/src/main/kotlin/com/gzzn/omms/msgexchange/infra/metrics/PipelineMetrics.kt @@ -94,7 +94,7 @@ class PipelineMetrics( .strongReference(true) .register(registry) - // SRVT/VIPF 尚未落明细表([G-SRVT-VIPF]):计数替代静默丢弃,为 Q11 提供真实流量证据。 + // SRVT/VIPF 尚未落明细表([G-SRVT-VIPF]):计数替代静默丢弃,为 Q2 提供真实流量证据。 Gauge.builder("msgx.pipeline.codec.srvt_seen.total", counters) { it.srvtSeenCount().toDouble() } .strongReference(true) .register(registry) diff --git a/src/main/kotlin/com/gzzn/omms/msgexchange/processing/Pump.kt b/src/main/kotlin/com/gzzn/omms/msgexchange/processing/Pump.kt index 569495f..5882a64 100644 --- a/src/main/kotlin/com/gzzn/omms/msgexchange/processing/Pump.kt +++ b/src/main/kotlin/com/gzzn/omms/msgexchange/processing/Pump.kt @@ -190,7 +190,7 @@ class MessageProcessor( } } - // [G-SRVT-VIPF]:SRVT/VIPF 段只保留在解码载荷里,尚未落明细表(清空语义待 Q11)。 + // [G-SRVT-VIPF]:SRVT/VIPF 段只保留在解码载荷里,尚未落明细表(清空语义待 Q2)。 // 计数 + 告警替代此前的静默丢弃;出现即证明真实报文携带该段,可作为定案依据。 val unpersisted = unpersistedCollectionHits(decoded.body) if (unpersisted.isNotEmpty()) {