Behavior-comparison baseline referenced by ACM2-3 (G13 golden matrix). Copied from legacy repo working copy (was untracked there).
545 lines
47 KiB
Markdown
545 lines
47 KiB
Markdown
# msgexchange-api 旧项目业务逻辑与用户故事文档
|
||
|
||
## 0. 文档说明
|
||
|
||
- 本文档记录 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` 与 `doc/unisysaodbsis.xsd`。
|
||
- 文档中"关联代码"均为相对仓库根目录的源码路径,可据此逐条复核。
|
||
|
||
## 1. 系统定位与集成边界
|
||
|
||
msgexchange-api 为成都机场 OMMS 消息交换服务(`com.gzzn.omms:msgexchange-api`,1.2.1-SNAPSHOT;Spring 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-api(ACAA) | 基础数据 | 机位列表、机位→登机桥编码两个接口(`basicdata` 模块) |
|
||
| Logstash(logback) | 日志采集 | 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 hash:field=`FLID`,value=`SCHD.FLTR`(带类型信息的 JSON 序列化) |
|
||
| `orms_stand` | Redis hash:field=机位编码,value=`OrmsStand` 对象 |
|
||
| `orms_stand_airbridge` | Redis hash:field=机位编码,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;新快照中未出现的旧 field 不会被删除,即 `flightInfo` 为合并写入语义,**并非整体替换**。快照差集清理须由新系统显式实现(见 6 节迁移验收边界)。
|
||
|
||
刷新成功:调用 `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 EXISTS(cminmsgs_hst 中已存在相同 ID)AND 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` 合并写入:报文内航班被新增/覆盖;快照中不存在而 Redis 已存在的旧航班不被清除(合并语义,非整体替换);
|
||
- 主航班 `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.name(dev = `opensearch`)/ nodes。
|
||
- `eureka.client.service-url.defaultZone`;`eureka.instance.hostName` / `prefer-ip-address`。
|
||
- `adminApi`:schema / baseUrl(dev、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` | hash:FLID → FLTR | 动态航班实时库 | 日计划合并写入(`hmset` = putAll,不清理差集)/ 动态增量 / 转历史后删除 |
|
||
| `orms_stand` | hash:standCode → OrmsStand | 机位基础数据 | 未命中时全量拉取并写入,过期 3600 秒 |
|
||
| `orms_stand_airbridge` | hash:standCode → 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 XML(JAXB 注解 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`)合并写入:新日计划快照中未出现的旧航班不会被清除(见 3.3)。如采用整体替换语义,新系统须显式清理差集并以回放测试固定预期。
|
||
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. **快照替换语义**:若日航班计划采用整体替换,须显式清理差集,并以回放测试固定「输入样本 → Redis 终态」预期(现役为合并语义,见 3.3)。
|
||
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)评审意见的落点,正文与附录均以修订后描述为准。
|