fix(acm2-107): finalize /all/flights contract and FLTR JSON payload
Close Q21 into C-11 and emit day-schedule FLTR-shaped JSON for Redis, schd, and query responses. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,17 +11,17 @@
|
|||||||
| 接口 | 请求契约 | 成功响应契约 | 失败契约 | 尚需确定 |
|
| 接口 | 请求契约 | 成功响应契约 | 失败契约 | 尚需确定 |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `POST /cminmsgs/send` | 请求体是 XML 原文;接受 `text/xml`、`application/xml`、`text/plain`,默认 UTF-8;仅限内网,网络层限制来源。 | 报文写入 `CMINMSGS` 后返回信箱编号;写入的报文与上游投递走同一条处理路径、效果一致;该响应只证明已落信,不证明业务处理或下游投递(`US-02`)。 | 空报文、超大小上限、非法 XML 不落信并返回错误;写信失败不返回编号;XML 解析禁用外部实体和外部资源访问。 | 大小上限、请求编码与 `Content-Type` 的精确处理规则、HTTP 状态码、成功/失败响应体字段及样例(`Q15`)。 |
|
| `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`)、状态码、响应字段及样例。 |
|
| `POST /schd/sync` | 触发一次 `RQFD` 日计划请求;请求体为空、请求全量(`C-4`);登记与落信规则见「在途与作废」。 | HTTP 200,返回请求编号;只表示已登记,不代表已落信、更不代表 AODB 已收到。 | 已有未结案请求时 HTTP 409,响应体 `open-request-exists`。 | — |
|
||||||
| `GET /all/flights` | 无已定义的请求字段;从 Redis 投影读取当前全部动态航班,不含共享航班,与网页客户端同源(`US-12`)。 | HTTP 200;响应体是裸 JSON 数组(不套旧 `ResponseDto`),元素为与 `KAFKA:schd` 同形的航班 JSON;共享航班(标量 `MAID` 非空)不出现在数组里;不分页(`Q21` 暂定)。 | HTTP 503;JSON 对象 `{"error":"FLIGHT_PROJECTION_UNAVAILABLE","reason":"<细节>"}`;Redis 或投影读失败时不得返回 200 空数组。 | 数组元素字段是否与旧 `SCHD.FLTR` 逐字一致的对拍样例。 |
|
| `GET /all/flights` | 无已定义的请求字段;从 Redis 投影读取当前全部动态航班,不含共享航班,与网页客户端同源(`US-12`)。 | HTTP 200;响应体是裸 JSON 数组(不套旧 `ResponseDto`),元素为日计划 `SCHD.FLTR` 转成的 JSON,与 `KAFKA:schd` 数组元素同形(`C-9`、`C-11`);共享航班(`MAID` 非空)不出现在数组里;不分页。 | HTTP 503;JSON 对象 `{"error":"FLIGHT_PROJECTION_UNAVAILABLE","reason":"<细节>"}`;Redis 或投影读失败时不得返回 200 空数组(`INV-11`)。 | — |
|
||||||
|
|
||||||
旧系统线索(来源:旧项目用户故事「HTTP 接口清单」「日计划请求」):
|
旧系统线索(来源:旧项目用户故事「HTTP 接口清单」「日计划请求」):
|
||||||
|
|
||||||
- 三个接口共用 `ResponseDto`,字段为 `is_success`、`err_code`、`err_msg`、`body`。
|
- 三个接口共用 `ResponseDto`,字段为 `is_success`、`err_code`、`err_msg`、`body`。
|
||||||
- `POST /cminmsgs/send` 的成功 `body` 是 `CMINMSGS_ID`,样例为 `{is_success: true, body: <ID>}`。
|
- `POST /cminmsgs/send` 的成功 `body` 是 `CMINMSGS_ID`,样例为 `{is_success: true, body: <ID>}`。
|
||||||
- 旧 `GET /all/flights` 用 `ResponseDto`,`body` 是非共享航班的 `SCHD.FLTR` 列表;新版暂定直接返回该列表对应的 JSON 数组(`Q21`)。
|
- 旧 `GET /all/flights` 用 `ResponseDto`,`body` 是非共享航班的 `SCHD.FLTR` 列表(对象即日计划 XML 解码后的 `FLTR`,再序列化为 JSON);新版成功体直接返回该列表对应的裸 JSON 数组(`C-11`),元素形状不变。
|
||||||
- `POST /schd/sync` 的请求体是 `{startDate, endDate}`,时间格式 `yyyy-MM-dd hh:mm` 用无 AM/PM 的 12 小时制。
|
- `POST /schd/sync` 的请求体是 `{startDate, endDate}`,时间格式 `yyyy-MM-dd hh:mm` 用无 AM/PM 的 12 小时制,解析结果写进 `RQFD` 的 `STDB`/`STDE`(`ddMMMyyHHmm`,大写)。
|
||||||
|
|
||||||
这些字段是否沿用、时间改用何种无歧义格式,都要核对后定稿。新版日计划是 AODB 当前时刻的完整航班列表(`US-07`),出站编码已定(`C-4`),不带日期筛选。
|
旧系统的 `{startDate, endDate}` 与 `RQFD` 的 `STDB`/`STDE` 均不沿用:新版日计划是 AODB 当前时刻的完整航班列表(`US-07`),出站编码已定(`C-4`),不带日期筛选;`POST /schd/sync` 请求与响应口径见 `Q16`。旧系统响应体是否能作为 `POST /cminmsgs/send` 的定稿样例,见 `Q15`。
|
||||||
|
|
||||||
## 入站报文与请求应答
|
## 入站报文与请求应答
|
||||||
|
|
||||||
@@ -40,8 +40,8 @@ AODB 经 CIIMS adapter 把 XML 报文写入 `CMINMSGS`,格式以架构指定
|
|||||||
|
|
||||||
| 主题 | 已确定的消息语义 | 尚需确定 |
|
| 主题 | 已确定的消息语义 | 尚需确定 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `msg` | 单条航班变更通知;航班动态与删除处理完成后投递;发送失败自动重试,一直失败的记录保留可查并告警(`US-08`);同一 `FLID` 的变更保序,对外按至少一次投递(`CLM-3`,单分区)。删除通知的来源有三处:`FDEL` 删除(`US-06`)、日计划快照覆盖范围内缺席删除(`US-07`)、历史清理在物理删除前必要时登记(架构 `D1`)。 | Kafka value 的字段与类型、变更和删除的区分方式(`Q5`)、编码方式。 |
|
| `msg` | 单条航班变更通知;Kafka value 是整条 `MSG` 的 JSON(`META` 加对应业务体,空字段不输出),变更与删除由 `META` 的类型与子类型区分(`Q5`);航班动态与删除处理完成后投递;发送失败自动重试,一直失败的记录保留可查并告警(`US-08`);同一 `FLID` 的变更保序,对外按至少一次投递(`CLM-3`,单分区)。删除通知的来源有三处:`FDEL` 删除(`US-06`)、日计划快照覆盖范围内缺席删除(`US-07`)、历史清理在物理删除前必要时登记(架构 `D1`)。 | 编码方式(JSON 之外的压缩或封装是否引入)。 |
|
||||||
| `schd` | 定时批量发送最新航班状态;两次 tick 之间积累的航班组成 `SCHD.FLTR` 数组 JSON,整批作为单条 record 发出(沿用旧系统,`Q1`);空字段不输出,字段与类型见 [XSD](../legacy/unisysaodbsis.xsd) 的 `FLTR`;没有变化不发;删除航班不进入本主题,由 `msg` 发一条删除通知;发送失败自动重试,对外按至少一次投递(`US-08`)。 | 消费方按数组格式解析的确认(`Q1`)。 |
|
| `schd` | 定时批量发送最新航班状态;两次 tick 之间积累的航班组成 `SCHD.FLTR` 数组 JSON,整批作为单条 record 发出,条数不设上限(沿用旧系统,`Q1`);空字段不输出,字段与类型见 [XSD](../legacy/unisysaodbsis.xsd) 的 `FLTR`;没有变化不发;删除航班不进入本主题,由 `msg` 发一条删除通知;发送失败自动重试,对外按至少一次投递(`US-08`)。 | — |
|
||||||
|
|
||||||
旧系统线索(来源:旧项目用户故事「前端通知」「动态类(FLOP-*)处理」):
|
旧系统线索(来源:旧项目用户故事「前端通知」「动态类(FLOP-*)处理」):
|
||||||
|
|
||||||
@@ -75,13 +75,13 @@ AODB 经 CIIMS adapter 把 XML 报文写入 `CMINMSGS`,格式以架构指定
|
|||||||
|
|
||||||
| 存储 | 承载内容 | 边界 | 尚需确定 |
|
| 存储 | 承载内容 | 边界 | 尚需确定 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Redis | 航班查询投影 | 只作查询,不是权威,也不存处理状态;只由本系统写入和移除,网页客户端与 `GET /all/flights` 读同一份,内容来自自有 PostgreSQL 的航班当前态;写投影成功、删除时移除成功,才算对应消息处理完成(架构「数据归属与一致性」;`US-05`、`US-06`)。 | 网页客户端读取约定(`Q6`)。 |
|
| Redis | 航班查询投影 | 只作查询,不是权威,也不存处理状态;只由本系统写入和移除,网页客户端经 `GET /all/flights` 读同一份、不直连 Redis,内容来自自有 PostgreSQL 的航班当前态;投影 value 与 `KAFKA:schd` 数组元素、查询成功体元素同形,均为日计划 `FLTR` 转成的 JSON(`C-9`、`C-11`);写投影成功、删除时移除成功,才算对应消息处理完成(架构「数据归属与一致性」;`US-05`、`US-06`)。 | — |
|
||||||
|
|
||||||
旧系统线索(来源:旧项目用户故事「Redis key 汇总」「术语与数据语义」「动态航班转历史」):
|
旧系统线索(来源:旧项目用户故事「Redis key 汇总」「术语与数据语义」「动态航班转历史」):
|
||||||
|
|
||||||
- 投影是 hash `flightInfo`:field 为 `FLID`,value 为完整 `SCHD.FLTR` 对象的带类型 JSON,不设过期;Redis 里没有名为 `schd` 的 key,`schd` 只是 Kafka 主题。
|
- 投影是 hash `flightInfo`:field 为 `FLID`,value 为完整 `SCHD.FLTR` 对象的带类型 JSON,不设过期;Redis 里没有名为 `schd` 的 key,`schd` 只是 Kafka 主题。新版 value 仍是 `FLTR` 转 JSON,但不使用旧系统的 Jackson 默认类型标注。
|
||||||
- 写入路径:日计划下载(`DNLD`/`RESP`)整体写入当天航班,单条变更(`ADFT`、`FLOP`)只写对应的一条,转历史时按 `FLID` 逐条移除。整体写入不删除本次映射中缺席的航班,与 `US-07` AC5 相反,新版按 `US-07` AC5 在覆盖范围内刷新。
|
- 写入路径:日计划下载(`DNLD`/`RESP`)整体写入当天航班,单条变更(`ADFT`、`FLOP`)只写对应的一条,转历史时按 `FLID` 逐条移除。整体写入不删除本次映射中缺席的航班,与 `US-07` AC5 相反,新版按 `US-07` AC5 在覆盖范围内刷新。
|
||||||
- 写入前生成主航班的共享航班列表 `MAFL`(`G-MAFL`),是否为网页客户端所需仍未定(`Q6`);登机桥字段 `abdg` 本版不提供——旧系统拼它的数据源是机位与登机桥映射缓存,已列入需求「范围与非目标」不交付。
|
- 写入前生成主航班的共享航班列表 `MAFL`,共享航班不单列、随主航班下发(`Q6`);旧系统在日计划下载时按 `MAID` 把共享航班挂进主航班 `MAFL`,登机桥字段 `abdg` 本版不提供——旧系统拼它的数据源是机位与登机桥映射缓存,已列入需求「范围与非目标」不交付。
|
||||||
|
|
||||||
### 自有 PostgreSQL:内部存储与 admin-api 只读
|
### 自有 PostgreSQL:内部存储与 admin-api 只读
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ AODB 经 CIIMS adapter 把 XML 报文写入 `CMINMSGS`,格式以架构指定
|
|||||||
| 航班当前态表 | 航班当前态的唯一权威;`FLID` 唯一(`INV-6`);Redis 和 Kafka 从处理结果派生,不反向覆盖这些表。 | 主键、字段与类型、外键/索引。 |
|
| 航班当前态表 | 航班当前态的唯一权威;`FLID` 唯一(`INV-6`);Redis 和 Kafka 从处理结果派生,不反向覆盖这些表。 | 主键、字段与类型、外键/索引。 |
|
||||||
| 内部处理表 | 管道处理、请求跟踪、留痕与互斥由本系统维护;不对外提供直接读写接口。 | 字段与约束由内部实现设计确定。 |
|
| 内部处理表 | 管道处理、请求跟踪、留痕与互斥由本系统维护;不对外提供直接读写接口。 | 字段与约束由内部实现设计确定。 |
|
||||||
|
|
||||||
admin-api 还从本系统数据库只读季度计划;供数方与报文形态待 `Q25` 确定。
|
季度计划不属本系统边界:admin-api 直接读 Oracle `FIMS_FLIGHTSCHD_SEASON`(`Q25`、[legacy/flight-apis.md](../legacy/flight-apis.md)),本系统只提供日计划请求。
|
||||||
|
|
||||||
自有 PostgreSQL 的物理表结构由本系统的迁移定稿。生产环境若改用 Oracle 11g,字段类型需先完成适配验证。
|
自有 PostgreSQL 的物理表结构由本系统的迁移定稿。生产环境若改用 Oracle 11g,字段类型需先完成适配验证。
|
||||||
|
|
||||||
@@ -130,4 +130,4 @@ admin-api 还从本系统数据库只读季度计划;供数方与报文形态
|
|||||||
|
|
||||||
## 待决事项
|
## 待决事项
|
||||||
|
|
||||||
由本系统与需求方确定的事项见 [specification.md](../specification.md)「本系统与需求方待决」。
|
由本系统与需求方确定的事项见 [specification.md](../specification.md)「待确认事项台账」。
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ PENDING → SENT → DONE
|
|||||||
- **只进不退**:`STATE_VERSION` 只升不降。
|
- **只进不退**:`STATE_VERSION` 只升不降。
|
||||||
- **条件标记**:`SENT` 时 `WHERE STATE_VERSION = 本批版本`;已被新版本覆盖则下轮重发。
|
- **条件标记**:`SENT` 时 `WHERE STATE_VERSION = 本批版本`;已被新版本覆盖则下轮重发。
|
||||||
|
|
||||||
发送:取未发送行(`PARAM:msgx.schd.flush-limit`)→ 整批 `SCHD.FLTR` JSON(`C-9`)→ 条件标 `SENT`。删航班只走 `msg`。
|
发送:取未发送行(`PARAM:msgx.schd.flush-limit`)→ 把各行载荷聚成 `SCHD.FLTR` JSON 数组发出(`C-9`)→ 条件标 `SENT`。`MSG_EVENT` 里每条 `schd` 载荷、以及 Redis 投影 value,都是单条 `FLTR` JSON(由 PG 当前态重建,`C-11`);删航班只走 `msg`。
|
||||||
|
|
||||||
周期与批上限见 [reference.md](reference.md)。`msg` 与 `schd` 不承诺顺序。
|
周期与批上限见 [reference.md](reference.md)。`msg` 与 `schd` 不承诺顺序。
|
||||||
|
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ msgexchange-api 为成都机场 OMMS 消息交换服务(`com.gzzn.omms:msgexch
|
|||||||
| `GET /flights/migrate` | — | 成功 | 迁移 `flight_hts` 至 `flight_hts2` | `FlightsController` |
|
| `GET /flights/migrate` | — | 成功 | 迁移 `flight_hts` 至 `flight_hts2` | `FlightsController` |
|
||||||
| `POST /kafka/topics/{name}/msgs` | JSON 或 XML | 成功 | 转发至指定 Kafka 主题 | `KafkaController` |
|
| `POST /kafka/topics/{name}/msgs` | JSON 或 XML | 成功 | 转发至指定 Kafka 主题 | `KafkaController` |
|
||||||
|
|
||||||
统一响应封装:`dto/ResponseDto.java`(字段 `is_success`、`err_code`、`err_msg`、`body`)与 `enums/ResultCode.java`。
|
统一响应封装:`dto/ResponseDto.java`(字段 `is_success`、`err_code`、`err_msg`、`body`)与 `enums/ResultCode.java`。成功取 `ResultCode.SUCCESS`,其 `code=1`、`message="成功"`;`POST /cminmsgs/send` 成功样例为 `{"is_success":true,"err_code":1,"err_msg":"成功","body":<CMINMSGS_ID>}`,`POST /schd/sync` 成功时 `body` 为 `null`。工程内没有 `@ControllerAdvice` 等全局异常处理,未捕获异常按 Spring Boot 默认错误响应返回 HTTP 500,不是 `ResponseDto`;各环境配置均未设置请求体大小上限。
|
||||||
|
|
||||||
### 5.2 定时任务与后台线程
|
### 5.2 定时任务与后台线程
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
- 不调用 admin-api,不从 admin-api 拉取、补全或合并任何数据。
|
- 不调用 admin-api,不从 admin-api 拉取、补全或合并任何数据。
|
||||||
- 不在 Redis 缓存机位基础数据与登机桥映射,本版不交付(`C-10`)。
|
- 不在 Redis 缓存机位基础数据与登机桥映射,本版不交付(`C-10`)。
|
||||||
|
|
||||||
**查询侧(admin-api)**:admin-api 从本系统数据库只读基础数据与季度计划(供数方待 `Q25`);历史航班检索读 Elasticsearch(`Q23`)。其余能力(航班动态导出、字典)不由本系统提供。
|
**查询侧(admin-api)**:admin-api 从本系统数据库只读基础数据;历史航班检索读 Elasticsearch(`Q23`);季度计划由 admin-api 直接读 Oracle `FIMS_FLIGHTSCHD_SEASON`,不经本系统(`Q25`)。其余能力(航班动态导出、字典)不由本系统提供。
|
||||||
|
|
||||||
## 2. 用户故事
|
## 2. 用户故事
|
||||||
|
|
||||||
|
|||||||
+18
-20
@@ -47,7 +47,7 @@
|
|||||||
### 2.2 上游(AODB / SIS)
|
### 2.2 上游(AODB / SIS)
|
||||||
|
|
||||||
- **C-3** 一条报文的身份 = `SNDR` + `TYPE` + `STYP` + `SEQN`。`SEQN` 自增,极少在消息服务器重启时重置;重置后不与历史冲突。
|
- **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-4** 出站请求写入 `COUTMSGS`;本系统只保证写入信箱,不保证 AODB 收到。编码:`SNDR=OMMS`;`SEQN` 本系统生成;`DTTM` 北京时间 `YYYYMMDDHHMMSS`;`RQRD` 共 14 种子类型(以 SIS 为准);`RQFD` 的 `STYP=NONE`,全量不带 `STDB`/`STDE` 筛选。季度计划不属本系统出站范围(`Q25`)。
|
||||||
- **C-5** 删航班只打删除标记;主航班与共享航班各自独立标记。
|
- **C-5** 删航班只打删除标记;主航班与共享航班各自独立标记。
|
||||||
- **C-6** 日计划快照里没带的字段,视为 AODB 已删掉该值,本地也清掉(`US-07` AC3)。
|
- **C-6** 日计划快照里没带的字段,视为 AODB 已删掉该值,本地也清掉(`US-07` AC3)。
|
||||||
|
|
||||||
@@ -55,15 +55,13 @@
|
|||||||
|
|
||||||
- **C-7** `POST /cminmsgs/send`:XML 写进入站信箱,与 adapter 走同一套处理。成功返回消息编号(只表示已写入、尚未处理);失败不返回编号。接受 `text/xml`、`application/xml`、`text/plain`(UTF-8);拒收空报文、超长、非法 XML;解析禁止访问外部资源。内网访问由网络层控制;仅供内部联调。
|
- **C-7** `POST /cminmsgs/send`:XML 写进入站信箱,与 adapter 走同一套处理。成功返回消息编号(只表示已写入、尚未处理);失败不返回编号。接受 `text/xml`、`application/xml`、`text/plain`(UTF-8);拒收空报文、超长、非法 XML;解析禁止访问外部资源。内网访问由网络层控制;仅供内部联调。
|
||||||
- 待确认:大小上限、HTTP 状态码与响应体 → `Q15`。
|
- 待确认:大小上限、HTTP 状态码与响应体 → `Q15`。
|
||||||
- **C-8** `POST /schd/sync`:登记一次 `RQFD` 日计划请求。同类型若还有未处理完的请求,新请求先不写信箱,等旧的处理完再写 `COUTMSGS`(`US-09` AC1)。
|
- **C-8** `POST /schd/sync`:登记一次 `RQFD` 日计划请求。请求体为空、请求全量(`C-4`);成功返回请求编号,已有未结案请求时返回 `409`(`Q16`)。同类型若还有未处理完的请求,新请求先不写信箱,等旧的处理完再写 `COUTMSGS`(`US-09` AC1)。
|
||||||
- 待确认:请求参数与 HTTP 响应 → `Q16`。
|
|
||||||
|
|
||||||
### 2.4 下游(admin-api、网页客户端)
|
### 2.4 下游(admin-api、网页客户端)
|
||||||
|
|
||||||
- **C-9** 航班变更发 Kafka:`msg` 一条变更一条消息;`schd` 把两轮采集之间积累的变化合成一条消息,内容是 `SCHD.FLTR` JSON 数组。空字段不输出,没变化不发;删航班只走 `msg`;不设 message key;可能重复投递(待确认 `Q1`)。
|
- **C-9** 航班变更发 Kafka:`msg` 一条变更一条消息,消息体是整条 `MSG` 的 JSON(`META` 加对应业务体),变更与删除由 `META` 的类型与子类型区分(`Q5`);`schd` 把两轮采集之间积累的变化合成一条消息,内容是 `SCHD.FLTR` JSON 数组,条数不设上限(`Q1`)。空字段不输出,没变化不发;删航班只走 `msg`;不设 message key;可能重复投递。
|
||||||
- **C-10** 静态参考数据写本系统库,admin-api 只读;本系统不调用 admin-api。
|
- **C-10** 静态参考数据写本系统库,admin-api 只读;本系统不调用 admin-api。
|
||||||
- **C-11** 航班快照写 Redis;`GET /all/flights` 与网页客户端读同一份;JSON 由报文 XML 转换。
|
- **C-11** 航班快照写 Redis;网页客户端经 `GET /all/flights` 读这一份,不直连 Redis;共享航班(`MAID` 非空)不单列返回(是否以主航班 `MAFL` 提供见 `Q6`)。成功 HTTP 200 返回裸 JSON 数组(不套旧 `ResponseDto`),不分页;失败 HTTP 503,JSON 对象 `{"error":"FLIGHT_PROJECTION_UNAVAILABLE","reason":"<细节>"}`;投影读失败不得返回 200 空数组(`INV-11`)。投影 value、`KAFKA:schd` 数组元素与查询成功体元素同形:都是日计划一条 `FLTR` 转成的 JSON(字段名与 [XSD](legacy/unisysaodbsis.xsd) `FLTR` 一致,空字段不输出,与 `C-9` 相同);由自有 PG 当前态重建,不是另起包装。
|
||||||
- 待确认:`MAFL` 是否一并提供(`Q6`);状态码与响应包装(`Q21`)。
|
|
||||||
|
|
||||||
## 3. 前提
|
## 3. 前提
|
||||||
|
|
||||||
@@ -113,12 +111,12 @@
|
|||||||
|
|
||||||
| 编号 | 状态 | 事项 | 说明 |
|
| 编号 | 状态 | 事项 | 说明 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Q1 | 待对方 | `schd` 一条 Kafka 消息里装多少变更 | 见 `C-9` |
|
| Q1 | 已定 | `schd` 一条 Kafka 消息里装多少变更 | 两轮发送之间积累的全部变更合成一条,条数不设上限(`C-9`) |
|
||||||
| Q2 | 待对方 | FLOP 里 `SRVT`/`VIPF` 等集合段缺席是否等于删除 | 见 `G-SRVT-VIPF` |
|
| Q2 | 待对方 | FLOP 里 `SRVT`/`VIPF` 等集合段缺席是否等于删除 | 见 `G-SRVT-VIPF`;旧系统不解析这两段,无法对拍 |
|
||||||
| Q3 | 待对方 | SIS 没写明的 FLOP 子类型怎么解析、处理完算啥 | 按现有逻辑处理(`US-05` AC1);见 `G-FLOP-SEMANTICS`、`CLM-1` |
|
| Q3 | 待对方 | SIS 没写明的 FLOP 子类型怎么解析、处理完算啥 | 按现有逻辑处理(`US-05` AC1);见 `G-FLOP-SEMANTICS`、`CLM-1`。旧系统找不到对应处理器时按失败兜底,仍写回处理时间且无业务改动 |
|
||||||
| Q4 | 待对方 | 上游重发时会不会改正文 | 假定内容不变,只认身份;改正文会被当重复跳过 |
|
| Q4 | 待对方 | 上游重发时会不会改正文 | 假定内容不变,只认身份;改正文会被当重复跳过。旧系统不比较正文 |
|
||||||
| Q5 | 待对方 | `msg` 消息体格式、变更与删除怎么区分 | 沿用旧系统 JSON(接口契约「Kafka」) |
|
| Q5 | 已定 | `msg` 消息体格式、变更与删除怎么区分 | 整条 `MSG` 的 JSON,空字段不输出;删除由 `META` 的类型与子类型标识(`C-9`) |
|
||||||
| Q6 | 待对方 | 网页客户端怎么读 Redis 快照 | 见 `C-11`;`MAFL` 是否提供未定 |
|
| Q6 | 本系统 | 网页客户端怎么读 Redis 快照 | 经 `GET /all/flights` 读、不直连 Redis(`C-11`);`MAFL` 是否随主航班返回仍待定,旧系统提供 |
|
||||||
| Q7 | 已定 | 信箱编号只增不减、不重用 | `INV-1`、`CLM-2`、`US-01` AC3 |
|
| Q7 | 已定 | 信箱编号只增不减、不重用 | `INV-1`、`CLM-2`、`US-01` AC3 |
|
||||||
| Q8 | 已定 | 入站处理时间列名 `CMINMSGS_DATE_PROCESSED` | 术语「处理标记」、`US-10` |
|
| Q8 | 已定 | 入站处理时间列名 `CMINMSGS_DATE_PROCESSED` | 术语「处理标记」、`US-10` |
|
||||||
| Q9 | 已定 | 入站行删除条件与保留期 | 本系统只写回处理标记、不删除;清除与保留期由库方负责(`C-1`) |
|
| Q9 | 已定 | 入站行删除条件与保留期 | 本系统只写回处理标记、不删除;清除与保留期由库方负责(`C-1`) |
|
||||||
@@ -127,17 +125,17 @@
|
|||||||
| Q12 | 已定 | 主航班与共享航班删除联动 | `US-06` AC2 |
|
| Q12 | 已定 | 主航班与共享航班删除联动 | `US-06` AC2 |
|
||||||
| Q13 | 已定 | `RQRD` / `RQFD` 编码字段 | `C-4` |
|
| Q13 | 已定 | `RQRD` / `RQFD` 编码字段 | `C-4` |
|
||||||
| Q14 | 本系统 | 生产用 PostgreSQL 还是 Oracle 11g | Oracle 未验证前不作支持承诺 |
|
| Q14 | 本系统 | 生产用 PostgreSQL 还是 Oracle 11g | Oracle 未验证前不作支持承诺 |
|
||||||
| Q15 | 本系统 | `POST /cminmsgs/send` HTTP 约定 | 大小上限、状态码、响应体;见 `C-7` |
|
| Q15 | 本系统 | `POST /cminmsgs/send` HTTP 约定 | 大小上限、状态码、最终响应体;见 `C-7`。旧系统成功响应是 `ResponseDto`(`err_code=1`,`body` 为信箱编号),且未配置大小上限 |
|
||||||
| Q16 | 本系统 | `POST /schd/sync` 请求与响应 | 见 `C-8` |
|
| Q16 | 已定 | `POST /schd/sync` 请求与响应 | 请求体为空、请求全量(`C-4`);成功返回请求编号,开放请求未结案返回 `409`(`C-8`) |
|
||||||
| Q17 | 本系统 | 人工发 `RQRD` 的入口 | `US-09` 要求能人工发,HTTP 清单里没有 |
|
| Q17 | 本系统 | 人工发 `RQRD` 的入口 | `US-09` 要求能人工发,HTTP 清单里没有;旧系统也没有 |
|
||||||
| Q18 | 本系统 | 回退时正在处理的消息怎么办 | 见 `OPS-4` |
|
| Q18 | 已定 | 回退时正在处理的消息怎么办 | 未写回处理标记的消息仍算未处理,由旧系统继续;旧系统停机只等在途任务跑完(`OPS-4`) |
|
||||||
| Q19 | — | (未分配) | — |
|
| Q19 | — | (未分配) | — |
|
||||||
| Q20 | — | (未分配) | — |
|
| Q20 | — | (未分配) | — |
|
||||||
| Q21 | 本系统 | `GET /all/flights` HTTP 约定 | 见 `C-11` |
|
| Q21 | 已定 | `GET /all/flights` HTTP 约定 | 成功裸数组、失败 503 与错误对象;见 `C-11` |
|
||||||
| Q22 | 已定 | 静态参考数据对应哪些表 | `C-10`;物理为 schema `basicdata` 多表(非 `REF_MASTER` 单表),映射见 `implementation.md`「静态参考数据」 |
|
| Q22 | 已定 | 静态参考数据对应哪些表 | `C-10`;物理为 schema `basicdata` 多表(非 `REF_MASTER` 单表),映射见 `implementation.md`「静态参考数据」 |
|
||||||
| Q23 | 本系统 | Elasticsearch 历史怎么写、保留多久 | 见 `US-14`、`G-FLIGHT-HIST-RETENTION` |
|
| Q23 | 本系统 | Elasticsearch 历史怎么写、保留多久 | 见 `US-14`、`G-FLIGHT-HIST-RETENTION`。旧系统按 `SODT`+`FLID` 写入 `flight_hts`,保留期未实现 |
|
||||||
| Q24 | 本系统 | `REQ_TRACK` 已结案记录保留多久 | 见 `G-REQ-TRACK-RETENTION` |
|
| Q24 | 本系统 | `REQ_TRACK` 已结案记录保留多久 | 见 `G-REQ-TRACK-RETENTION`;旧系统没有对等记录,只有 `COUTMSGS` 的 ACK 列 |
|
||||||
| Q25 | 本系统 | 季度计划从哪来、什么格式 | SIS 只有日计划;旧系统读 Oracle 季度表 |
|
| Q25 | 已定 | 季度计划从哪来、什么格式 | 不属本系统范围:admin-api 从 Oracle `FIMS_FLIGHTSCHD_SEASON` 读(`C-4`) |
|
||||||
|
|
||||||
状态说明:**待对方** = 需对接方确认;**已定** = 结论已写入 `C-x`/`INV-x`;**本系统** = 由本系统与需求方决定;**—** = 编号保留未用。
|
状态说明:**待对方** = 需对接方确认;**已定** = 结论已写入 `C-x`/`INV-x`;**本系统** = 由本系统与需求方决定;**—** = 编号保留未用。
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import io.micronaut.http.annotation.Produces
|
|||||||
* 数据只从投影来,不回落 PG:查询与网页客户端必须读同一份,不然两边会看到不同的航班
|
* 数据只从投影来,不回落 PG:查询与网页客户端必须读同一份,不然两边会看到不同的航班
|
||||||
* (`INV-11`)。投影读不出来就返回错误——空列表会被前端当成"现在没有航班"(`US-12` AC2)。
|
* (`INV-11`)。投影读不出来就返回错误——空列表会被前端当成"现在没有航班"(`US-12` AC2)。
|
||||||
*
|
*
|
||||||
* 响应口径暂定(`Q21`):成功是裸 JSON 数组、不套旧系统的 `ResponseDto`;失败是 503 加一个
|
* 成功:裸 JSON 数组、不套旧系统的 `ResponseDto`;失败:503 加错误对象(`C-11`)。
|
||||||
* 错误对象。投影载荷的形状与 `KAFKA:schd` 同一份,尚未按旧系统 `SCHD.FLTR` 逐字对拍。
|
* 数组元素与 `KAFKA:schd` 同形,都是日计划 `FLTR` 转成的 JSON(`C-9`、`C-11`)。
|
||||||
*/
|
*/
|
||||||
@Controller("/all/flights")
|
@Controller("/all/flights")
|
||||||
class FlightQueryController(
|
class FlightQueryController(
|
||||||
@@ -45,7 +45,7 @@ class FlightQueryController(
|
|||||||
private fun nonSharedFlights(): List<com.fasterxml.jackson.databind.JsonNode> =
|
private fun nonSharedFlights(): List<com.fasterxml.jackson.databind.JsonNode> =
|
||||||
projection.readAll()
|
projection.readAll()
|
||||||
.map { mapper.readTree(it) }
|
.map { mapper.readTree(it) }
|
||||||
.filter { it.path("scalars").path("MAID").asText("").isBlank() }
|
.filter { it.path("MAID").asText("").isBlank() }
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val PROJECTION_UNAVAILABLE = "FLIGHT_PROJECTION_UNAVAILABLE"
|
private const val PROJECTION_UNAVAILABLE = "FLIGHT_PROJECTION_UNAVAILABLE"
|
||||||
|
|||||||
@@ -189,13 +189,22 @@ class AdftProcessor(
|
|||||||
// 共享小工具(处理器层私有约定)
|
// 共享小工具(处理器层私有约定)
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|
||||||
/** 航班整态载荷:`KAFKA:schd` 事件与 Redis 投影共用这一份形状。 */
|
/**
|
||||||
internal fun flightPayload(next: FlightSnapshot): Map<String, Any> = linkedMapOf(
|
* 航班整态载荷:`KAFKA:schd` 事件与 Redis 投影共用。
|
||||||
"flid" to next.flid,
|
*
|
||||||
"stateVersion" to next.stateVersion,
|
* 形状就是日计划一条 `FLTR` 转成 JSON(字段名与 SIS/XSD 一致,`C-9`、`C-11`):先 `FLID`,再标量,再集合段。
|
||||||
"scalars" to next.scalars,
|
* 空串与空集合不输出;内部的 `stateVersion` 不进载荷,只用于投影/事件代次守卫。
|
||||||
"collections" to next.collections,
|
*/
|
||||||
)
|
internal fun flightPayload(next: FlightSnapshot): Map<String, Any> {
|
||||||
|
val out = linkedMapOf<String, Any>("FLID" to next.flid)
|
||||||
|
next.scalars.forEach { (key, value) ->
|
||||||
|
if (value.isNotEmpty()) out[key] = value
|
||||||
|
}
|
||||||
|
next.collections.forEach { (key, rows) ->
|
||||||
|
if (rows.isNotEmpty()) out[key] = rows
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除通知:只走 `KAFKA:msg`,`schd` 不发 tombstone(`C-9`)。
|
* 删除通知:只走 `KAFKA:msg`,`schd` 不发 tombstone(`C-9`)。
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ interface FlightProjectionPort {
|
|||||||
class FlightProjectionFailure(msgId: Long, cause: Throwable) :
|
class FlightProjectionFailure(msgId: Long, cause: Throwable) :
|
||||||
RuntimeException("flight projection write failed msgId=$msgId: ${cause.message ?: cause.javaClass.simpleName}", cause)
|
RuntimeException("flight projection write failed msgId=$msgId: ${cause.message ?: cause.javaClass.simpleName}", cause)
|
||||||
|
|
||||||
/** 投影载荷与 `KAFKA:schd` 整态同一份 JSON:投影和通知同源,对账时不用比两种形状(`C-11`)。 */
|
/** 投影载荷与 `KAFKA:schd` 整态同一份:都是 `FLTR` 转 JSON(`C-9`、`C-11`)。 */
|
||||||
internal fun projectionOf(snapshot: FlightSnapshot, mapper: ObjectMapper): FlightProjectionWrite.Upsert =
|
internal fun projectionOf(snapshot: FlightSnapshot, mapper: ObjectMapper): FlightProjectionWrite.Upsert =
|
||||||
FlightProjectionWrite.Upsert(
|
FlightProjectionWrite.Upsert(
|
||||||
flid = snapshot.flid,
|
flid = snapshot.flid,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import org.junit.jupiter.api.Assertions.assertFalse
|
|||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
/** `GET /all/flights`:投影读、过滤共享航班、失败时 503(`US-12`、`C-11`、`Q21` 暂定)。 */
|
/** `GET /all/flights`:投影读、过滤共享航班、失败时 503(`US-12`、`C-11`)。 */
|
||||||
class FlightQueryControllerTest {
|
class FlightQueryControllerTest {
|
||||||
|
|
||||||
private val mapper = ObjectMapper()
|
private val mapper = ObjectMapper()
|
||||||
@@ -16,8 +16,8 @@ class FlightQueryControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `returns bare JSON array of non-shared flights`() {
|
fun `returns bare JSON array of non-shared flights`() {
|
||||||
val projection = StubFlightProjectionPort()
|
val projection = StubFlightProjectionPort()
|
||||||
projection.snapshots["1"] = """{"flid":"1","scalars":{"MAID":""}}"""
|
projection.snapshots["1"] = """{"FLID":"1","FLNO":"CA001"}"""
|
||||||
projection.snapshots["2"] = """{"flid":"2","scalars":{"MAID":"1"}}"""
|
projection.snapshots["2"] = """{"FLID":"2","FLNO":"CA002","MAID":"1"}"""
|
||||||
val controller = FlightQueryController(projection, mapper)
|
val controller = FlightQueryController(projection, mapper)
|
||||||
|
|
||||||
val response = controller.all()
|
val response = controller.all()
|
||||||
@@ -26,7 +26,8 @@ class FlightQueryControllerTest {
|
|||||||
val root = mapper.readTree(response.body()!!)
|
val root = mapper.readTree(response.body()!!)
|
||||||
assertTrue(root.isArray)
|
assertTrue(root.isArray)
|
||||||
assertEquals(1, root.size())
|
assertEquals(1, root.size())
|
||||||
assertEquals("1", root[0].path("flid").asText())
|
assertEquals("1", root[0].path("FLID").asText())
|
||||||
|
assertEquals("CA001", root[0].path("FLNO").asText())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.gzzn.omms.msgexchange.processing
|
||||||
|
|
||||||
|
import com.gzzn.omms.msgexchange.domain.flight.FlightSnapshot
|
||||||
|
import com.gzzn.omms.msgexchange.domain.flight.FlightState
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
/** 投影/`schd` 载荷是日计划 `FLTR` 转 JSON(`C-9`、`C-11`)。 */
|
||||||
|
class FlightPayloadTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `emits FLTR-shaped JSON and omits empty fields and internal version`() {
|
||||||
|
val snapshot = FlightSnapshot(
|
||||||
|
flid = "121112312",
|
||||||
|
operationDay = null,
|
||||||
|
state = FlightState.ACTIVE,
|
||||||
|
stateVersion = 7,
|
||||||
|
scalars = mapOf(
|
||||||
|
"FLNO" to "UA724",
|
||||||
|
"MAID" to "",
|
||||||
|
"REMC" to "keep",
|
||||||
|
),
|
||||||
|
collections = mapOf(
|
||||||
|
"GTDT" to listOf(mapOf("GATE" to "G28")),
|
||||||
|
"DELY" to emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val payload = flightPayload(snapshot)
|
||||||
|
|
||||||
|
assertEquals("121112312", payload["FLID"])
|
||||||
|
assertEquals("UA724", payload["FLNO"])
|
||||||
|
assertEquals("keep", payload["REMC"])
|
||||||
|
assertEquals(listOf(mapOf("GATE" to "G28")), payload["GTDT"])
|
||||||
|
assertFalse(payload.containsKey("MAID"))
|
||||||
|
assertFalse(payload.containsKey("DELY"))
|
||||||
|
assertFalse(payload.containsKey("stateVersion"))
|
||||||
|
assertFalse(payload.containsKey("scalars"))
|
||||||
|
assertFalse(payload.containsKey("collections"))
|
||||||
|
assertFalse(payload.containsKey("flid"))
|
||||||
|
assertTrue(payload.keys.first() == "FLID")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user