feat: 二阶段

This commit is contained in:
2026-07-15 17:38:02 +08:00
parent d884aa36ff
commit bb406cd354
28 changed files with 1076 additions and 79 deletions

View File

@@ -144,7 +144,7 @@ com/codechecker/serialization-schema-checker/1.0.0/
| 大量 Redis 误报 | 锁/计数器未过滤 | 补充 ignore.key_patterns |
| 大量 MQ 误报 | 压测/临时 topic | 补充 ignore.mq_destinations |
| commit 数显示为 1实际多个 | 浅克隆下 `rev-list` 看不到中间提交 | 已修复:优先事件 `commits` 长度;并 deepen 到 before 为祖先 |
| 漏报(模式/模块) | W0x/MQ 未开 / include_modules 过窄 | 确认 W01~W05 与 mq_patterns;检查模块过滤 |
| 漏报(模式/模块) | W0x/MQ 未开 / include_modules 过窄 | 确认 W01~W05 与 mq_patterns(含 MQ03~05、MQ-K03/K04检查 `mq_read_hints_enabled`模块过滤 |
| 类型展开不完整 | 类型在依赖 jar 中 | 补充 `manual_mappings.value_type` |
---

View File

@@ -1,8 +1,8 @@
# MQ 消息体序列化结构变更检测 — 方案
> 版本v0.3
> 版本v0.4
> 日期2026-07-15
> 状态:**Phase M1 已实现**MQ01/MQ02 + MQ-K01/MQ-K02M2/M3 未启动
> 状态:**Phase M1 + M2 已实现**生产侧 MQ01~05 / MQ-K01~K04 + 读侧 MQ-R
> 关联:复用 `serialization-schema-checker` 的 Schema 提取、Diff、企微通知与 CI 框架
> 业务样本仓:`jnpf-java-cloud`**RocketMQ + Kafka**
@@ -37,7 +37,7 @@
在 push 时静态分析 **消息体类型的序列化 Schema** 是否相对对比区间发生变更,覆盖 **RocketMQ + Kafka**,并复用现有企微通知 / notify|block 能力。
### 1.3 非目标(本方案首版)
### 1.3 非目标
- 不连接真实 Broker不拉取积压消息做运行时校验
- 不解析依赖 jar 内消息类型(仅本仓 `src/main/java`
@@ -56,8 +56,9 @@
| `rocketMQTemplate.syncSend(dest, dto)` | 高(如钱包扣费) | **纳入** |
| `asyncSend` / `syncSendOrderly` 等 | 中 | **纳入** |
| `convertAndSend` | 视封装而定 | **纳入** |
| `JSON.toJSONString` 再发 String | 较低 | unwrap 后取类型 |
| 只发 `String` / `byte[]` / `MessageExt` | | **默认忽略** |
| `MessageBuilder.withPayload` 再 send | 中(如 IM 延时消息) | **纳入**MQ04 |
| `JSON.toJSONString` 再发 String | 较低 | unwrap 后取类型MQ05 |
| 只发 `String` / `byte[]` / 无泛型 `Message` | 有 | **默认忽略** |
样本(资金钱包):
@@ -74,6 +75,8 @@ public class WalletDeductConsumer implements RocketMQListener<WalletDeductReq> {
|------|----------|------|
| `kafkaTemplate.send(topic, dto)` | 中(值班食安项等) | **纳入** |
| `kafkaTemplate.send(topic, List<Xxx>)` | 中(巡店食安项列表) | **纳入**rootArray |
| `kafkaTemplate.send(ProducerRecord)` | 中 | **纳入**MQ-K03 |
| 先 JSON 再 `send(topic, json)` | 较低 | unwrapMQ-K04 |
| `@KafkaListener` + `String` + `JSONObject.parseObject(..., Xxx.class)` | 有(数据分析中差评) | **读侧补强 MQ-R** |
| `KafkaTopicUtil` 仅创建 Topic | 有(租户) | **忽略**(无消息体) |
@@ -118,6 +121,8 @@ public void handleMessage(String message) {
| RocketMQ | `RocketMQListener<T>``onMessage(T)` + `@RocketMQMessageListener` |
| Kafka | `@KafkaListener` 方法参数类型;或方法内 `parseObject/parseArray(..., Xxx.class)`(与现有 W06 共享解析能力) |
开关:`detection.mq_read_hints_enabled`(默认 **true**)。
---
## 3. 方案总览
@@ -135,8 +140,8 @@ public void handleMessage(String message) {
```text
Git Diff → 变更 Java 文件
├─ RedisW01~W05 + W06 ← 已有
└─ MQRocketMQMQ01~+ KafkaMQ-K*
+ Listener / parse 补强MQ-R本方案
└─ MQRocketMQMQ01~05+ KafkaMQ-K01~K04
+ Listener / parse 补强MQ-R已实现
同一套 TypeSchema / SchemaDiffer / Skeleton / WeCom
```
@@ -161,7 +166,7 @@ Git Diff → 变更 Java 文件
| MQ01 | `rocketMQTemplate.syncSend(dest, payload, …)` | dest、payload 类型 |
| MQ02 | `asyncSend` / `syncSendOrderly` / `sendOneWay` 等 | 同上 |
| MQ03 | `convertAndSend(dest, payload)` | 同上 |
| MQ04 | `MessageBuilder.withPayload(obj)` 再 send | payload 类型 |
| MQ04 | `MessageBuilder.withPayload(obj)` 再 send,或 `Message<T>` | payload / `T` |
| MQ05 | 先 `toJSONString`/`getObjectToString` 再 send String | unwrap 后类型 |
### 4.2 生产侧 — Kafka
@@ -170,7 +175,7 @@ Git Diff → 变更 Java 文件
|---------|------------|------|
| MQ-K01 | `kafkaTemplate.send(topic, payload)` | topic、payload 类型 |
| MQ-K02 | `kafkaTemplate.send(topic, key, payload)` | 同上(忽略分区 key |
| MQ-K03 | `send(ProducerRecord)` / `ListenableFuture` 封装若可解析 | topic + value 类型 |
| MQ-K03 | `send(ProducerRecord)` | topic + value 类型 |
| MQ-K04 | 先 JSON 序列化为 String 再 `send(topic, json)` | unwrap 后类型 |
Payload 为 `List<Xxx>` / `Collection` 时标记 **rootArray**,骨架为 JSON 数组(与 Redis List 一致)。
@@ -187,7 +192,7 @@ Payload 为 `List<Xxx>` / `Collection` 时标记 **rootArray**,骨架为 JSON
|---------|------|------|
| MQ-R01 | `RocketMQListener<T>` / `@RocketMQMessageListener` | 补强同 destination 生产点 |
| MQ-R02 | `@KafkaListener` + 参数类型 `T`(非 String | 补强同 topic |
| MQ-R03 | Listener 内 `parseObject`/`parseArray(..., Xxx.class)` | 补强(复用 W06 检测器 |
| MQ-R03 | Listener 内 `parseObject`/`parseArray(..., Xxx.class)` | 补强(复用 parse AST |
开关:`detection.mq_read_hints_enabled`(默认 true
@@ -220,7 +225,6 @@ Payload 为 `List<Xxx>` / `Collection` 时标记 **rootArray**,骨架为 JSON
- 删除字段橙 `warning`;新增绿 `info`
- destination 未解析时灰色提示
- 可选后缀:「请评估消费积压与兼容反序列化」
「通道」字段用于区分中间件;若模板求简,可省略通道仅靠 Topic 形态区分。
@@ -272,37 +276,36 @@ manual_mappings:
## 7. 分阶段交付
### Phase M1 — MVPRocketMQ + Kafka 基础投递)
### Phase M1 — MVPRocketMQ + Kafka 基础投递)
| 任务 | 说明 |
|------|------|
| MQ01/MQ02 | RocketMQ `syncSend` / `asyncSend` |
| MQ-K01/MQ-K02 | Kafka `send(topic, payload)` / 三参 send |
| destination 推断 | 字面量、常量、拼接Kafka 动态 topic → `*` |
| Schema Diff + 骨架通知 | 复用 ReportBuilder可选「通道」行 |
| Schema Diff + 骨架通知 | 复用 ReportBuilder「通道」行 |
| 夹具 | `fixtures/mq/rocket-wallet/``fixtures/mq/kafka-patrol/` |
| 配置 | `mq_patterns`(含 MQ-K*)、`ignore.mq_destinations` |
| List 根数组骨架 | `List<CheckItemDetailVo>` 等(与 M2 需求合并交付) |
**验收**
1.`WalletDeductReq` 字段 → 企微出现 RocketMQ Topic 骨架变更
2.`CheckItemDetailVo` 字段 → 企微出现 Kafka Topic 骨架变更
### Phase M2 — 增强
### Phase M2 — 增强
| 任务 | 说明 |
|------|------|
| MQ03~MQ05、MQ-K03/K04 | convertAndSend、MessageBuilder、JSON 字符串发送、ProducerRecord |
| MQ03~MQ05、MQ-K03/K04 | convertAndSend、MessageBuilder/`Message<T>`、JSON 字符串发送、ProducerRecord |
| MQ-R01~R03 | RocketMQ Listener + Kafka `@KafkaListener` / parse 补强 |
| List 根数组骨架 | 巡店 `List<CheckItemDetailVo>` |
| 夹具 | `fixtures/mq/rocket-im/``fixtures/mq/kafka-record/` |
### Phase M3 — 运营
**验收**
| 任务 | 说明 |
|------|------|
| 积压风险提示文案 | 统一提示评估消费 lag / 积压 |
| 更多夹具 | 值班 Kafka、中差评 Listener、IM Favorite 等 |
| 分 webhook / 标题前缀 | 缓存 vs MQ 可选拆分 |
1. `MessageBuilder.withPayload(DutyImNotice)` + `asyncSend` → 命中 MQ04改 VO 字段可告警
2. `kafkaTemplate.send(ProducerRecord)` → 命中 MQ-K03
3. Listener / `parseObject` 可补强同 Topic 弱类型生产点
---
@@ -312,10 +315,11 @@ manual_mappings:
|------|------|
| Kafka topic 按租户动态拼接 | 归一 `prefix:*``manual_mappings` |
| RocketMQ / Kafka 混用同一 VO | 各投递点独立告警(符合预期) |
| Listener 收 String、parse 在方法深处 | MQ-R03 + 复用 W06 AST |
| Listener 收 String、parse 在方法深处 | MQ-R03 + parse AST |
| 生产/消费跨模块对不齐 | 同仓索引 + destination 对齐;失败则仅写侧 |
| Jackson / Fastjson / Kafka JsonSerializer 细节差 | 首版字段名;必要时方言或 mapping |
| 只改消费未改生产类型 | 不告警(工具职责是消息体 Schema |
| `include_modules` 过窄 | 观察期扩大模块或置空全仓 |
---
@@ -334,6 +338,6 @@ manual_mappings:
## 10. 下一步
1. ~~按 Phase M1 开发~~ **已完成**MQ01/02 + MQ-K01/K02
2. Phase M2MQ03~05、MQ-K03/K04、MQ-R 读侧补强
3. 业务仓验收 Topic`capital-topic:WALLET_DEDUCT`RocketMQ、巡店/值班 Kafka topic
4. 观察期按需扩大 `include_modules`
2. ~~按 Phase M2 开发~~ **已完成**MQ03~05、MQ-K03/K04、MQ-R
3. 业务仓验收`MessageBuilder` IM Topic、巡店/值班 Kafka、`capital-topic:WALLET_DEDUCT`
4. 观察期按需扩大 `include_modules`

View File

@@ -535,20 +535,20 @@ notify:
| 误报反馈 | `suppressions` 按写入点 / change_types 精细忽略 |
| 更多业务场景覆盖 | 考勤、文件下载进度等 |
### Phase 4 — MQ 消息体结构检测(方案已落地,含 Kafka,开发待启
### Phase 4 — MQ 消息体结构检测(已落地,含 Kafka
业务仓同时存在:
- **RocketMQ**`RocketMQTemplate.syncSend(topic:tag, dto)` / `RocketMQListener<T>`
- **Kafka**`KafkaTemplate.send(topic, vo|List)` / `@KafkaListener` + `parseObject`
- **RocketMQ**`RocketMQTemplate.syncSend` / `asyncSend` / `MessageBuilder` / `RocketMQListener<T>`
- **Kafka**`KafkaTemplate.send(topic|ProducerRecord, …)` / `@KafkaListener` + `parseObject`
消息体字段变更会导致积压旧消息反序列化失败,风险模型与 Redis 同类。
| 任务 | 说明 | 状态 |
|------|------|------|
| 方案文档 | RocketMQ + Kafka 模式、destination、复用 Schema Diff/企微 | ✅ 见专用文档 |
| Phase M1 | RocketMQ syncSend + Kafka send + 骨架通知 + 夹具 | 待启 |
| Phase M2/M3 | Listener/parse 补强、更多投递形态、运营 | 待启 |
| 方案文档 | RocketMQ + Kafka 模式、destination、复用 Schema Diff/企微 | ✅ |
| Phase M1 | RocketMQ sync/async + Kafka send + 骨架通知 + 夹具 | |
| Phase M2 | MessageBuilder / convertAndSend / ProducerRecord / JSON 字符串 + MQ-R | |
**专用方案:** [`docs/MQ序列化结构检测方案.md`](./MQ序列化结构检测方案.md)
@@ -561,6 +561,8 @@ notify:
- `SchemaDifferTest`:纯字段路径对比逻辑
- `JavaSchemaExtractorTest`:类字段展开、注解、内部类
- `RedisWritePointDetectorTest`:各种写入 AST 模式匹配
- `MqWritePointDetectorTest`RocketMQ/Kafka 投递(含 MQ04 MessageBuilder、MQ-K03 ProducerRecord
- `MqReadHintDetectorTest`MQ-R Listener / parseObject 补强
- `RedisKeyResolverTest`常量、format、拼接推断
### 11.2 样本夹具测试fixtures
@@ -574,6 +576,10 @@ notify:
| `fixtures/tenant/` | 包装结构变更TenantVO → CacheEnvelope |
| `fixtures/lock/` | 锁/计数器/token 应被忽略 |
| `fixtures/template/` | W04 Template 直写 |
| `fixtures/mq/rocket-wallet/` | RocketMQ syncSendMQ01 |
| `fixtures/mq/rocket-im/` | MessageBuilder + asyncSendMQ04、convertAndSendMQ03 |
| `fixtures/mq/kafka-patrol/` | Kafka List 根数组MQ-K01 |
| `fixtures/mq/kafka-record/` | ProducerRecordMQ-K03、JSON 字符串 sendMQ-K04 |
### 11.3 端到端测试

View File

@@ -43,9 +43,9 @@ include_modules:
`redisCheck` 仓库维护,随 jar 发布,默认包含:
- `detection.patterns`**W01~W05**JSON 字符串写入 + Template 直写 + Hash
- `detection.mq_patterns`**MQ01/MQ02、MQ-K01/MQ-K02**RocketMQ / Kafka 生产侧)
- `detection.mq_patterns`**MQ01~MQ05、MQ-K01~MQ-K04**RocketMQ / Kafka 生产侧)
- `detection.read_hints_enabled`W06 读侧反序列化类型辅助(默认 true
- `detection.mq_read_hints_enabled`MQ 读侧补强Phase M2默认 false
- `detection.mq_read_hints_enabled`MQ-R 读侧 Listener / parse 补强(默认 true
- `ignore.key_patterns`(锁 / 计数器 / token
- `ignore.mq_destinations`MQ destination 忽略,默认真空)
- `detection.min_confidence``max_field_depth`
@@ -113,18 +113,23 @@ detection:
- W04 # redisTemplate 直写对象
- W05 # opsForHash().put
# MQ 生产侧投递Phase M1
# MQ 生产侧投递Phase M1 + M2
mq_patterns:
- MQ01 # rocketMQTemplate.syncSend
- MQ02 # asyncSend / syncSendOrderly / sendOneWay
- MQ03 # convertAndSend
- MQ04 # MessageBuilder.withPayload / Message<T>
- MQ05 # 先 toJSONString 再 send String
- MQ-K01 # kafkaTemplate.send(topic, payload)
- MQ-K02 # kafkaTemplate.send(topic, key, payload)
- MQ-K03 # kafkaTemplate.send(ProducerRecord)
- MQ-K04 # 先 JSON 序列化为 String 再 send
# W06读侧反序列化类型辅助不产生独立告警
read_hints_enabled: true
# MQ-R 读侧补强(Phase M2默认关闭
mq_read_hints_enabled: false
# MQ-R 读侧补强(Listener / parseObject
mq_read_hints_enabled: true
# 类型推断最低置信度,低于此值标记为低置信度提示
min_confidence: 0.6
@@ -240,7 +245,7 @@ detection:
- **不是写入模式**:不会单独因为「多了一处 parse」而告警
- 能关联到 `redis get(key)` 时,还可补强 unresolved key
- 覆盖优先级:`manual_mappings` > W06 > 写侧 AST
- **仅补强 Redis 写入点**MQ 投递点W06MQ-R 为 Phase M2
- **仅补强 Redis 写入点**MQ 投递点走 `mq_read_hints_enabled`MQ-R
关闭示例:
@@ -249,12 +254,12 @@ detection:
read_hints_enabled: false
```
### 3.5.2 detection.mq_patterns / ignore.mq_destinationsMQ Phase M1
### 3.5.2 detection.mq_patterns / ignore.mq_destinationsMQ
| 配置项 | 说明 |
|--------|------|
| `mq_patterns` | `MQ01`/`MQ02`RocketMQ`MQ-K01`/`MQ-K02`Kafka;默认已开启 |
| `mq_read_hints_enabled` | MQ-R 读侧补强,**默认 false** |
| `mq_patterns` | RocketMQ`MQ01`~`MQ05`Kafka`MQ-K01`~`MQ-K04`;默认已全部开启 |
| `mq_read_hints_enabled` | MQ-R 读侧补强Listener / parse**默认 true** |
| `ignore.mq_destinations` | 忽略 destinationglob`benchmark:*` |
企微 MQ 块:`- Topic --> ...`,并带 `> **通道**: RocketMQ|Kafka`
@@ -375,10 +380,10 @@ include_modules: [] # 全仓
---
## 7. MQ 消息体检测Phase M1 已落地)
## 7. MQ 消息体检测Phase M1 + M2 已落地)
同一 jar 默认启用 RocketMQ / Kafka **生产侧**投递检测(`detection.mq_patterns`),与 Redis 共用 Diff、骨架与企微模板。
同一 jar 默认启用 RocketMQ / Kafka **生产侧**投递检测(`detection.mq_patterns`与 **MQ-R** 读侧补强,与 Redis 共用 Diff、骨架与企微模板。
- 配置说明见 §3.5.2
- 读侧 MQ-R、`MessageBuilder` / `ProducerRecord` 等见方案 Phase M2
- `MessageBuilder` / `convertAndSend` / `ProducerRecord` / JSON 字符串发送、Listener/`parseObject` 补强
- 完整设计:[MQ序列化结构检测方案.md](./MQ序列化结构检测方案.md)