Files
msgexchange-v2/docs/legacy/msgexchange-api-legacy-user-stories.md
T
windyboy 50e0e7f389 docs: 修复跨文档冲突与外部规范口径
不动代码、配置与迁移,只收敛文档口径。

- 登记两项外部冲突:Q13 日计划缺失可选字段(SIS §3.16 注释 4 要求删除本地值,
  与现行"未携带字段保留"相反)、Q14 主/共享删除顺序与 EROR 回报义务
  (SIS §1.6.1-1.d / §4.8);航班级"缺席不删"补 SIS §3.16 注释 1 依据。
- flight-state §3.3 补主/共享关联按 FLID 值比较的规则。
- message-lifecycle:§11 水位不变量与 §5.1 空洞老化自洽;删除过期的 updatedAt
  判据,统一为 PROCESSING_STARTED_AT + 可注入 Clock;§5.1/§6 补 SIS 480 分钟
  Expiry 与 legacy 1 天保留窗,作为 Q2/Q6/Q7 的取值参照。
- architecture/design:明细表口径统一为 8 张资源明细表 + FLIGHT_ROUTE_POINT;
  Oracle 11g 断言收敛为待适配验证;配置键按 application.yml 实际名称加 msgx. 前缀。
- user-stories:Q2/Q6/Q11 补外部锚点并收窄 Q11 到重置周期 + SNDR 值域;
  新增 Q13/Q14 并挂入 US-05/06/08 前置;五个独立事实改为引用 message-lifecycle §1。
- README/legacy:文档基线改为代码基线 eea1120 + git log 指引;补"外部协议冲突以
  SIS 为准"的维护规则;修正 /all/flights 过滤措辞;术语统一为「回填意图」。

验证:文档间章节引用 0 缺失、markdown 0 断链、SIS 引用小节全部存在、
Q1–Q14 正文与 Q 表双向可解析、代码块配对正常。
2026-09-10 20:54:01 +08:00

551 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# msgexchange-api 旧项目业务逻辑与用户故事文档
## 0. 文档说明
> **nextgen 定位注(msgexchange-v2**:本文件描述 **legacy** 行为基线。nextgen 为**上游报文
> 处理中间件**——生产主路径 = JDBC 轮询共享 MySQL `CMINMSGS`(上游经 CIIMS 等外部落信);
> legacy §3.1 HTTP 收报与 §3.2 轮询在现役并存,nextgen 将轮询提升为主拓扑、HTTP 降为 compat
> (见仓库 `docs/architecture.md`)。
- 本文档记录 msgexchange-api(现役旧项目)的完整业务逻辑,并以用户故事形式组织,作为新系统(Micronaut + Kotlin)迁移设计及一致性对拍的行为基线。
- 内容依据仓库源码与资源配置编写:`src/main/java/com/gzzn/omms/msgexchange-api/**``src/main/resources/*.yml`;分支 `feature/add_dev_containers_supports`HEAD `eb72a668`)。
- 报文结构依据 `SIS_AODB_RMS-V0.1.md``unisysaodbsis.xsd`(同目录 docs/legacy/)。
- 优先级:本文只描述现役行为;与 `SIS_AODB_RMS-V0.1.md` 冲突时以 SIS 为准。
- 文档中"关联代码"均为相对仓库根目录的源码路径,可据此逐条复核。
## 1. 系统定位与集成边界
msgexchange-api 为成都机场 OMMS 消息交换服务(`com.gzzn.omms:msgexchange-api`1.2.1-SNAPSHOTSpring Boot 1.5.17 / Spring Cloud Edgware / Java 8;服务名 `msgexchangeapi`)。端口 8080 为 Spring Boot 缺省值——各 `application-*.yml` 均未配置 `server.port`,迁移时勿寻找该配置项。
职责概述:
1. 接收上游系统(AODB / CIIMS 等)下发的航班计划与动态 XML 报文;
2. 将报文落库(`CMINMSGS`)后异步解析处理,维护"最新动态航班库"Redis);
3. 通过 Kafka 将航班变更通知前端(ACDW);
4. 将已结束运营的航班沉淀为历史数据(Elasticsearch、MySQL 历史表);
5. 提供 HTTP 接口用于收报、日计划请求、动态航班查询与运维操作。
外部集成点如下:
| 集成对象 | 用途 | 说明 |
| --- | --- | --- |
| MySQL(库 `cdairport` | 消息持久化 | 表 `CMINMSGS`(入站报文)、`CMINMSGS_HST`(历史)、`CMINMSGS_TEST`(测试)、`COUTMSGS`(出站报文) |
| Redis Cluster | 动态航班库、基础数据缓存 | key `flightInfo``orms_stand``orms_stand_airbridge`RedisTemplate 启用 Jackson `enableDefaultTyping` |
| Kafka | 通知前端 | 业务主题 `msg`(单条事件)、`schd`(定时批量);另提供任意主题转发接口;本服务仅生产、不消费(无 `@KafkaListener` |
| Elasticsearch 6.x | 历史航班存储 | transport 客户端(9300);索引 `flight_hts``flight_hts2`dev 配置 `cluster.name=opensearch` |
| Eureka | 服务注册、admin-api 服务发现 | `@LoadBalanced RestTemplate` 按服务名 `ADMINAPI` 调用 |
| admin-apiACAA | 基础数据 | 机位列表、机位→登机桥编码两个接口(`basicdata` 模块) |
| Logstashlogback | 日志采集 | TCP socket 输出 JSON 日志 |
参与角色:报文发送方(上游系统或手工工具)、前端使用者(ACDW)、计划/签派操作员、值班运维人员、系统自身(轮询线程与定时任务)。
## 2. 术语与数据语义
| 术语 | 定义 |
| --- | --- |
| `CMINMSGS` 记录 | 一条入站原始报文。列:`CMINMSGS_ID`(自增主键)、`CMINMSGS_CLOB_MSG`(原始 XML)、`CMINMSGS_DATE_RECEIVED`(接收时间)、`CMINMSGS_DATE_PROCESSED``CMINMSGS_STATUS``CMINMSGS_SUBSYSTEM_*`(子系统信息;现役处理时未回填) |
| 处理标记语义 | 收报时 `CMINMSGS_DATE_PROCESSED=NULL`;处理完成(含失败或命中忽略清单)后由 Handler 置为当前时间。Handler 返回失败且未置位时(如日计划刷新失败、ADFT 数量校验失败),后续轮询将再次处理该消息 |
| `MSG` | 报文根对象(`entity/msg/MSG.java`):`META`TYPE / STYP / SNDR / DTTM / SEQN)与业务体(`SCHD` / `FLOP` / `RQFD` 等) |
| `SCHD.FLTR` | 动态航班对象,存储于 Redis `flightInfo`。关键字段:`FLID`(航班标识)、`SODT`/`ACTT`/`CNCL`(计划/实际/取消时间,格式 `ddMMMyyHHmm`)、`MVIN`A=到港,D=离港)、`MAID`(共享航班主航班 FLID)、`MAFL`(主航班的共享航班列表)、`ABTM` 及派生字段 `abdg``PSDT`/`LPSDT`(当前/历史计划机位)、`DELY`/`ABN`(延误与异常) |
| `ABN` 异常列表 | 类型:`DLY`(延误数组)、`CAN`(取消)、`RTN`(返航)、`ALT`(备降) |
| 共享航班 | `MAID != NULL` 的航班。其自身变更不直接发送 Kafka 消息,随主航班(MAID 指向的航班)变更一并下发 |
| Handler | 消息子类型的处理类,命名规则 `msghandler/{type小写}/{STYP}Handler`。Handler 非 Spring 管理的 Bean:由 Dispatcher 反射实例化,依赖经 `SpringUtil` 获取 |
| `ignoreMsg` | 忽略规则,逗号分隔的 `TYPE-STYP``TYPE-*`。命中的消息不进入 Handler,直接标记为已处理 |
| `flightInfo` | Redis hashfield=`FLID`value=`SCHD.FLTR`(带类型信息的 JSON 序列化) |
| `orms_stand` | Redis hashfield=机位编码,value=`OrmsStand` 对象 |
| `orms_stand_airbridge` | Redis hashfield=机位编码,value=登机桥编码数组的 JSON 字符串 |
| `COUTMSGS` | 出站报文表,列清单见 5.5 |
## 3. 端到端业务流程
### 3.1 入站收报
`POST /cminmsgs/send`,请求体为原始 XML 字符串。
调用链:`CminmsgsController.send``CminmsgServiceImpl.sendXmlMsg`。处理内容:
1. 创建 `CMINMSGS` 记录:`CLOB_MSG` 保存原始 XML`DATE_RECEIVED` 设为当前时间,`DATE_PROCESSED` 为空;
2. 落库并返回自增 ID`ResponseDto.success(id)`)。
收报接口不解析、不校验报文内容;业务处理异步进行。
### 3.2 消息采集与分派
- 启动:`AppRunner``CommandLineRunner`)调用 `MsgExchangeServiceImpl.start()`,以 `ScheduledExecutorService`(单线程,命名 `msgexchange-%d`)按 `msgExchange.intervalSeconds`(各环境均配置为 1 秒,代码无默认值)固定频率执行 `MsgExchangeRunner`
- 采集:`MsgExchangeRunner.run` 调用 `cminmsgService.getNewMsgsAfterId(0L)`,即查询 `CMINMSGS_ID > 0 且 DATE_PROCESSED IS NULL` 的记录,逐条调用 `MsgHandlerDispatcher.dispatch`;异常仅记录日志,不中断批次。
- 分派:`MsgHandlerDispatcher.dispatch` 执行以下步骤:
1. `exchangeService.xmlToMsg(clob)` 将 XML 解析为 `MSG`Jackson `XmlMapper` + JAXB 注解 introspection),构造 `CminmsgWapper`
2. 读取 `META.TYPE``META.STYP`;若命中 `ignoreMsg`,将该记录标记为已处理并返回成功;
3. 否则按命名规则反射加载 Handler(`Class.forName` + `newInstance`),调用 `run(CminmsgWapper)`
4. 任一步骤抛出异常:将该记录标记为已处理(避免滞留待处理队列)、记录错误日志、返回失败(不重试)。
### 3.3 计划类(SCHD-*)处理
分派目标为 `msghandler/schd/*Handler`(继承 `SchdBaseHandler`)。
**RESP / DNLD(日航班计划刷新,实现相同)**
`RESPHandler.run` / `DNLDHandler.run` 调用 `FlightInfoServiceImpl.updateByDaySchd`
1. 读取 `SCHD.FLTR` 全量列表;
2. `updateMafltr`:将共享航班(`MAID != NULL`)生成 `MAFLDATA(FLID, FLNO)`,追加到对应主航班的 `MAFL` 列表;
3. `updatefltrBridge`:将各航班 `ABTM` 中的 `ABDG` **按序直接追加**到派生字段 `abdg`(代码不插入分隔符——分隔符依赖 `ABDG` 数据本身是否带逗号),拼接结果若以逗号结尾则**仅删除末尾一个逗号**
4. 调用 `redisService.hmset("flightInfo", FLID→FLTR)` 写入航班集合。
说明:`RedisService.hmset` 实现为 `opsForHash().putAll`,仅新增/覆盖 field;这是 legacy 行为记录,不构成新系统的设计依据。
刷新成功:调用 `sendDschd` 向 Kafka 主题 `msg` 发送日计划到达通知(`MSG` 仅保留 `META`,业务体为空),随后将该记录标记为已处理,返回成功。
刷新失败(`updateByDaySchd` 返回 false):不标记已处理,返回失败;该消息在后续轮询中重试。
**ADFT(计划外航班添加)**
`ADFTHandler.run`(实际执行顺序):
1. 校验 `SCHD.FLTR` 数量为 1,否则直接 `HandlerResult.failure()` 返回且**不标记已处理**(与 RESP/DNLD 刷新失败同属"返回失败未置位"分支,后续轮询重试,见 5.7-2);
2. `saveFltr` 将航班写入 Redis`hset`);
3. 非共享航班(`MAID == NULL`):加入发送缓冲并调用 `sendMsg` 发送完整消息 JSON 至 Kafka `msg`
4. **标记已处理**(此步先于下方共享航班联动执行,代码中非共享发送与标记先于共享联动,与"处理完再标记"的直觉不同);
5. 共享航班(`MAID != NULL`):不直接发送;从 Redis 取主航班(`flightInfoService.getByFlid`),向主航班内存对象 `MAFL` 追加 `MAFLDATA(FLID, FLNO)`,再将变更后的主航班加入发送缓冲——**未调用 `saveFltr` 写回 Redis**(与 FDEL 同类遗留缺陷,见 5.7-14);若主航班不存在(`getByFlid` 返回 null),此处对 null 取 `getMafl()` 会抛空指针(Dispatcher 异常兜底会再次标记已处理)。
### 3.4 动态类(FLOP-*)处理
分派目标为 `msghandler/flop/*Handler`(共 29 个,继承 `FlopBaseHandler`)。通用处理流程 `FlopBaseHandler.run`
1.`FLOP.FLID`,调用 `flightInfoService.getByFlid` 查询 Redis
2. 航班不存在:标记已处理、记录告警、返回失败(视为处理完成,不再重试);
3. 航班存在:
- 子类覆写的 `updateFltr(FLOP, FLTR)` 就地更新动态航班(子类语义见 5.3);
- `saveFltr` 写回 Redis
- 若为非共享航班(`MAID == NULL`):加入发送缓冲并调用 `sendMsg` 发送至 Kafka `msg`
- 标记已处理,返回 `success(FLTR)`
例外:`FDELHandler` 覆写 `run`(删除航班)。处理内容:
1. 航班存在:`deleteFltr` 删除 Redis 记录;**无条件调用 `sendMsg`** 发送删除消息(由前端执行本地删除),不区分共享/非共享航班(与下述通用规则不一致,见 5.7);标记已处理;
2. 被删航班为共享航班:从主航班 `MAFL` 中移除对应元素,并将变更后的主航班加入发送缓冲。该修改仅作用于从 Redis 读出的内存对象(未调用 `saveFltr` 写回 Redis),元素比对使用 `==``MAFLDATA.getFlid()``FLTR.getFLID()` 均为 `BigInteger`,故为引用比较而非值比较),属遗留缺陷(见 5.7);
3. 航班不存在:标记已处理并返回失败。
共享航班联动规则(通用,FDEL 除外):动态与计划 Handler 对共享航班(`MAID != NULL`)自身变更不单独发送消息;主航班自身变更(更新、删除联动)将主航班放入发送缓冲,共享航班列表随主航班一并下发。
### 3.5 前端通知(Kafka
- 即时事件(主题 `msg`):由 3.3、3.4 中 `sendMsg` / `sendDschd` 发送。`exchangeService.msgToJson` 序列化 `META` 与 TYPE 对应的业务体。
- 批量缓冲(主题 `schd`):`FltrSendScheduled`(每 3 秒,cron `scheduled.flightSendCron`)调用 `FltrSendBufferServiceImpl.takeAllFltr` 取空进程内队列(`ConcurrentLinkedQueue`),经 `JsonUtil.getString` 序列化后发送至 Kafka `schd`。队列为空时不发送。
- **现役为 at-most-once**`takeAllFltr` 在 Kafka 发送前即复制并清空队列;序列化或发送异常仅记录日志、不回灌队列,该批消息可能整体丢失(见 US-B2、5.7-17)。
### 3.6 入站报文历史归档
`CminmsgsHisScheduled`cron `0 0 3 * * ?`,即 `scheduled.cminmsgHisCron`)调用 `CminmsgHstDao.moveToHistory`,以原生 SQL 在单事务内执行:
1. `INSERT INTO cminmsgs_hst SELECT ... FROM cminmsgs src WHERE NOT EXISTScminmsgs_hst 中已存在相同 IDAND src.CMINMSGS_DATE_RECEIVED < NOW() - 1 DAY`
2. `DELETE FROM cminmsgs WHERE CMINMSGS_DATE_RECEIVED < NOW() - 1 DAY`
说明:`CmoutmsgHstServiceImpl``insertToTable` 中按 `CMINMSGS_ID % 10` 分表(`cminmsgs_hst_0``cminmsgs_hst_9`)的历史归档实现仍保留于代码中,但未被当前定时任务使用。
### 3.7 动态航班转历史
`FlightHisScheduled`cron `0 30 3 * * ?`,即 `scheduled.flightInfoCron`)执行以下步骤:
1. `flightInfoService.findAll`:读取 Redis `flightInfo` 全部动态航班;
2. `FlightHisServiceImpl.getHisToBeTran`:按下列任一条件判定可转历史(时间字段格式 `ddMMMyyHHmm`,阈值为毫秒,见 5.4):
- `SODT`(计划时间)距当前时间超过 `SODT_HST_TIME`3 天);
- `CNCL`(取消时间)距当前时间超过 `CANCEL_HST_TIME`1 小时);
- 备降:`FDIV.DDES = CTU``FDIV.DDIR = FROM`(应降成都、实际备降他处),且 `SODT` 距当前时间超过 `FDIV_HST_TIME`
- 离港(`MVIN = D`):计划时间早于当日且存在实际时间 `ACTT`,且 `ACTT` 不晚于当前时间;
- 到港(`MVIN = A`):计划时间早于当日且存在实际时间,且 `ACTT` 距当前时间超过 `ARRIVE_HST_TIME`1 小时);
3. `FlightHisServiceImpl.save`:逐条按 `SODT + FLID` 查询 ES 索引 `flight_hts`type `_doc`),存在则更新、不存在则新增;写入内容为 `JSONObject.parseObject(JsonUtil.getString(FLTR))`;单条失败记录日志并跳过;
4. 保存成功的 `FLID` 列表经 `flightInfoService.batchDeleteFltr` 从 Redis 批量删除。
说明:`getHisToBeTran`(步骤 2)的逐条判定未做单条异常隔离:`SODT` 在四个判定方法中均无空值保护直接解析(缺失/非法即抛异常);`ACTT`/`CNCL` 为空有保护、仅"非空但格式非法"才抛;`MVIN` 为空且 `ACTT` 存在时 `equals` 调用(isDMatch:236、isAMatch:264)会 NPE。任一条抛异常都会由 `FlightHisScheduled` 外层捕获并中断当次整批,剩余航班不再处理;"单条失败不影响其他航班"仅适用于步骤 3 `save` 的 ES 写入循环(见 US-D2、5.7-18)。
### 3.8 HTTP 查询与运维接口
- `GET /all/flights`:读取 Redis 全部动态航班,过滤掉共享航班(`MAID != NULL`)后返回 `ResponseDto<List<SCHD.FLTR>>`
- `GET /flights/migrate`scroll 读取 ES `flight_hts` 全量数据,经 `transToNewFltrs` 还原 `SCHD.FLTR`(将 `ABN[].BODY` 按类型 DLY / CAN / RTN / ALT 映射回对象字段),逐条写入 ES `flight_hts2`。该接口为一次性数据迁移工具。
- `POST /kafka/topics/{name}/msgs`:请求体先按 JSON(Map)解析,成功则直接发送至 Kafka 主题 `{name}`;解析失败则按 XML 解析为 `MSG` 再转 JSON 发送。该接口提供 XML / JSON 双模转发能力。
- `POST /schd/sync`:见 3.9。
### 3.9 日计划请求(出站报文构造)
`POST /schd/sync`,请求体 `SchdSyncDto {startDate, endDate}`,时间格式 `yyyy-MM-dd hh:mm`
`SchdController.sync` 解析时间后调用 `CmoutmsgServiceImpl.sendSchdGetMsg`
1. 生成序号:`SEQN = max(COUTMSGS_ID) + 1`;无记录时为 `1`
2. 构造报文:`META{SNDR=OSH5, TYPE=RQFD, STYP=NONE, DTTM=yyyyMMddHHmmss, SEQN}`,业务体 `RQFD{STDB, STDE}`(时间格式 `ddMMMyyHHmm`,大写);使用 `MSGBuilder``METABuilder`
3. `exchangeService.msgToXml` 序列化为 XML(根元素 `MSG`,含 `xsi:noNamespaceSchemaLocation="unisysaodbsis.xsd"`);
4. 写入 `COUTMSGS``CLOB_MSG` 为报文 XML`DATE_INSERTED` 为当前时间,`ROUTINGID = OSH5RQFD`
代码检索结果:现役工程中不存在读取或发送 `COUTMSGS` 的执行器(对 `CmoutmsgDao` 的引用仅存在于 `CmoutmsgServiceImpl``save``findMaxId`)。`/schd/sync` 仅完成报文构造与落库,实际发送通道需另行确认。
### 3.10 基础数据(机位与登机桥)
- `OrmsStandManager`(单例,非 Spring Bean):读取 Redis `orms_stand`;键不存在时经 `OrmsStandServiceImpl.findAll``RestTemplate` GET `adminApi.ormsStandsUrl`,按 Eureka 服务名 `ADMINAPI` 解析)拉取全量机位,以 `standCode → OrmsStand` 写入 Redis(过期时间 3600 秒),随后读取返回。
- `OrmsStandAirbridgeManager`(单例,非 Spring Bean):读取 Redis `orms_stand_airbridge`;机位字段无缓存时经 `getAirbridgeCodeByStandCode`GET `.../ormsStands/{standCode}/airbridgeCode`)拉取,以 JSON 数组字符串存入 hash 字段,再读取并解析为 `List<String>`
- 使用方:机位相关 Handler(如 `PSDTHandler.updateAbg`)。处理规则:取最后一个计划机位编码查询机位基础数据;机位类型 `standType = near`(近机位)时将登机桥编码拼接至 `abdg`;远机位或清空机位消息时置 `abdg` 为空。
## 4. 用户故事
> 用户故事格式:`ID | 角色 | 期望能力 | 业务目标`。每条包含触发条件、处理流程(代码链路)与验收标准。
> 角色代号:上游 = 报文发送方;前端 = ACDW 使用者;计划 = 计划/签派操作员;运维 = 值班运维人员;系统 = 系统自身(轮询线程与定时任务)。
### EPIC-A 入站消息生命周期
**US-A1 接收并持久化上游 XML 报文**
- 角色 / 期望 / 目标:作为上游,期望提交完整 XML 报文并即时取得受理标识,以便确认报文已受理待处理。
- 触发条件:`POST /cminmsgs/send`,请求体为 XML 文本。
- 处理流程:`CminmsgsController.send``CminmsgServiceImpl.sendXmlMsg``CMINMSGS` 落库:`CLOB_MSG` 保存原始报文、`DATE_RECEIVED` 为当前时间、`DATE_PROCESSED` 为空)→ 返回受理 ID。
- 验收标准:
- 响应结构 `{is_success: true, body: <自增 ID>}`
- 数据表新增一条记录,且 `DATE_PROCESSED` 为空;
- 报文原样保存,不做解析或校验。
- 关联代码:`controller/CminmsgsController.java``service/CminmsgServiceImpl.java``dao/CminmsgDao.java``entity/Cminmsg.java`
**US-A2 定期采集未处理报文并分派**
- 角色 / 期望 / 目标:作为系统,期望周期性获取未处理报文并按类型转交对应 Handler,以便报文得到业务处理。
- 触发条件:应用启动后,按 `msgExchange.intervalSeconds`(各环境均配置为 1 秒,代码无默认值)周期执行。
- 处理流程:`MsgExchangeServiceImpl.start``MsgExchangeRunner.run`(查询 `ID > 0 且 DATE_PROCESSED IS NULL`)→ `MsgHandlerDispatcher.dispatch`(解析 XML、反射调用 Handler)。
- 验收标准:
- 已处理(`DATE_PROCESSED` 非空)的记录不会被再次采集;
- 单条消息处理异常不影响同批其他消息;
- 需区分"抛异常"与"Handler 返回失败":抛异常由 Dispatcher 兜底标记已处理(不会无限滞留);Handler 返回失败且未标记的分支(RESP/DNLD 刷新失败、ADFT 数量校验失败)会被轮询持续重试,直至每日归档移入历史表(见 5.7-2)。
- 关联代码:`runner/AppRunner.java``runner/MsgExchangeRunner.java``service/MsgExchangeServiceImpl.java``msghandler/MsgHandlerDispatcher.java``service/exchange/ExchangeServiceImpl.java`
**US-A3 忽略清单内消息仅登记不处理**
- 角色 / 期望 / 目标:作为上游,期望指定类型消息(如 `LDM-*``REGN-*``RSTA-*``EROR-*`)跳过业务处理,以避免无效处理与噪声日志。
- 触发条件:报文的 `META.TYPE-STYP` 命中 `msgExchange.ignoreMsg` 规则(逗号分隔,支持 `TYPE-STYP``TYPE-*`,大小写不敏感)。
- 处理流程:`MsgHandlerDispatcher.isNeedHandler` 判定为不需要处理 → `updateBatchProcessed` 标记已处理 → 返回成功。
- 验收标准:
- 命中规则的记录被标记为已处理;
- 不触发任何 Handler 反射调用。
- 关联代码:`msghandler/MsgHandlerDispatcher.java`;配置项 `msgExchange.ignoreMsg` 于各环境 `application-*.yml`(注意:test 环境值为 `error-*`,其余环境为 `eror-*`,属现存差异)。
**US-A4 日航班计划快照整体刷新(SCHD-RESP / SCHD-DNLD**
- 角色 / 期望 / 目标:作为上游,期望下发某时段的日航班计划(含多航班 `FLTR` 列表),以便本服务与前端获得最新计划基线。
- 触发条件:收到 `SCHD-RESP``SCHD-DNLD` 报文。
- 处理流程:`RESPHandler.run` / `DNLDHandler.run``FlightInfoServiceImpl.updateByDaySchd`
1. 重组共享航班(`MAFLDATA` 挂入主航班 `MAFL`);
2. 重组登机桥(`ABTM``ABDG` 按序直接追加为 `abdg`,见 3.3);
3. `hmset("flightInfo", FLID→FLTR)` 合并写入(新增/覆盖 field,不删除快照中未出现的旧 field);
4. 成功后发送日计划到达通知至 Kafka `msg`,并标记已处理。
- 验收标准:
- Redis `flightInfo` 合并写入:报文内航班被新增/覆盖;该行为仅供 legacy 对拍使用;
- 主航班 `MAFL` 包含各共享航班的 `FLID` / `FLNO`
- 各航班 `abdg``ABDG` 值按序直接追加的结果:分隔符依赖数据本身,代码不插入分隔符;仅当拼接结果以逗号结尾时删除末尾一个逗号。回放测试需按该真实机制设定期望(见 3.3);
- Kafka `msg` 收到仅含 `META` 的通知消息;
- 刷新失败时该消息不标记已处理,后续轮询重试。
- 关联代码:`msghandler/schd/RESPHandler.java``msghandler/schd/DNLDHandler.java``service/flightInfo/FlightInfoServiceImpl.java``service/exchange/ExchangeServiceImpl.java``service/CminmsgServiceImpl.java`
**US-A5 添加计划外临时航班(SCHD-ADFT)**
- 角色 / 期望 / 目标:作为上游,期望推送单条临时航班计划,以便其进入动态航班库并即时通知前端。
- 触发条件:收到 `SCHD-ADFT` 报文。
- 处理流程:`ADFTHandler.run`(按代码实际顺序;约束:仅处理 `FLTR` 恰含 1 条的报文,数量≠1 属失败路径而非超出处理范围):
1. 校验 `FLTR` 数量为 1;不满足则返回失败且**不标记已处理**(后续轮询重试,见 5.7-2);
2. `saveFltr` 写入 Redis
3. 非共享航班(`MAID == NULL`)加入缓冲并发送 Kafka `msg`
4. 标记已处理(先于共享航班联动执行);
5. 共享航班:向从 Redis 取出的主航班内存对象 `MAFL` 追加 `MAFLDATA` 并将主航班加入缓冲(该修改仅作用于内存对象,未写回 Redis,见 5.7-14);主航班不存在时此处抛空指针。
- 验收标准(现役可证实范围):
- 单条航班写入 Redis
- 共享航班联动仅作用于发送缓冲中的主航班内存对象;Redis 内主航班记录不保证被同步更新(遗留缺陷,见 5.7-14);
- 通知与缓冲规则与动态航班一致(见 US-B1、US-B2)。
- 关联代码:`msghandler/schd/ADFTHandler.java`
**US-A6 按 FLOP-* 事件增量更新动态航班**
- 角色 / 期望 / 目标:作为上游,期望按 `FLID` 下发指定事件的航班变更,以便动态航班库字段精确更新并同步前端。
- 触发条件:`META.TYPE=FLOP``STYP` 对应 29 个动态 Handler 之一。
- 处理流程:`FlopBaseHandler.run`
1.`FLID` 查询 Redis
2. 航班存在:子类 `updateFltr` 就地更新 → `saveFltr` 写回 → 非共享航班加入缓冲并发送 `msg` → 标记已处理;
3. 航班不存在:标记已处理并返回失败。
- 验收标准:
- 仅当目标航班存在时执行更新;
- 更新结果写回 Redis
- 共享航班自身不直接发送消息。
- 关联代码:`msghandler/flop/base/FlopBaseHandler.java` 及各覆写 `updateFltr` 的子类(清单见 5.3)。
**US-A7 各 FLOP 子事件字段更新语义**
- 说明:以下子事件语义依据各 Handler 实现代码核实;**不表示均已具备自动化测试**。现有测试仅覆盖 ABTM、CHOT、DELY、LACL、PSDT 五个子类型(`src/test/java/.../msghandler/flop/`);ACTT、CNCL、FDEL 等关键路径无对应测试。按 29 个 STYP 建立「输入样本 × Redis 状态变更 × Kafka 通知」验收矩阵为新系统要求(见 6 节)。
- `ACTT` 实际时间:`FLTR.ACTT = FLOP.ACTT``ACTTHandler`);
- `ABTM` 靠桥 / 撤桥:按 `ABOP`A=靠桥,D=撤桥)在 `ABTM` 列表中定位同动作元素,存在则仅更新 `AOTM`,不存在则新增(`ABTMHandler`);
- `PSDT` 机位:机位相同则直接返回;否则将旧机位写入 `LPSDT`、清空并写入新 `PSDT`(机位号 `PSNO=0` 且无状态视为清空);随后按最后一个机位及基础数据计算 `abdg``PSDTHandler`);
- `DELY` 延误:清空旧延误;空 `CODE` 表示取消延误(同步清理 `ABN` 中的 DLY 元素);否则写入 `DELY` 并替换 `ABN` 中 DLY 元素(`DELYHandler`);
- `CNCL` 取消:写入 `CNCL`,并维护 `ABN` 中 CAN 元素(`CNCLHandler`);
- `FDEL` 删除:见 US-A8。
- 其余子类型(FRET / PADT / CLDT / FTSS / TRML / FDIV / HNAG / STND / ROUT / RENO / BOTM / BDPB / REMC / VIPP / FLIN / LACL / ESTT / TAOP / GTDT / CKDT / FLTY / ACFT / CHOT)的字段更新逻辑以各自 `updateFltr()` 实现为准。
- 关联代码:`msghandler/flop/*.java``src/test/java/.../msghandler/flop/*Test.java`
**US-A8 删除动态航班(FLOP-FDEL**
- 角色 / 期望 / 目标:作为上游,期望通知某航班从运营计划中删除,以便本服务删除实时记录并通知前端同步删除。
- 处理流程:`FDELHandler.run`
1. 航班存在:`deleteFltr` 删除 Redis 记录 → 无条件发送删除消息至 Kafka `msg`(不区分共享/非共享航班)→ 标记已处理;
2. 被删航班为共享航班:从内存中的主航班 `MAFL` 移除对应元素并缓冲主航班(该修改未写回 Redis,见 5.7);
3. 航班不存在:标记已处理并返回失败。
- 验收标准(现役可证实范围):
- Redis 中对应 `FLID` 被删除;
- 被删航班为共享航班时:发送缓冲中主航班的 `MAFL` 不再包含被删共享航班;Redis 内主航班记录不受该操作影响(遗留缺陷,见 5.7)。
- 关联代码:`msghandler/flop/FDELHandler.java``service/flightInfo/FlightInfoServiceImpl.java`
### EPIC-B 前端通知
**US-B1 单条动态即时通知**
- 角色 / 期望 / 目标:作为前端,期望在航班动态更新、删除或日计划完成时即时收到单条 JSON 消息,以便实时更新展示。
- 处理流程:`FlopBaseHandler.sendMsg` / `SchdBaseHandler.sendMsg|sendDschd``exchangeService.msgToJson(msg)``kafkaservice.msgSend("msg", json)`
- 验收标准:消息发送至主题 `msg`;动态与计划 Handler 中共享航班自身不触发单条通知。
- 例外(遗留行为):`FDELHandler` 对共享航班的删除同样发送 `msg` 删除消息(无条件调用 `sendMsg`)。是否保留该通知语义由迁移决策确定(见 5.7)。
- 关联代码:`msghandler/flop/base/FlopBaseHandler.java``msghandler/schd/base/SchdBaseHandler.java``service/KafkaServiceImpl.java``service/exchange/ExchangeServiceImpl.java`
**US-B2 批量缓冲定时下发**
- 角色 / 期望 / 目标:作为前端,期望定期收到本窗口内变更航班的聚合消息,以便合并刷新并降低消息量。
- 处理流程:`FltrSendScheduled`cron `*/3 * * * * ?`)→ `takeAllFltr` 取空进程内队列 → `JsonUtil.getString(fltrs)` → 发送至 Kafka `schd`
- 验收标准:消息发送至主题 `schd`,内容为 `FLTR` 数组 JSON;队列为空时不发送。
- 现役限制:`takeAllFltr` 先取空队列再发送,发送/序列化异常仅记录日志、不回灌,为 **at-most-once**(批次可能整体丢失),"定期收到"不保证可靠投递(见 3.5、5.7-17、6.2-5)。
- 关联代码:`scheduled/FltrSendScheduled.java``service/FltrSendBufferServiceImpl.java`
### EPIC-C 日计划请求
**US-C1 按时间范围发起日计划请求**
- 角色 / 期望 / 目标:作为计划人员,期望提交时间范围,以登记一次日航班计划请求(RQFD 出站报文写入 `COUTMSGS`)。现役能力止于"登记出站意图",不含实际投递。
- 触发条件:`POST /schd/sync`,请求体 `{startDate, endDate}`(时间格式 `yyyy-MM-dd hh:mm`)。
- 处理流程:`SchdController.sync`(时间解析)→ `CmoutmsgServiceImpl.sendSchdGetMsg`
1. `SEQN = max(COUTMSGS_ID) + 1`
2. 构造 `RQFD-NONE` 报文(`RQFD.STDB` / `STDE` 格式 `ddMMMyyHHmm`,大写);
3. `msgToXml` 序列化后写入 `COUTMSGS``ROUTINGID=OSH5RQFD`)。
- 验收标准(现役可证实范围):
- 报文 `META{SNDR=OSH5, TYPE=RQFD, STYP=NONE}` 且含时间段字段;
- `COUTMSGS` 新增记录,XML 根元素为 `MSG` 并声明 `unisysaodbsis.xsd`
- 现役实现约束(经代码核实):
- 控制器不校验 `sendSchdGetMsg` 返回值;
- 时间解析采用 12 小时制 `hh`(无 AM/PM 标记),空值、区间合法性与解析失败均无校验;
- 工程内无 `COUTMSGS` 读取/发送执行器(见 3.9)。
- 迁移要求:新系统须显式定义投递责任与出站状态机,提供重试与可观测性;时间格式改为 24 小时制(`HH`);对时间空值、区间及解析失败做输入校验(见 6 节)。
- 关联代码:`controller/SchdController.java``service/CmoutmsgServiceImpl.java``entity/msg/builder/MSGBuilder.java``entity/msg/builder/METABuilder.java`
### EPIC-D 历史与归档
**US-D1 每日归档过期入站报文**
- 角色 / 期望 / 目标:作为系统,期望在每日固定时间将接收超过一天的 `CMINMSGS` 转入历史表并清理原表,以控制主表数据量。
- 处理流程:`CminmsgsHisScheduled`cron `0 0 3 * * ?`)→ `CminmsgHstDao.moveToHistory`INSERT...SELECT NOT EXISTS + DELETE,单事务)。
- 验收标准:
- `cminmsgs_hst` 增加符合条件记录(列见 5.5);
- 原表删除一天前的记录;
- 历史表中已存在的 ID 不重复归档。
- 关联代码:`scheduled/CminmsgsHisScheduled.java``dao/CminmsgHstDaoImpl.java`
**US-D2 每日将过期动态航班写入历史索引**
- 角色 / 期望 / 目标:作为系统,期望将已结束运营的航班从实时库转入 Elasticsearch 历史库,以便历史查询可用并保持实时库精简。
- 处理流程:`FlightHisScheduled`cron `0 30 3 * * ?`)→ `findAll``getHisToBeTran`(判定条件见 3.7)→ `FlightHisServiceImpl.save`(按 `SODT + FLID` upsert 至 `flight_hts`)→ 成功航班从 Redis 删除。
- 验收标准:
- 满足任一历史条件的航班写入 ES
- 仅写入成功的航班从 Redis 删除;
- "单条失败不影响其他航班"仅适用于 ES 写入阶段(`save` 循环逐条捕获异常);`getHisToBeTran` 资格判定阶段未逐条隔离,空/非法时间字段会中断当次整批(见 3.7、5.7-18)。
- 关联代码:`scheduled/FlightHisScheduled.java``service/flightInfo/FlightHisServiceImpl.java``elasticsearch/ElasticsearchUtil.java``service/flightInfo/FlightInfoServiceImpl.java`
**US-D3 一次性历史索引迁移**
- 角色 / 期望 / 目标:作为运维,期望将 `flight_hts` 全量历史迁移至新索引 `flight_hts2`,以完成索引结构变更。
- 触发条件:`GET /flights/migrate`
- 处理流程:scroll 全量读取 `flight_hts``transToNewFltrs`(还原 `ABN` 子对象 DLY / CAN / RTN / ALT)→ 逐条写入 `flight_hts2`
- 验收标准:数据完整写入 `flight_hts2`,异常子对象还原正确。
- 关联代码:`controller/FlightsController.java`migrateFlightHst / transToNewFltrs / mapToDly)。
### EPIC-E 实时查询与基础数据
**US-E1 查询全部最新动态航班**
- 角色 / 期望 / 目标:作为前端或调用方,期望获取当前全量动态航班(不含共享航班),以便页面渲染。
- 触发条件:`GET /all/flights`
- 处理流程:`findAll`Redis hmget)→ 过滤掉共享航班(`MAID != NULL`)→ `ResponseDto.success`
- 验收标准:返回结果不含共享航班记录,内容与 Redis `flightInfo` 一致。
- 关联代码:`controller/FlightsController.java``service/flightInfo/FlightInfoServiceImpl.java`
**US-E2 以机位与登机桥基础数据完善动态航班**
- 角色 / 期望 / 目标:作为系统,期望依据计划机位获取机位类型与登机桥编码,以生成动态航班的登机桥字段。
- 处理流程:`PSDTHandler.updateAbg``OrmsStandManager` / `OrmsStandAirbridgeManager`(读取 Redis 缓存,未命中时经 `RestTemplate` 调用 admin-api)。
- 验收标准:近机位(`standType = near`)生成 `abdg`;远机位或清空机位消息时 `abdg` 为空。
- 关联代码:`basicdata/manager/OrmsStandManager.java``basicdata/manager/OrmsStandAirbridgeManager.java``basicdata/service/OrmsStandServiceImpl.java``msghandler/flop/PSDTHandler.java`
### EPIC-F 运维工具
**US-F1 转发报文至指定 Kafka 主题**
- 角色 / 期望 / 目标:作为运维,期望将 JSON 或 XML 报文发送至指定主题,以支持联调、补发与压测。
- 触发条件:`POST /kafka/topics/{name}/msgs`
- 处理流程:请求体按 JSON 解析成功则直接发送;失败则按 XML 解析为 `MSG` 后转 JSON 发送。
- 验收标准:JSON 直发;XML 解析后发送;两种输入均返回成功响应。
- 关联代码:`controller/KafkaController.java``utils/JsonUtil.java``service/exchange/ExchangeServiceImpl.java`
**US-F2 服务启动即开始消息处理**
- 角色 / 期望 / 目标:作为运维,期望服务启动后自动开始消息轮询处理,无需人工干预。
- 触发条件:应用启动(`CommandLineRunner` 执行完成)。
- 处理流程:`AppRunner.run``CommandLineRunner`)→ `MsgExchangeServiceImpl.start`
- 验收标准:启动后即按 `msgExchange.intervalSeconds` 周期开始轮询并分派未处理报文(见 US-A2)。
- 关联代码:`runner/AppRunner.java`
## 5. 附录
### 5.1 HTTP 接口清单
| 方法 / 路径 | 请求体 | 响应(ResponseDto | 功能 | 实现 |
| --- | --- | --- | --- | --- |
| `POST /cminmsgs/send` | XML 文本 | `body=CMINMSGS_ID` | 收报入库 | `CminmsgsController` |
| `POST /schd/sync` | `{startDate, endDate}` | 成功 | 构造 RQFD-NONE 报文并写入 COUTMSGS | `SchdController` |
| `GET /all/flights` | — | `body=List<SCHD.FLTR>`(非共享) | 全量动态航班查询 | `FlightsController` |
| `GET /flights/migrate` | — | 成功 | 迁移 `flight_hts``flight_hts2` | `FlightsController` |
| `POST /kafka/topics/{name}/msgs` | JSON 或 XML | 成功 | 转发至指定 Kafka 主题 | `KafkaController` |
统一响应封装:`dto/ResponseDto.java`(字段 `is_success``err_code``err_msg``body`)与 `enums/ResultCode.java`
### 5.2 定时任务与后台线程
| 名称 | 周期(配置 key) | 行为 | 实现 |
| --- | --- | --- | --- |
| 消息轮询处理 | `msgExchange.intervalSeconds`(各环境均配置为 1 秒,代码无默认值) | 采集未处理 CMINMSGS 并分派 | `AppRunner``MsgExchangeServiceImpl``MsgExchangeRunner` |
| 入站报文历史归档 | `scheduled.cminmsgHisCron``0 0 3 * * ?` | 一天前报文转 `CMINMSGS_HST` 并清理 | `CminmsgsHisScheduled` |
| 动态航班转历史 | `scheduled.flightInfoCron``0 30 3 * * ?` | 过期航班写 ES `flight_hts`,从 Redis 删除 | `FlightHisScheduled` |
| 缓冲批量发送 | `scheduled.flightSendCron``*/3 * * * * ?` | 取空缓冲并发送 Kafka `schd` | `FltrSendScheduled` |
| 异步执行线程池 | `scheduled.corePoolSize` / `maxPoolSize` / `queueCapacity` | 定时任务异步执行 | `config/ScheduledAsyncConfig.java` |
### 5.3 消息类型与 Handler 清单
计划类(`msghandler/schd/`,继承 `SchdBaseHandler`):
| STYP | Handler | 行为 |
| --- | --- | --- |
| RESP | `RESPHandler` | 日计划快照刷新、通知、标记已处理;失败时重试 |
| DNLD | `DNLDHandler` | 同 RESP |
| ADFT | `ADFTHandler` | 添加单条计划外航班并联动共享航班 |
动态类(`msghandler/flop/`,共 29 个,继承 `FlopBaseHandler`;除 FDEL 覆写 `run` 外,其余覆写 `updateFltr`):
`ABTM``FRET``PADT``ACTT``PSDT``CLDT``FTSS``TRML``FDEL``FDIV``HNAG``STND``DELY``ROUT``RENO``BOTM``CNCL``BDPB``REMC``VIPP``FLIN``LACL``ESTT``TAOP``GTDT``CKDT``FLTY``ACFT``CHOT`
已核实语义:`ACTT` 实际时间、`ABTM` 靠 / 撤桥时间、`PSDT` 机位、`DELY` 延误、`CNCL` 取消、`FDEL` 删除;其余子类型以各自 `updateFltr()` 实现及 SIS 规范为准。
忽略清单默认值:dev / local / pro = `ldm-*,regn-*,rsta-*,eror-*`test = `ldm-*,regn-*,rsta-*,error-*`
### 5.4 配置项(示例取自 application-dev.yml
- `spring.jpa``ddl-auto=none`;物理命名策略 `PhysicalNamingStrategyStandardImpl`
- `spring.datasource`MySQL,库 `cdairport`
- `spring.redis.cluster`nodes / password / pool。
- `kafka.producer.*`servers、retries=0、linger=1、batch、buffer、max-request`kafka.consumer.*`:含 zookeeper 遗留配置,无监听器使用。
- `msgExchange``intervalSeconds=1``ignoreMsg``waitDateLeftOffset=600000`(各环境均有配置,但 Java 代码零引用,**未被使用**,迁移时可剔除)。
- `logstash.host`dev = `hz011.int.it2000.com.cn:5555`local = test = `130.120.3.234:5000`pro = `172.17.35.160:5000`application-pro.yml:60,与 local/test 不同)。
- `scheduled``flightInfoCron=0 30 3 * * ?``cminmsgHisCron=0 0 3 * * ?``flightSendCron=*/3 * * * * ?``corePoolSize=10``maxPoolSize=50``queueCapacity=10`
- `hstCondition`(单位毫秒):`ARRIVE_HST_TIME=3600000`1 小时)、`CANCEL_HST_TIME=3600000`1 小时)、`FDIV_HST_TIME=59400000``SODT_HST_TIME=259200000`3 天)。
- `elasticsearch`ip / port=9300 / pool=5 / cluster.namedev = `opensearch`/ nodes。
- `eureka.client.service-url.defaultZone``eureka.instance.hostName` / `prefer-ip-address`
- `adminApi`schema / baseUrldev、test、pro 使用服务名 `ADMINAPI`;仅 local 使用 host:port `130.120.3.231:92`)、`ormsStandsUrl``getAirbridgeCodeByStandCodeUrl`
### 5.5 数据表列清单(实体映射)
`CMINMSGS``entity/Cminmsg.java`):`CMINMSGS_ID``CMINMSGS_CLOB_MSG``CMINMSGS_DATE_PROCESSED``CMINMSGS_DATE_RECEIVED``CMINMSGS_STATUS``CMINMSGS_SUBSYSTEM_DATE_SENT``CMINMSGS_SUBSYSTEM_NAME``CMINMSGS_SUBSYSTEM_SEQUENCE``CMINMSGS_SUBSYSTEM_SUBTYPE``CMINMSGS_SUBSYSTEM_TYPE`
`CMINMSGS_HST``entity/CminmsgHst.java`)与 `CMINMSGS_TEST``entity/CminmsgTest.java`):列同上。
`COUTMSGS``entity/Cmoutmsg.java`):`COUTMSGS_ID``COUTMSGS_ACK_DATE_RECV``COUTMSGS_ACK_REQD``COUTMSGS_ACK_RESEND_TIMES``COUTMSGS_CLOB_MSG``COUTMSGS_DATE_INSERTED``COUTMSGS_DATE_SENT``COUTMSGS_ENCRYPT``COUTMSGS_ERROR``COUTMSGS_FINAL_GROUP_IND``COUTMSGS_GROUP_ID``COUTMSGS_GROUP_ORDER``COUTMSGS_NO_MESSAGES``COUTMSGS_TRUEFALS_GROUP``ROUTINGID`
### 5.6 Redis key 汇总
| key | 结构 | 内容 | 维护方式 |
| --- | --- | --- | --- |
| `flightInfo` | hashFLID → FLTR | 动态航班实时库 | 日计划合并写入(`hmset` = putAll,不清理差集)/ 动态增量 / 转历史后删除 |
| `orms_stand` | hashstandCode → OrmsStand | 机位基础数据 | 未命中时全量拉取并写入,过期 3600 秒 |
| `orms_stand_airbridge` | hashstandCode → JSON 数组字符串 | 机位与登机桥关系 | 逐机位懒加载写入,过期 3600 秒 |
### 5.7 实现特征与已知问题
以下为经代码核实的实现特征,供新系统设计对照:
1. Handler 由 Dispatcher 反射实例化(非 Spring Bean),依赖经 `SpringUtil` 手动获取;分派通过反射调用 `run`
2. 处理标记不区分成功与失败:Handler 内部失败亦标记已处理;不标记分支包括 RESP / DNLD 刷新失败(`updateByDaySchd` 返回 false)与 ADFT 数量校验失败(直接 `HandlerResult.failure()`)。这些消息按轮询周期持续重试,无显式重试上限/死信;每日 3:00 归档任务按 `CMINMSGS_DATE_RECEIVED < 1 天` 将其移入历史表并删除(重试窗口约 1 天,见 3.6)。
3. XML 解析与序列化基于 Jackson XMLJAXB 注解 introspection);`msgToXml` / `msgToJson` 通过反射 `get{TYPE}()` 获取业务体;`xmlToMsg` 含 BDPB 空元素兼容处理。
4. RedisTemplate 启用 Jackson `enableDefaultTyping``orms_stand_airbridge` 的值为 JSON 字符串(存在二次编码)。
5. JSON 处理并存两套工具:`JsonUtil`Jackson)与 fastjson `JSONObject`ES 写入、FlightsController);部分控制器直接实例化 `ObjectMapper`
6. `CmoutmsgHstServiceImpl``CMINMSGS_ID % 10` 分表归档逻辑及 `insertToTable` 仍保留,但未被当前定时任务使用(现使用单表 `cminmsgs_hst``moveToHistory`)。
7. Kafka 仅生产不消费;`kafka.consumer.*` 与 zookeeper 配置无消费方;业务主题固定为 `msg``schd`
8. `COUTMSGS` 仅写入无读取;`/schd/sync` 产物无发送执行器(见 3.9)。
9. 明文口令直接存放于各环境 `application-*.yml`;默认激活环境为 dev`spring.profiles.active=dev`)。
10. 忽略清单在 test 环境配置为 `error-*`,与其他环境(`eror-*`)不一致。
11. `hstCondition` 各阈值按毫秒计算(配置注释亦为毫秒),而相关代码注释存在单位表述不一致。
12. `updateByDaySchd``RedisService.hmset``putAll`)合并写入;该实现细节仅描述 legacy 行为。
13. `FDELHandler` 对共享航班的删除同样调用 `sendMsg` 发送 `msg` 删除消息(与"共享航班不直接发送"的通用规则不一致)。
14. `FDELHandler` 删除共享航班、`ADFTHandler` 添加共享航班时,对主航班 `MAFL` 的修改均仅作用于内存对象并进入发送缓冲,未调用 `saveFltr` 写回 Redis;FDEL 元素比对使用 `==``MAFLDATA.getFlid()``FLTR.getFLID()` 均为 `BigInteger`,故为引用比较而非值比较)。属遗留缺陷,新系统应采用值相等比较并在同一原子状态变更中持久化主/共享航班关系。
15. `/schd/sync` 时间解析使用 12 小时制 `hh`(无 AM/PM 标记),且对空值、区间、解析失败及服务返回值均无校验。
16. `updatefltrBridge``ABDG` 为**按序直接追加**,代码不插入分隔符(分隔符依赖数据自身是否带逗号),仅当拼接结果以逗号结尾时删除末尾一个逗号(见 3.3)。
17. Kafka 批量缓冲(主题 `schd`)为 **at-most-once**`FltrSendScheduled``takeAllFltr` 先取空进程内队列,序列化或发送异常仅记录日志、不回灌队列,该批消息可能整体丢失(见 3.5、US-B2)。
18. `FlightHisServiceImpl.getHisToBeTran`(资格判定)未对单条航班做异常隔离:`SODT` 缺失/非法(四个判定方法均无空值保护直接解析)、`ACTT`/`CNCL` 非空但格式非法、`MVIN` 为空且 `ACTT` 存在(isDMatch/isAMatch 的 `equals` NPE)等任一条异常都会由 `FlightHisScheduled` 外层捕获并中断当次整批,剩余航班不再处理;仅 `save`(ES 写入)循环逐条捕获异常(见 3.7、US-D2)。
## 6. 迁移验收边界(评审并入)
本节内容依据 Plane ACMA-4 评审意见整理,用于区分"现役行为"(正文与附录的描述对象)与"新系统验收要求"(本节),避免迁移对拍时把待实现语义当作现役事实。
### 6.1 类别区分
- 现役行为:文档正文与附录所描述内容,均可经"关联代码"复核。
- 迁移目标:新系统须显式实现的能力(见 6.2),不以现役实现为上限。
- 待确认外部契约:`COUTMSGS` 投递通道与协议(3.9)、搜索集群类型(ES 或 OpenSearch,见 5.4)、前端对 FDEL 通知语义的依赖(US-B1 例外)。
### 6.2 迁移验收边界
1. **日计划语义**:新系统的日计划处理规则以 [运营航班状态设计](../flight-state.md) 为准,不从 legacy Redis 行为推导。
2. **共享航班增删链路**:决定保留或消除 FDEL 对共享航班的通知(US-B1 例外);主/共享航班关系(ADFT 添加、FDEL 删除)须在同一原子状态变更中持久化(修复 ADFT/FDEL 未写回与 FDEL `==` 引用比较缺陷,见 5.7-14)。
3. **可执行验收矩阵**:覆盖 3 个 SCHD 与 29 个 FLOP 子类型,维度为「输入样本 × Redis 状态变更 × Kafka 通知结果」;对当前无测试的关键路径(如 ACTT、CNCL、FDEL、RESP、DNLD、ADFT)补自动化用例。
4. **入站可靠性**:每类消息的幂等键、重复投递与多实例并发行为。
5. **失败处理**:解析、业务、Kafka、Redis、ES 失败的分类、重试上限、死信与人工重放。
6. **一致性**:跨 MySQL、Redis、Kafka、ES 的一致性与可恢复性约束。
7. **契约**:消息顺序、共享航班更新与前端可观察事件的契约测试。
8. **安全基线**:移除 `application-*.yml` 明文凭据与 Redis 默认多态反序列化(`enableDefaultTyping`)。
### 6.3 评审采纳记录
| ACMA-4 评审意见 | 处理 | 文档落点 |
| --- | --- | --- |
| 1 日计划"整体覆盖"表述不符 | 采纳,改为合并写入语义 | 3.3、US-A4、5.6、5.7-12、6.2-1 |
| 2 FDEL 共享航班亦发送通知 | 采纳,标注例外 | 3.4、US-A8、US-B1、5.7-13、6.2-2 |
| 3 共享航班删除关系不保证持久化 | 采纳,标注遗留缺陷 | 3.4、US-A8、5.7-14、6.2-2 |
| 4 测试覆盖声明过度 | 采纳,区分"语义核实"与"测试覆盖" | US-A7、5.7、6.2-3 |
| 5 RQFD 故事业务承诺超出现役 | 采纳,改为"登记出站意图" | US-C1、3.9、5.7-15、6.2 |
| 补充验收边界 | 采纳 | 6.2、6.3 |
### 6.4 第二轮评审采纳记录(2026-09-03 ZCode 自动评审 + 二次全面复审)
| 评审项 | 处理 | 文档落点 |
| --- | --- | --- |
| 1 logstash.host pro 环境值错误 | 采纳,pro = `172.17.35.160:5000` | 5.4 |
| 2 adminApi.baseUrl test 环境应为服务名 | 采纳,dev/test/pro 用 `ADMINAPI`,仅 local 用 host:port | 5.4 |
| 3 ADFT 数量校验失败不标记、持续重试 | 采纳,纳入"不标记"分支,区分抛异常/返回失败 | 3.3、US-A2、US-A5、5.7-2 |
| 4 ADFT 共享航班联动同样不持久化主航班 MAFL | 采纳,扩展 5.7-14 为 ADFT/FDEL;验收收敛为缓冲对象 | 3.3、US-A5、5.7-14、6.2-2 |
| 5 abdg 拼接机制为按序直接追加 | 采纳,改写 3.3 与 US-A4 验收 | 3.3、US-A4、5.7-16 |
| 6 FDEL `==` 为 BigInteger 引用比较 | 采纳,修正 Long 表述 | 3.4、5.7-14 |
| 7 ADFT 标记已处理先于共享联动 | 采纳,按代码顺序重写 | 3.3、US-A5 |
| 8 端口 8080 为隐式缺省 | 采纳,注明无 server.port 配置 | 1 节 |
| 9 附录 C/D/E 悬空引用 | 采纳,改为 5.3/5.4/5.5 | 2、3.4、3.7、US-A6、US-D1、6.1 |
| 10 US-A5 缺触发条件、US-F2 缺验收标准 | 采纳,补齐格式字段 | US-A5、US-F2 |
| 11 waitDateLeftOffset 未被使用 | 采纳,标注未使用 | 5.4 |
| 12 失败消息隐式重试至归档(约 1 天窗口) | 采纳,补充说明 | 5.7-2、3.6 |
| 二次全面复审:批量 Kafka 通知为 at-most-once | 采纳,标注现役限制 | 3.5、US-B2、5.7-17、6.2-5 |
| 二次全面复审:历史资格判定坏数据中断整批 | 采纳,收窄验收至 ES 写入阶段 | 3.7、US-D2、5.7-18 |
| R1 5.7-18/3.7 坏数据举例不精确(SODT 无保护、ACTT 缺失有保护、MVIN 空 NPE) | 采纳,按代码实况改述 | 3.7、5.7-18 |
| R2 "默认 1 秒"实为各环境配置值(代码无默认) | 采纳,改为"各环境均配置 1 秒、代码无默认值" | 3.2、US-A2、5.2 |
| R3 US-A5 触发条件括注移入流程约束;2 节补 ADFT 失败示例 | 采纳 | US-A5、2 节 |
本节与新系统实施计划(ACMA-3)同步维护;迁移实施 issue 仅引用本修订版用户故事作为一致性对拍基线。6.4 采纳记录含首轮(6.3)、第二轮与第三轮(R1–R3)评审意见的落点,正文与附录均以修订后描述为准。