运营日时区只认 PARAM:msgx.operation-day.zone:JobRunner、HistorySweepJob、JdbcSnapshotLogRepository.purgeBefore 改走注入的 OperationDayProps;ScheduleProcessor/AdftProcessor 删除「非法值回退字面量」;新增 @Context 启动自检 OperationDayZoneCheck,配置错位在启动时暴露。 验证:./gradlew test 126 tests / 0 fail(新增 OperationDayPropsTest 覆盖合法解析与非法即失败)。
msgexchange-v2(机场上游报文处理中间件)
系统角色:消费 CIIMS/AODB 等上游经共享 MySQL 信箱(CMINMSGS)投递的 XML 报文,
解析处理后维护自有 PostgreSQL 航班当前态并向 Kafka / 出站信箱投递——中间件,非报文源系统。
依据 ACMA-8 v4 综合架构(单写者严格 FIFO 管道)与 ACMA-6 技术选型
(Micronaut 5.1 + Kotlin 2.3)搭建;存储边界与阶段 B 按 ACM2-11/ACM2-12 定案
(自有 PostgreSQL 维护内部状态,共享 MySQL 仅作信箱边界)。
本仓库独立于 legacy msgexchange-api(Java 8 / Spring Boot 1.5 / Maven)——
过渡期两套系统并存(影子对拍→切流→旧仓库冻结),legacy 维护不受本仓库影响。
JDK 口径实测修正:Micronaut 5.1 系构件要求 JVM 25+(
./gradlew :dependencies实测 core 系解析 5.1.13,platform BOM 5.1.3,classpath 混用);计划原定 JDK 21 不可行; 工程已按 JDK 25 配置(ACMA-9 记录)。
系统边界
| 方向 | 机制 | 说明 |
|---|---|---|
| 入站(主路径) | JDBC 轮询共享 CMINMSGS |
上游经 CIIMS 等外部系统写信箱;本系统 1s 轮询 DATE_PROCESSED IS NULL 发现新信 → 自有 PG 入队(InboxPoller,U05) |
| 入站(compat) | HTTP POST /cminmsgs/send |
手工注入/影子对拍;写信箱 + PG 入队,非生产主拓扑 |
| 处理 | 主泵 FIFO 管道 | 解码 → identity → Handler 决策 → PostgreSQL 航班当前态 |
| 出站 | Kafka + COUTMSGS |
向下游推送 msg/schd;请求类报文写出站信箱 |
与 SIS / legacy 一致:本系统不替代 CIIMS 落信,不生成原始 AODB 业务报文。
包结构 → ACMA-8 架构映射
| 包 | 职责 | 对应 ACMA-8 |
|---|---|---|
ingress/ |
Ingress & Inbox:JDBC 轮询共享信箱发现新信 → 自有 PG 入队(+ HTTP compat 写路径),不解析报文 | 流程 1,I3 |
processing/ |
Processing 主泵:严格 FIFO 领取、identity 绑定、纯函数决策、事务2 | 流程 2/4,I1/I2/I5 |
delivery/ |
Delivery & Projection:每 target 严格 FIFO 投递、schd 聚合 | 流程 3 |
reference/ |
Reference & Query:21 类同步 + 15 类请求状态机 | 流程 6 |
jobs/ |
泵作业:ARCHIVE / HISTORY_SWEEP / PROJECTION_REBUILD | 流程 4/5/7,I4 |
codec/ |
SIS XML codec(注解 wire DTO → 领域载荷,禁 DTD/外部实体) | 决策 4 前置 |
domain/ |
领域模型:Decision、事件、状态机枚举、Phase 开关 | I1–I5 |
config/ |
PipelineProps 参数表(ACMA-8 参数初值,msgx.*) |
— |
infra/ |
仓储接口、stub、健康、重试策略 | 数据模型节 |
资源
docs/legacy/SIS_AODB_RMS-V0.1.md+docs/legacy/unisysaodbsis.xsd:消息结构唯一事实源 (wire 契约冻结,自 legacy 仓库复制以自包含;codec 实装依据,ACM2-2/ACM2-3)。db/migration/V1__flight_state_baseline.sql:自有 PostgreSQL(唯一自有库)的航班状态与处理管道表结构; 共享 MySQL(CMINMSGS/COUTMSGS)仅信箱 DML,不建表。lua/batch_delete.lua:3:30 清场批量删除(仅 ES 写成功集,I4)。application.yml:口令全部环境变量外置(零入库);datasources.default=自有 PG(enabled=false 待 U05)、mailbox.shared-mysql=共享信箱;msgx.phase权威口径(A 现役;B 缓做); pipeline 参数 = ACMA-8 参数表初值。
未完成(按计划属于后续阶段,不是本脚手架遗漏)
- Handler 业务(3+29):
processing/HandlerRegistry仅注册骨架,翻译属阶段 2/3。 - 依赖版本锁定:
gradle/libs.versions.toml中版本为计划口径,需阶段 0 「Micronaut×现网 Eureka 互操作冒烟 + logstash + ES REST」通过后固化。 - 自有 PG 数据层 + InboxPoller(ACM2-12):
infra/persistence/Repositories.kt目前是接口 (Micronaut Data JDBC on PG + 信箱适配层 CminmsgMailbox/OutboxMailbox + JDBC 轮询入队 属 U05 批次),主泵/调度循环以接口驱动,纯逻辑已抽离可单测。 - 信箱适配层与共享库边界(ACM2-12):上游外部写
CMINMSGS;本系统 JDBC 轮询读 + 处理回填写;出站写COUTMSGS(mailbox.shared-mysql 配置段已占位);入队/回填的 外部副作用/补偿模型属 U05 批次。
数据库初始化(ACM2-12 口径)
自有 PostgreSQL(唯一自有库):Flyway 执行 db/migration/V1__flight_state_baseline.sql,建立
航班当前态、明细表、处理终态与 outbox 等表(PG 方言)。全新库直接执行即可,无 legacy 前置。
共享 MySQL(cdairport,他人系统库):本系统不建表/schema,仅信箱 DML——上游外部写
CMINMSGS;本系统 JDBC 轮询读 + 处理回填;出站写 COUTMSGS(他人读取发送);表结构与
保留策略归库方管理。部署前需确认共享库 CMINMSGS 已存在(他人系统提供);本仓库不声明其 schema。
- 事务模型:与共享库交互均为外部副作用(ACM2-12)——主路径=上游外部写信箱 →
JDBC 轮询发现新信 → 自有 PG 建 PENDING 入队(失败重扫补建);HTTP
/cminmsgs/send为 compat 写路径;处理成功回填 DATE_PROCESSED/STATUS 为最终一致。 - 航班状态:写入自有 PostgreSQL;完整规则见
docs/flight-state.md。 - 影子对拍:自有 PG 开独立 schema;共享信箱为单信箱无法双写,影子输入=只读水位/回放口径。
本地开发依赖中间件栈(Podman / Docker Compose,ACM2-13)
仓库根目录提供兼容 Podman Compose 与 Docker Compose 的开发中间件栈 compose.yaml,包含:
- 共享信箱 MySQL(
mysql:8.4LTS,端口 3306,库cdairport):容器启动时自动执行deploy/dev/mysql-init/01-mailbox.sql创建本地联调所需的CMINMSGS、CMINMSGS_HST、COUTMSGS模拟表。 - 自有 PostgreSQL(
postgres:17-alpine,端口 5432,库msgx):容器提供干净数据库,应用启动时由 Flyway(db/migration/V1__flight_state_baseline.sql)自动建自有表。 - Valkey(
valkey/valkey:8-alpine,端口 6379):本地中间件兼容服务,不承担航班状态权威或日计划处理。 - Kafka(
apache/kafka:3.8.0KRaft 单节点,端口 9092):listenerPLAINTEXT://localhost:9092,default.replication.factor=1,已预配幂等生产者与 acks=all 所需的单节点参数。
快速启动
# 1. 复制环境变量示例(可按需修改口令与端口)
cp .env.example .env
# 2. 启动开发依赖栈(支持 docker compose 或 podman compose)
docker compose up -d
# 3. 检查各服务健康状态(均为 healthy)
docker compose ps
环境变量与应用映射
| 环境变量 | 说明 | 示例默认值 | 应用配置映射(application.yml) |
|---|---|---|---|
MSGX_MAILBOX_URL |
共享信箱 MySQL JDBC URL | jdbc:mysql://localhost:3306/cdairport?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4 |
mailbox.shared-mysql.url |
MSGX_MAILBOX_USER |
共享信箱 MySQL 用户名 | msgx_dev |
mailbox.shared-mysql.username |
MSGX_MAILBOX_PASSWORD |
共享信箱 MySQL 密码 | msgx_dev_pass |
mailbox.shared-mysql.password |
MSGX_PG_URL |
自有 PostgreSQL JDBC URL | jdbc:postgresql://localhost:5432/msgx |
datasources.default.url |
MSGX_PG_USER |
自有 PostgreSQL 用户名 | msgx_dev |
datasources.default.username |
MSGX_PG_PASSWORD |
自有 PostgreSQL 密码 | msgx_dev_pass |
datasources.default.password |
MSGX_REDIS_URI |
Redis/Valkey 连接串 | redis://localhost:6379 |
redis.uri |
MSGX_KAFKA_SERVERS |
Kafka bootstrap brokers | localhost:9092 |
kafka.bootstrap.servers |
MSGX_KAFKA_ACKS |
生产确认级别 | all |
kafka.producers.default.acks |
MSGX_KAFKA_IDEMPOTENCE |
幂等生产者开关 | true |
kafka.producers.default.enable-idempotence |
MSGX_KAFKA_MAX_IN_FLIGHT |
单连接最大在途请求数 | 1 |
kafka.producers.default.max-in-flight-requests-per-connection |
切流前 Kafka Broker 版本确认
生产契约以 user-stories.md US-07 / §7-6 为准:对接 Kafka 2.8+ / 3.x+,生产者强制 acks=all、enable.idempotence=true 与 max.in.flight.requests.per.connection=1,严禁非幂等降级。README 不提供生产降级环境变量组合。
旧系统 msgexchange-api 底层依赖 kafka-clients:0.10.1.1;现网 Broker 确切版本须在切流前实测确认:
- 版本探测:切流前通过本栈工具探测目标集群 API 能力:
docker exec msgx-dev-kafka /opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server <TARGET_IP>:9092 - 门禁判定:若输出中
InitProducerId(22)为 可用 → 保持现代高可靠默认(acks=all、idempotence=true、max.in.flight=1)。 - 阻塞切流:若
InitProducerId(22)为 UNSUPPORTED(Broker < 0.11)→ 阻塞切流,须升级 Broker 或经架构豁免(ACM2-1 基础设施升级门禁);降级参数仅可作为经批准的 runbook 附录,不得作为生产验收口径与 README 默认配置并存。
构建
./gradlew build # 需网络拉取依赖;内网环境见 gradle.properties 注释
./gradlew test # 纯逻辑单测(identity / schd 聚合 / 配置绑定 / 管道语义)
MICRONAUT_ENVIRONMENTS=dev ./gradlew run # dev stub 冒烟:内存 stub,无需 DB/Redis/Kafka/Eureka
进程级 dev 冒烟(2026-09-07 修复并实测通过):此前
./gradlew run因未声明application.mainClass报 "No main class specified"(已补,见 build.gradle.kts);随后实测又暴露 dev profile 三处装配问题并已修复(application-dev.yml):stub 模式未排除 DataSource (datasources.default.enabled=false);micronaut 自带 Redis/Kafka 健康指示器在无 broker 时把 /health 拖成 500(dev 关闭 redis.health/kafka.health,健康由自定义指示器承担);管理端点前缀误用micronaut.endpoints.*(正确为顶层endpoints.*,/env、/beans 这才真正放开);另关闭 eureka discovery。当前MICRONAUT_ENVIRONMENTS=dev ./gradlew run无需任何环境变量/外部依赖即可启动, compat HTTPPOST /cminmsgs/send(application/json)返回记录 ID,/healthUP。 已知边界:text/plain compat 写 415 属 U16 缺口(契约对拍后固化);application-dev.yml数据源配置 已对齐为自有 PG 与共享 MySQL 信箱(ACM2-12/13);测试侧端到端语义由 PipelineSmokeTest (内嵌上下文,compat 写路径)覆盖。生产主路径(JDBC 轮询InboxPoller)属 U05,dev stub 尚未覆盖。
注解处理:Kotlin 侧经 KSP(
kotlin-ksp+micronaut-inject-kotlin)生成 Micronaut BeanDefinition(U01);若 build 产物缺少*$Definition类,先检查 KSP 是否生效。 dev/shadow 冒烟装配:msgx.stubs=true(内存仓储/适配层,见 infra/stub)+msgx.pipeline.autostart=true(PipelineLifecycle 拉起专用线程,U07);生产默认两者关闭。
文档
从 设计文档入口 开始阅读;航班状态设计见 docs/flight-state.md。
- docs/architecture.md:架构速览——中间件定位、总体拓扑(JDBC 轮询主路径)、 上下游边界、模块职责、关键决策、数据边界、部署与安全姿态、可观测性、就绪度。
- docs/design.md:设计细节——系统边界、状态机与错误分类、数据模型、 核心流程语义(流程 1 主路径/compat 分述)、失败/重试/重放、不变量落点、参数表、已知缺口。
- docs/user-stories.md:阶段 A US-01~US-14、延后清场 US-15、上线 Epic、 legacy HTTP 去留及逐项文档 TODO;包含验收标准、依赖、实现差距与待确认问题。 (原 ACM2-15~22 文档整改清单 user-stories-todo.md 已收敛完成并移除,跟踪记录见 Plane。)
- docs/flight-state.md:航班状态的唯一现行设计规范;历史方案不作为实现依据。
- docs/legacy/:外部参考/基线材料(自 legacy 仓库拷贝,非本系统文档)——
msgexchange-api-legacy-user-stories.md(legacy 行为对拍基线,ACMA-4)、unisysaodbsis.xsd、SIS_AODB_RMS-V0.1.md(消息结构唯一事实源;CIIMS 中间件交换模型)。
关联
Plane airport_chengdu_msgexchange_v2(ACM2,现行入口):ACM2-3(综合架构 v4,
架构权威,存储边界按 ACM2-12 修订;ACM2-11 为决策史)、ACM2-4(脚手架跟踪)、ACM2-10(评审与实施计划
U01–U30)、ACM2-12(存储边界与共享信箱决策);ACMA 系列仅作归档历史/迁移来源
(ACMA-8 v4 / ACMA-6 选型 / ACMA-9 JDK 口径在归档中可溯)。