docs+fix: 文档回到代码事实;修复 dev 冒烟使其真正可跑(ACM2-10 复核意见落地)
实测过程发现并修复四层问题,随后全部按实测结果校订文档:
- build.gradle.kts:补 application.mainClass(Kotlin 顶层 main → ApplicationKt)。
此前 ./gradlew run 报 "No main class specified",installDist 启动脚本主类为空(Dockerfile 不可运行)。
- application-dev.yml:stub 模式真正免基础设施——
* datasources.default.enabled=false(micronaut-jdbc JdbcDataSourceEnabled 条件排除 DataSource,
原注释"stub 不建连"意图未实现,DataSource eager 强制连 MySQL 导致启动失败);
* 关闭 micronaut 自带 Redis/Kafka 健康指示器(无 broker 时装配即把 /health 打成 500);
* 端点配置前缀修正:micronaut.endpoints.* 是死配置(/env 404、/beans 401 实测),
正确为顶层 endpoints.*(env 默认禁用 / beans 默认 enabled+sensitive 一并注明);
* 关闭 eureka discovery,避免无注册中心时 /health 变 DOWN。
验证:MICRONAUT_ENVIRONMENTS=dev ./gradlew run 无任何 env/外部依赖启动 → POST /cminmsgs/send
200 返回 ID → /health UP;text/plain 415(U16 缺口实证)。37 测试全绿未受影响。
- README/docs/architecture.md/docs/design.md:按实测与复核意见校订——
* README:dev 冒烟命令恢复为"无需 DB/Redis/Kafka",记录修复缘由;
* architecture:U17 服务名未落地(注册名仍取 micronaut.application.name=msgexchange-nextgen,
msgx.service-name 无消费方)、三层隔离为目标态(仅 register-eureka=false 生效)、
管理端点 5.1 实际口径(/env 默认禁用、/beans 默认 enabled+sensitive)、就绪度口径更新;
* design:§9 补 U17 行;smoke 路径 UNSUPPORTED→CODEC_ERROR 修正;毒丸 head-deadline 判据不可达、
快照 CAS 崩溃重放二次自增版本等"代码行为≠文档声明"处加实现注;I1 状态行同步。
- gradle/libs.versions.toml + gradle.properties:版本矩阵注释按依赖解析实测修正
(platform 5.1.3 生效但 core 系解析 5.1.13,classpath 混用;原"5.1.10 无平台 BOM"说法与观测不符)。
未提交内容:无。代码级缺陷(毒丸判据、CAS 重放、DNLD 路由顺序、identity 键释放等)仍属
ACM2-10 排期,本次仅按文档职责如实标注,未改动实现。
This commit is contained in:
@@ -61,9 +61,20 @@ REQ_TRACK / PUMP_JOB / FLIGHT_STATE),并假定 `CMINMSGS`(及其历史表
|
||||
```bash
|
||||
./gradlew build # 需网络拉取依赖;内网环境见 gradle.properties 注释
|
||||
./gradlew test # 纯逻辑单测(identity / schd 聚合 / 配置绑定 / 管道语义)
|
||||
MICRONAUT_ENVIRONMENTS=dev ./gradlew run # dev stub 冒烟:内存仓储 + 启动主泵/投递(无需 DB/Redis/Kafka)
|
||||
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` **无需任何环境变量/外部依赖**即可启动,
|
||||
> `POST /cminmsgs/send`(application/json)返回记录 ID,`/health` UP。
|
||||
> 已知边界:text/plain 收报 415 属 U16 缺口(契约对拍后固化);测试侧端到端语义由
|
||||
> PipelineSmokeTest(内嵌上下文)覆盖。
|
||||
|
||||
> 注解处理:Kotlin 侧经 KSP(`kotlin-ksp` + `micronaut-inject-kotlin`)生成 Micronaut
|
||||
> BeanDefinition(U01);若 build 产物缺少 `*$Definition` 类,先检查 KSP 是否生效。
|
||||
> dev/shadow 冒烟装配:`msgx.stubs=true`(内存仓储/适配层,见 infra/stub)+
|
||||
|
||||
@@ -56,6 +56,12 @@ dependencies {
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
}
|
||||
|
||||
application {
|
||||
// Kotlin 顶层 main(Application.kt)→ JVM 主类为 ApplicationKt;
|
||||
// 不声明则 ./gradlew run 报 "No main class specified"、installDist 产出的启动脚本主类为空。
|
||||
mainClass.set("com.gzzn.omms.msgexchange.nextgen.ApplicationKt")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
+10
-5
@@ -28,7 +28,7 @@
|
||||
| 权威存储 | Redis(阶段 A) | flightInfo hash;仅主泵线程写(I5);Lua 脚本原子覆盖 |
|
||||
| 投递 | Kafka(acks=all + 幂等) | outbox 模式,经 MSG_EVENT 表中转 |
|
||||
| 投影(阶段 B) | Elasticsearch + Redis 投影 + FLIGHT_STATE | 仅阶段 B 启用(`msgx.phase`) |
|
||||
| 注册中心 | Eureka(Micronaut 原生键) | 服务名契约 `msgexchangeapi`(影子 `msgexchangeapi-shadow`) |
|
||||
| 注册中心 | Eureka(Micronaut 原生键) | 服务名契约 `msgexchangeapi`(影子 `msgexchangeapi-shadow`)——**U17 未落地**:当前注册名仍取 `micronaut.application.name`(=msgexchange-nextgen),`msgx.service-name` 无运行时消费方(见 §8 与 design.md §9) |
|
||||
| 可观测 | logstash TCP(Async 包装)+ MDC traceId + 自定义健康指示器 | 见 [design.md §8](design.md) |
|
||||
|
||||
## 3. 总体拓扑
|
||||
@@ -117,7 +117,8 @@
|
||||
| A(`msgx.phase=A`) | Redis flightInfo | KAFKA:msg、KAFKA:schd | 管道骨架+重试闭环已实装;Redis Lua/实仓储属 U05/U09 |
|
||||
| B(`msgx.phase=B`) | FLIGHT_STATE + 投影 | + ES:flight_hts、REDIS:flightInfo | 未实施(阶段 2 后) |
|
||||
|
||||
**就绪度(诚实口径,2026-09-06)**:可编译、可测试(37 测试全绿)、dev stub 冒烟可端到端;
|
||||
**就绪度(2026-09-07 复核口径)**:可编译、37 测试全绿、**dev stub 进程级冒烟实测可端到端**
|
||||
(`./gradlew run` 无外部依赖启动 → 收报 200 → `/health` UP,修复记录见 README「进程级 dev 冒烟」);
|
||||
生产默认配置**不可对外服务**——`autostart=false` 且生产(stubs=false)下仓储无实装、DI 装配
|
||||
即失败。生产就绪前置:U05(数据层+事务)、U07 fail-fast 定案、U09(快照恢复协议)、
|
||||
U13(投递毒丸补全)、U15(统一序号)。逐项状态见 ACM2-10「定稿实施计划」。
|
||||
@@ -125,11 +126,15 @@ U13(投递毒丸补全)、U15(统一序号)。逐项状态见 ACM2-10「
|
||||
## 8. 部署与安全姿态
|
||||
|
||||
- **实例数 = 1**(主泵单写者前提);双实例误配当前无运行期防护(U26:租约/DB 锁 + 拒启,未实装)。
|
||||
- **影子隔离**:服务名(`msgexchangeapi-shadow`)+ 独立 schema + Redis key 前缀 + 独立 topic
|
||||
三层隔离;`msgx.register-eureka=false` 可脱离注册中心对拍。
|
||||
- **影子隔离(目标态;U17/U26 未落地,勿按现状引用)**:服务名(`msgexchangeapi-shadow`)+ 独立
|
||||
schema + Redis key 前缀 + 独立 topic 三层隔离;当前代码仅 `msgx.register-eureka=false` 生效——
|
||||
Kafka topic 写死字面量 `"msg"`/`"schd"`(Dispatcher)、`FlightRedisClient.eval` 无 key 前缀参数、
|
||||
服务名未接 `msgx.service-name`(§2)。
|
||||
- **网络信任模型**:`/cminmsgs/send` 无鉴权(沿用现役内网信任姿态);eureka default-zone
|
||||
回退 `127.0.0.1:8761`;口令/端点全部环境变量外置(零入库)。安全节细化属 U28。
|
||||
- **管理端点**:`/env`、`/beans` 默认 sensitive,仅 dev/影子环境放开(`application-dev.yml`)。
|
||||
- **管理端点**:Micronaut 5.1 下 `/env` 默认**禁用**、`/beans` 默认 enabled+sensitive;dev/影子经
|
||||
顶层 `endpoints.*`(**非** `micronaut.endpoints.*`——实测前缀错误时不生效)放开 `/env`、`/beans`
|
||||
与 health 明细。工程未引入 micronaut-security,sensitive 的实际拦截行为待 U28 定案。
|
||||
- **同名单风险**:影子与生产同名同路径会互相收报——切流前必须核对服务名三隔离。
|
||||
|
||||
## 9. 可观测性
|
||||
|
||||
+18
-9
@@ -82,7 +82,10 @@ ErrorClass(两侧共用):
|
||||
与「统一 FIFO」注释存在已知偏离(U15 未实装)**;统一序号列定案后消除。
|
||||
3. 队头 FAILED 且退避未到期:`poisoned()` 判定(attempts≥maxAttempts 或滞留超
|
||||
head-deadline 10m)→ DEAD(EXHAUSTED) 毒丸升级(Pump 侧;投递侧同语义属 U13,未实装);
|
||||
否则 sleep 至 nextAttemptAt。
|
||||
**实现注**:headDeadline 判据以 `updatedAt` 为锚,而 updatedAt 与 nextAttemptAt 同一次 FAILED
|
||||
写入、退避 ≤60s(封顶)→ 有 nextAttemptAt>now 必有 now−updatedAt≤60s<10m,**滞留超时分支实际
|
||||
不可达**,仅 attempts 维度生效;锚点语义随 U05/U13 定案并补测试(Pump 未注入 Clock,主泵级
|
||||
门禁无单测锁定,见 §7);否则 sleep 至 nextAttemptAt。
|
||||
4. 正常队头 → `MessageProcessor.processOne`:
|
||||
入口守卫(FAILED 且已 exhausted → DEAD)→ `rawOf` 缺失 → DEAD(MALFORMED) →
|
||||
decode(MALFORMED→DEAD / CODEC_ERROR→FAILED)→ identity 首绑
|
||||
@@ -121,7 +124,9 @@ CAS 冲突 → FAILED(INFRA)+退避(串行泵下不应发生→告警语义)
|
||||
|
||||
**已知缺口(U09,未定案)**:Lua 与 CAS 分属两存储,非同一事务;崩溃窗口
|
||||
(Lua 后/CAS 前、CAS 后/SUCCEEDED 前)与幂等重放判据(版本不二次自增)的显式恢复协议
|
||||
待定案并补测试。
|
||||
待定案并补测试。**实现注**:代码亦未实现「重放 no-op」——CAS 已提交而 SUCCEEDED 未写时,
|
||||
重放会重新 `getGen` 到已达标版本并再次 CAS 成功(版本 1→2 二次自增),与「版本不二次自增」
|
||||
注释不符(今日因 staging 未实装不可达,阶段 2 接 staging 后即成真缺陷)。
|
||||
|
||||
### 3.5 泵作业(`JobExecutor`,经 PUMP_JOB 同队列)
|
||||
|
||||
@@ -147,7 +152,7 @@ CAS 冲突 → FAILED(INFRA)+退避(串行泵下不应发生→告警语义)
|
||||
|
||||
| 不变量 | 语义 | 落点 | 状态 |
|
||||
|---|---|---|---|
|
||||
| I1 | 单写者严格 FIFO + HOL 阻塞 + 毒丸升级 | `headUnfinished`/`headUnsent` 队头语义、`poisoned()` | 实装(job 相对队头的全序属 U15) |
|
||||
| I1 | 单写者严格 FIFO + HOL 阻塞 + 毒丸升级 | `headUnfinished`/`headUnsent` 队头语义、`poisoned()` | 实装(attempts 毒丸生效;head-deadline 判据不可达待修,见 §3.2 注;job 相对队头的全序属 U15) |
|
||||
| I2 | Redis 先写、后于事件创建(happens-before) | `processOne` 阶段 A 分支 | TODO redisApply(流程占位已留) |
|
||||
| I3 | identity 首绑幂等;接收层无唯一约束;SUCCEEDED 回填 | `Identity`/`tryBindIdentity`/`backfillOnSuccess` | 实装 |
|
||||
| I4 | 清场仅删 ES 成功集;按代差删 | `HistorySweepJob`/SNAPSHOT_REPLACE delFields | 门禁实装,ES 接线 TODO |
|
||||
@@ -175,9 +180,12 @@ logback 独立于本文件,环境变量前缀 `MSGX_LOGSTASH_*`。
|
||||
## 7. 测试策略
|
||||
|
||||
- **接口驱动 + 假仓储**:管道语义全部离线单测(无 DB/Redis/Kafka),时间用 `MutableClock`。
|
||||
- **不变量测试**:FIFO/HOL、schd 批退避与 DLQ、毒丸升级、重试上限、重放白名单、
|
||||
聚合最新态、配置绑定、DI 装配冒烟(PipelineSmokeTest 端到端:收报→FAILED(UNSUPPORTED)
|
||||
→重放→schd 聚合发出)。
|
||||
- **不变量测试**:FIFO/HOL、schd 批退避与 DLQ、重试上限、重放白名单、
|
||||
聚合最新态、配置绑定、DI 装配冒烟(PipelineSmokeTest:收报→FAILED(CODEC_ERROR,stub codec
|
||||
恒 CODEC_ERROR)→重放→schd 聚合发出)。
|
||||
**范围注**:以上覆盖的是边界级(processOne/flushSchd/仓储)语义;主泵 tick 级 HOL/毒丸/退避
|
||||
门禁因 Pump 未注入 Clock 而无单测锁定(§3.2 注),DispatcherTickTest 仅锁批退避与「队首未到期
|
||||
不推进」。
|
||||
- 现状 37 测试全绿(`./gradlew test`;wrapper 钉 9.6.1 + JDK 25;内网构建设
|
||||
`GRADLE_USER_HOME`/`TMPDIR` 指向可写目录)。
|
||||
- **U29 门禁(规划)**:不变量清单化入 CI 红即阻塞;「新增逻辑必伴生不变量测试」入贡献约定。
|
||||
@@ -197,10 +205,11 @@ logback 独立于本文件,环境变量前缀 `MSGX_LOGSTASH_*`。
|
||||
|---|---|---|
|
||||
| U05 | 仓储接口无实装;`@Transactional`/allopen 未引入 → 生产 DI 装配失败、事务1/2 未原子 | 阶段 1(Micronaut Data JDBC + allopen + 事务生效回归) |
|
||||
| U07/U26 | `autostart` 默认关=有意门禁,但生产无 fail-fast;双实例无运行期防护 | fail-fast 定案 + 租约/DB 锁拒启 |
|
||||
| U09 | 快照跨存储恢复协议未定案 | 崩溃窗口清单 + 幂等判据 + 测试 |
|
||||
| U13 | 投递侧无 createdAt/headDeadline 超时升级、无 DEAD 告警出口 | WP2 |
|
||||
| U09 | 快照跨存储恢复协议未定案(含崩溃重放二次自增版本未实现 no-op,见 §3.4 注) | 崩溃窗口清单 + 幂等判据 + 测试 |
|
||||
| U13 | 投递侧无 createdAt/headDeadline 超时升级、无 DEAD 告警出口;处理侧 head-deadline 判据亦不可达(见 §3.2 注) | WP2 |
|
||||
| U15 | job 与队头消息无统一全序(入队时间近似) | 统一序号列定案 |
|
||||
| U16 | `/cminmsgs/send` 无 @Consumes/字符集、无错误路径契约(@ControllerAdvice) | WP2(legacy 逐字对拍固化) |
|
||||
| U16 | `/cminmsgs/send` 无 @Consumes/字符集(实测 text/plain 415)、无错误路径契约(@ControllerAdvice) | WP2(legacy 逐字对拍固化) |
|
||||
| U17 | Eureka 注册名仍取 `micronaut.application.name`(=msgexchange-nextgen);`msgx.service-name` 无运行时消费方 → 影子/切流前注册名与文档契约脱节 | `micronaut.application.name=${msgx.service-name}`(application.yml:9/30) |
|
||||
| U18 | §2 所列 DDL 缺口 | WP2 |
|
||||
| U19–U21 | 请求状态机量纲/死分支、identity 绑定静默跳过、REF_DATA 接口/DDL 对齐 | WP2 |
|
||||
| U22–U24 | 载荷类型收敛、eventSeq 未接线、每报文全量读语义定案 | WP3 |
|
||||
|
||||
@@ -3,4 +3,6 @@ kotlin.code.style=official
|
||||
# 阶段 0 冒烟锁定(U02/T13):io.micronaut.application 插件经 micronaut.version 选择注入的
|
||||
# micronaut-platform BOM 版本——平台 BOM 仅发布至 5.1.3(5.1.10 无对应平台 BOM,KSP 会解析失败)。
|
||||
# 内网环境可将依赖镜像指向内网 Maven 仓库(沿用现网环境前提)
|
||||
# 【2026-09-07 实测修正】platform BOM 5.1.3 生效,但 core 系实际解析到 5.1.13(更高约束覆盖),
|
||||
# classpath 为 5.1.3/5.1.13 混用;「5.1.10 无平台 BOM 会解析失败」与观测不符(见 libs.versions.toml)。
|
||||
micronaut.version=5.1.3
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
# - Micronaut:版本由 gradle.properties `micronaut.version` 统一驱动(=5.1.3,micronaut-platform BOM
|
||||
# 最高发布版本;5.1.10 仅有 core 补丁无对应平台 BOM,KSP 处理器类路径会解析失败)。
|
||||
# 下方 micronaut = "5.1.3" 作为 ksp 处理器(micronaut-inject-kotlin)的版本来源,勿再回落 5.1.0 / JDK21 旧口径。
|
||||
# 【2026-09-07 实测修正】`./gradlew :dependencies` 显示 micronaut-platform:5.1.3 BOM 确实生效,但
|
||||
# io.micronaut core 系(http-server-netty/management/runtime/inject/aop)实际被更高约束解析到 **5.1.13**,
|
||||
# data 系停留在 5.1.3 → classpath 为 5.1.3 与 5.1.13 混用;KSP 处理器(micronaut-inject-kotlin)为 5.1.3,
|
||||
# 与运行期 core 5.1.13 并存。上文「版本由 micronaut.version 单点驱动=5.1.3」「5.1.10 无平台 BOM 会解析
|
||||
# 失败」与观测不符,勿再引用;版本矩阵需按解析结果重新钉真(建议随 U05 数据层批次处理)。
|
||||
# - Kotlin 2.3.0 + KSP 2.3.0(Kotlin 2.3 配套版;注解处理必需,否则 Micronaut 不生成 BeanDefinition)。
|
||||
[versions]
|
||||
kotlin = "2.3.0"
|
||||
|
||||
@@ -9,26 +9,58 @@ msgx:
|
||||
pipeline:
|
||||
autostart: true
|
||||
|
||||
# DB 仅在真实实装接入后需要;stub 模式不建连(Flyway 关闭;datasource 保留默认值以免占位符解析失败)
|
||||
# dev 不注册也不发现(stub):关 eureka discovery,避免 discovery 客户端健康指示器
|
||||
# 因无 eureka 服务而把 /health 拉成 DOWN
|
||||
eureka:
|
||||
client:
|
||||
discovery:
|
||||
enabled: false
|
||||
|
||||
# DB 仅在真实实装接入后需要;stub 模式不建连——datasources.default.enabled=false 经
|
||||
# micronaut-jdbc 的 JdbcDataSourceEnabled 条件排除 DataSource bean("stub 不建连"落地);
|
||||
# 下方 url 仍保留合法默认(host/port/name 分拆,避免占位符默认值内嵌冒号解析坏)。
|
||||
# 真实接入时:设 MSGX_DB_URL(或 MSGX_DB_HOST/PORT/NAME)+ enabled=true。
|
||||
datasources:
|
||||
default:
|
||||
enabled: false
|
||||
url: jdbc:mysql://${MSGX_DB_HOST:127.0.0.1}:${MSGX_DB_PORT:3306}/${MSGX_DB_NAME:msgexchange_dev}
|
||||
username: ${MSGX_DB_USER:root}
|
||||
password: ${MSGX_DB_PASSWORD:}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
# stub 模式无 DataSource:Flyway 显式关闭(主配置默认 enabled:true 只作用于有 DataSource 的环境)
|
||||
flyway:
|
||||
datasources:
|
||||
default:
|
||||
enabled: false
|
||||
|
||||
datasources:
|
||||
default:
|
||||
url: ${MSGX_DB_URL:jdbc:mysql://127.0.0.1:3306/msgexchange_dev}
|
||||
username: ${MSGX_DB_USER:root}
|
||||
password: ${MSGX_DB_PASSWORD:}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# stub 模式无真实 Redis/Kafka:关掉 micronaut 自带的 RedisHealthIndicator / KafkaHealthIndicator
|
||||
# (二者装配需构造真实 client/config;实测未设 MSGX_REDIS_URI / MSGX_KAFKA_SERVERS 时 /health 整体报 500)。
|
||||
# 健康由自定义 redis-flight-store / kafka-delivery indicator 承担(stub ping=true → UP)。
|
||||
redis:
|
||||
health:
|
||||
enabled: false
|
||||
|
||||
micronaut:
|
||||
endpoints:
|
||||
kafka:
|
||||
health:
|
||||
enabled: false
|
||||
# stub 无真实 Kafka:仍给 servers 合法默认(Micronaut 占位符默认值内嵌冒号会解析坏,
|
||||
# 故拆 host/port 两个无冒号默认;字面冒号留在占位符之间)。真实接入时设 MSGX_KAFKA_HOST/PORT。
|
||||
bootstrap:
|
||||
servers: ${MSGX_KAFKA_HOST:127.0.0.1}:${MSGX_KAFKA_PORT:9092}
|
||||
|
||||
# 管理端点(U03/N32):/env 默认禁用、/beans 默认 enabled+sensitive——dev 显式放开。
|
||||
# 注意:端点配置前缀是顶层 `endpoints`(micronaut.endpoints.* 是死配置,实测不生效)。
|
||||
endpoints:
|
||||
env:
|
||||
enabled: true
|
||||
sensitive: false
|
||||
beans:
|
||||
enabled: true
|
||||
sensitive: false
|
||||
health:
|
||||
details-visible: ANONYMOUS # dev 排障:无鉴权也显示各 indicator 明细(生产不加载本文件)
|
||||
|
||||
micronaut:
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
Reference in New Issue
Block a user