feat: V1.1 - 文件结构修改
This commit is contained in:
339
docs/v1.0/MQ序列化结构检测实施方案.md
Normal file
339
docs/v1.0/MQ序列化结构检测实施方案.md
Normal file
@@ -0,0 +1,339 @@
|
||||
# MQ 消息体序列化结构变更检测 — 方案
|
||||
|
||||
> 版本:v0.4
|
||||
> 日期:2026-07-15
|
||||
> 状态:**Phase M1 + M2 已实现**(生产侧 MQ01~05 / MQ-K01~K04 + 读侧 MQ-R)
|
||||
> 关联:复用 `serialization-schema-checker` 的 Schema 提取、Diff、企微通知与 CI 框架
|
||||
> 业务样本仓:`jnpf-java-cloud`(**RocketMQ + Kafka**)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 1.1 为什么要做
|
||||
|
||||
业务同时使用 **RocketMQ** 与 **Kafka** 投递业务对象:
|
||||
|
||||
| 中间件 | 典型写法 | 序列化要点 |
|
||||
|--------|----------|------------|
|
||||
| RocketMQ | `rocketMQTemplate.syncSend(topic:tag, dto)` | Spring MessageConverter(多为 Jackson)把对象变成消息体 |
|
||||
| Kafka | `kafkaTemplate.send(topic, vo|List)` | Spring `KafkaTemplate` + value serializer(多为 Json)编码对象 |
|
||||
| Kafka 消费 | `@KafkaListener` + `parseObject(message, Xxx.class)` | 常以 String 接收后再 Fastjson 反序列化 |
|
||||
|
||||
当消息 DTO / VO **删字段、改类型、加包装层**时:
|
||||
|
||||
- Topic / 重试队列里仍可能有**旧结构消息**
|
||||
- 新消费代码反序列化失败,或字段为空导致静默逻辑错误
|
||||
|
||||
这与 Redis 缓存「残留旧 value」同一类问题:
|
||||
|
||||
| | Redis | RocketMQ | Kafka |
|
||||
|--|-------|----------|-------|
|
||||
| 残留形态 | 未过期 key | Topic 积压 / 重试 | Topic 积压 / 消费 lag |
|
||||
| 路由标识 | key 模式 | **topic:tag** | **topic**(一般无 tag;动态后缀可归一 `*`) |
|
||||
| 典型序列化 | Fastjson 字符串或 Template 直写 | MessageConverter | Kafka JsonSerializer / 手写 JSON 字符串 |
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
在 push 时静态分析 **消息体类型的序列化 Schema** 是否相对对比区间发生变更,覆盖 **RocketMQ + Kafka**,并复用现有企微通知 / notify|block 能力。
|
||||
|
||||
### 1.3 非目标
|
||||
|
||||
- 不连接真实 Broker,不拉取积压消息做运行时校验
|
||||
- 不解析依赖 jar 内消息类型(仅本仓 `src/main/java`)
|
||||
- 不替代权限、幂等、消费失败重试等业务正确性检查
|
||||
- 不扫仅 Admin 建 Topic 的工具类(如 `KafkaTopicUtil`,无业务 body)
|
||||
- RabbitMQ 等若后续出现再扩展(当前仓以 RocketMQ / Kafka 为主)
|
||||
|
||||
---
|
||||
|
||||
## 2. 业务调研结论(jnpf-java-cloud)
|
||||
|
||||
### 2.1 RocketMQ
|
||||
|
||||
| 写法 | 出现情况 | 策略 |
|
||||
|------|----------|------|
|
||||
| `rocketMQTemplate.syncSend(dest, dto)` | 高(如钱包扣费) | **纳入** |
|
||||
| `asyncSend` / `syncSendOrderly` 等 | 中 | **纳入** |
|
||||
| `convertAndSend` | 视封装而定 | **纳入** |
|
||||
| `MessageBuilder.withPayload` 再 send | 中(如 IM 延时消息) | **纳入**(MQ04) |
|
||||
| 先 `JSON.toJSONString` 再发 String | 较低 | unwrap 后取类型(MQ05) |
|
||||
| 只发 `String` / `byte[]` / 无泛型 `Message` | 有 | **默认忽略** |
|
||||
|
||||
样本(资金钱包):
|
||||
|
||||
```java
|
||||
rocketMQTemplate.syncSend(CapitalMqConstants.TOPIC + ":" + tag, req); // WalletDeductReq
|
||||
|
||||
@RocketMQMessageListener(...)
|
||||
public class WalletDeductConsumer implements RocketMQListener<WalletDeductReq> { ... }
|
||||
```
|
||||
|
||||
### 2.2 Kafka
|
||||
|
||||
| 写法 | 出现情况 | 策略 |
|
||||
|------|----------|------|
|
||||
| `kafkaTemplate.send(topic, dto)` | 中(值班食安项等) | **纳入** |
|
||||
| `kafkaTemplate.send(topic, List<Xxx>)` | 中(巡店食安项列表) | **纳入**(rootArray) |
|
||||
| `kafkaTemplate.send(ProducerRecord)` | 中 | **纳入**(MQ-K03) |
|
||||
| 先 JSON 再 `send(topic, json)` | 较低 | unwrap(MQ-K04) |
|
||||
| `@KafkaListener` + `String` + `JSONObject.parseObject(..., Xxx.class)` | 有(数据分析中差评) | **读侧补强 MQ-R** |
|
||||
| `KafkaTopicUtil` 仅创建 Topic | 有(租户) | **忽略**(无消息体) |
|
||||
|
||||
生产样本(巡店):
|
||||
|
||||
```java
|
||||
List<CheckItemDetailVo> thousandsData = ...;
|
||||
kafkaTemplate.send(topicBuilder.patrolStoreTopic(tenantId), thousandsData);
|
||||
```
|
||||
|
||||
生产样本(值班):
|
||||
|
||||
```java
|
||||
KafkaTemplate<String, Object> kafkaTemplate;
|
||||
kafkaTemplate.send(topic, data); // CheckItemDetailVO
|
||||
```
|
||||
|
||||
消费样本(数据分析):
|
||||
|
||||
```java
|
||||
@KafkaListener(topics = "ftb-evaluate-real-notification${...}", groupId = "...")
|
||||
public void handleMessage(String message) {
|
||||
AddedMessageNotificationToVO vo = JSONObject.parseObject(message, AddedMessageNotificationToVO.class);
|
||||
}
|
||||
```
|
||||
|
||||
动态 Topic(如按租户拼接)静态推断结果形如 `patrol-store-topic:*`,与 Redis key `*` 规则一致。
|
||||
|
||||
### 2.3 「Key」等价物
|
||||
|
||||
| 中间件 | 聚合键形态 | 来源 |
|
||||
|--------|------------|------|
|
||||
| RocketMQ | `topic:tag` | 字面量、常量、`TOPIC + ":" + TAG` |
|
||||
| Kafka | `topic` | 字面量、常量、`topicBuilder.xxx(tenantId)` → 前缀+`*` |
|
||||
|
||||
无法解析时:展示表达式 + `<font color="comment">(destination 未解析)</font>`。
|
||||
|
||||
### 2.4 读侧补强
|
||||
|
||||
| 中间件 | 补强来源 |
|
||||
|--------|----------|
|
||||
| RocketMQ | `RocketMQListener<T>`、`onMessage(T)` + `@RocketMQMessageListener` |
|
||||
| Kafka | `@KafkaListener` 方法参数类型;或方法内 `parseObject/parseArray(..., Xxx.class)`(与现有 W06 共享解析能力) |
|
||||
|
||||
开关:`detection.mq_read_hints_enabled`(默认 **true**)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 方案总览
|
||||
|
||||
### 3.1 产品形态
|
||||
|
||||
并入现有 `serialization-schema-checker`:
|
||||
|
||||
- 同一 CLI / 流水线 / Schema Diff / 企微模板
|
||||
- 配置增加 `mq_patterns`(含 RocketMQ + Kafka)
|
||||
- 通知按 **Topic / destination** 分块;文案统一 `Topic -->`
|
||||
|
||||
### 3.2 与现有链路
|
||||
|
||||
```text
|
||||
Git Diff → 变更 Java 文件
|
||||
├─ Redis:W01~W05 + W06 ← 已有
|
||||
└─ MQ:RocketMQ(MQ01~05)+ Kafka(MQ-K01~K04)
|
||||
+ Listener / parse 补强(MQ-R) ← 已实现
|
||||
↓
|
||||
同一套 TypeSchema / SchemaDiffer / Skeleton / WeCom
|
||||
```
|
||||
|
||||
对比区间:push **`before` → `after`**。
|
||||
|
||||
### 3.3 核心原则
|
||||
|
||||
1. 只关心**消息体对象 Schema**,不关心 Broker / ACL / 限流
|
||||
2. **有结构变更即告警**;`block` 与缓存共用
|
||||
3. **静态分析**;仅本仓 `src/main/java`
|
||||
4. RocketMQ 与 Kafka **同一 Diff / 通知模型**,仅投递 AST 模式不同
|
||||
|
||||
---
|
||||
|
||||
## 4. 检测模式设计
|
||||
|
||||
### 4.1 生产侧 — RocketMQ
|
||||
|
||||
| 模式 ID | 匹配表达式 | 提取 |
|
||||
|---------|------------|------|
|
||||
| MQ01 | `rocketMQTemplate.syncSend(dest, payload, …)` | dest、payload 类型 |
|
||||
| MQ02 | `asyncSend` / `syncSendOrderly` / `sendOneWay` 等 | 同上 |
|
||||
| MQ03 | `convertAndSend(dest, payload)` | 同上 |
|
||||
| MQ04 | `MessageBuilder.withPayload(obj)` 再 send,或 `Message<T>` | payload / `T` |
|
||||
| MQ05 | 先 `toJSONString`/`getObjectToString` 再 send String | unwrap 后类型 |
|
||||
|
||||
### 4.2 生产侧 — Kafka
|
||||
|
||||
| 模式 ID | 匹配表达式 | 提取 |
|
||||
|---------|------------|------|
|
||||
| MQ-K01 | `kafkaTemplate.send(topic, payload)` | topic、payload 类型 |
|
||||
| MQ-K02 | `kafkaTemplate.send(topic, key, payload)` | 同上(忽略分区 key) |
|
||||
| MQ-K03 | `send(ProducerRecord)` | topic + value 类型 |
|
||||
| MQ-K04 | 先 JSON 序列化为 String 再 `send(topic, json)` | unwrap 后类型 |
|
||||
|
||||
Payload 为 `List<Xxx>` / `Collection` 时标记 **rootArray**,骨架为 JSON 数组(与 Redis List 一致)。
|
||||
|
||||
**忽略**:
|
||||
|
||||
- payload 为字面量、纯无结构 `String`/`byte[]`(无业务类型时)
|
||||
- 仅 Topic Admin API(`AdminClient.createTopics` 等)
|
||||
- destination 命中 `ignore.mq_destinations`
|
||||
|
||||
### 4.3 消费侧辅助(不单独告警)
|
||||
|
||||
| 模式 ID | 匹配 | 作用 |
|
||||
|---------|------|------|
|
||||
| MQ-R01 | `RocketMQListener<T>` / `@RocketMQMessageListener` | 补强同 destination 生产点 |
|
||||
| MQ-R02 | `@KafkaListener` + 参数类型 `T`(非 String) | 补强同 topic |
|
||||
| MQ-R03 | Listener 内 `parseObject`/`parseArray(..., Xxx.class)` | 补强(复用 parse AST) |
|
||||
|
||||
开关:`detection.mq_read_hints_enabled`(默认 true)。
|
||||
|
||||
### 4.4 Schema Diff
|
||||
|
||||
复用现有变更类型与注解规则。
|
||||
序列化方言:首版按字段名;Jackson / Fastjson / Kafka JsonSerializer 差异必要时用 `manual_mappings`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 报告与通知
|
||||
|
||||
### 5.1 企微块(RocketMQ / Kafka 统一)
|
||||
|
||||
```markdown
|
||||
- Topic --> `capital-topic:WALLET_DEDUCT`
|
||||
> **通道**: `RocketMQ`
|
||||
> **位置**: `WalletDeductProducer#send:38`
|
||||
> **类型**: `WalletDeductReq`
|
||||
> **value值由:** “{...}”
|
||||
> **变更为:** “{...}”
|
||||
|
||||
- Topic --> `patrol-store-food-safe:*`
|
||||
> **通道**: `Kafka`
|
||||
> **位置**: `PatrolServiceImpl#sendFoodSafeData:3140`
|
||||
> **类型**: `List<CheckItemDetailVo>`
|
||||
> **value值由:** “[{...}]”
|
||||
> **变更为:** “[{...}]”
|
||||
```
|
||||
|
||||
- 删除字段橙 `warning`;新增绿 `info`
|
||||
- destination 未解析时灰色提示
|
||||
|
||||
「通道」字段用于区分中间件;若模板求简,可省略通道仅靠 Topic 形态区分。
|
||||
|
||||
### 5.2 CI 控制台
|
||||
|
||||
字段明细可标注 `RocketMQ` / `Kafka`;再输出与企微一致的 Markdown。
|
||||
|
||||
---
|
||||
|
||||
## 6. 配置草案
|
||||
|
||||
```yaml
|
||||
detection:
|
||||
patterns: [W01, W02, W03, W04, W05]
|
||||
read_hints_enabled: true
|
||||
|
||||
mq_patterns:
|
||||
# RocketMQ
|
||||
- MQ01
|
||||
- MQ02
|
||||
- MQ03
|
||||
- MQ04
|
||||
- MQ05
|
||||
# Kafka
|
||||
- MQ-K01
|
||||
- MQ-K02
|
||||
- MQ-K03
|
||||
- MQ-K04
|
||||
mq_read_hints_enabled: true
|
||||
|
||||
ignore:
|
||||
mq_destinations:
|
||||
- "*:TEST"
|
||||
- "benchmark:*"
|
||||
|
||||
manual_mappings:
|
||||
- id: wallet-deduct-mq
|
||||
writer_method: "jnpf.capital.module.wallet.mq.WalletDeductProducer#send"
|
||||
key_pattern: "capital-topic:WALLET_DEDUCT"
|
||||
value_type: "jnpf.model.capital.dto.WalletDeductReq"
|
||||
|
||||
- id: patrol-kafka-food-safe
|
||||
writer_method: "jnpf.service.impl.PatrolServiceImpl#sendFoodSafeData"
|
||||
key_pattern: "*-patrol-store-*" # 按实际 topic 规则调整
|
||||
value_type: "jnpf.model.analyses.CheckItemDetailVo"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 分阶段交付
|
||||
|
||||
### Phase M1 — MVP(RocketMQ + Kafka 基础投递)✅
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| MQ01/MQ02 | RocketMQ `syncSend` / `asyncSend` |
|
||||
| MQ-K01/MQ-K02 | Kafka `send(topic, payload)` / 三参 send |
|
||||
| destination 推断 | 字面量、常量、拼接;Kafka 动态 topic → `*` |
|
||||
| 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 — 增强 ✅
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| MQ03~MQ05、MQ-K03/K04 | convertAndSend、MessageBuilder/`Message<T>`、JSON 字符串发送、ProducerRecord |
|
||||
| MQ-R01~R03 | RocketMQ Listener + Kafka `@KafkaListener` / parse 补强 |
|
||||
| 夹具 | `fixtures/mq/rocket-im/`、`fixtures/mq/kafka-record/` |
|
||||
|
||||
**验收**:
|
||||
|
||||
1. `MessageBuilder.withPayload(DutyImNotice)` + `asyncSend` → 命中 MQ04,改 VO 字段可告警
|
||||
2. `kafkaTemplate.send(ProducerRecord)` → 命中 MQ-K03
|
||||
3. Listener / `parseObject` 可补强同 Topic 弱类型生产点
|
||||
|
||||
---
|
||||
|
||||
## 8. 风险与限制
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|------|------|
|
||||
| Kafka topic 按租户动态拼接 | 归一 `prefix:*`;`manual_mappings` |
|
||||
| RocketMQ / Kafka 混用同一 VO | 各投递点独立告警(符合预期) |
|
||||
| Listener 收 String、parse 在方法深处 | MQ-R03 + parse AST |
|
||||
| 生产/消费跨模块对不齐 | 同仓索引 + destination 对齐;失败则仅写侧 |
|
||||
| Jackson / Fastjson / Kafka JsonSerializer 细节差 | 首版字段名;必要时方言或 mapping |
|
||||
| 只改消费未改生产类型 | 不告警(工具职责是消息体 Schema) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 决策对齐
|
||||
|
||||
| 项 | 结论 |
|
||||
|----|------|
|
||||
| 中间件范围 | **RocketMQ + Kafka**|
|
||||
| 对比区间 | `gitea.event.before` → `gitea.sha` |
|
||||
| 交付 | 同一 jar;patterns 区分 Redis / MQ(含 MQ-K*) |
|
||||
|
||||
---
|
||||
|
||||
## 10. 下一步
|
||||
|
||||
1. ~~按 Phase M1 开发~~ **已完成**(MQ01/02 + MQ-K01/K02)
|
||||
2. ~~按 Phase M2 开发~~ **已完成**(MQ03~05、MQ-K03/K04、MQ-R)
|
||||
3. 业务仓验收:`MessageBuilder` IM Topic、巡店/值班 Kafka、`capital-topic:WALLET_DEDUCT`
|
||||
646
docs/v1.0/redis序列化结构检测实施方案.md
Normal file
646
docs/v1.0/redis序列化结构检测实施方案.md
Normal file
@@ -0,0 +1,646 @@
|
||||
# 序列化结构变更检测 — 实施方案
|
||||
|
||||
> 版本:v0.3
|
||||
> 日期:2026-07-15
|
||||
> 技术栈:Java 11 + Maven + JavaParser
|
||||
> 目标仓库:`schemaCheck`(工具) / `jnpf-java-cloud`(被检测业务仓库)
|
||||
> 当前阶段:**Phase 1 + Phase 2 已完成**;**MQ 扩展方案已文档落地**(见 `docs/MQ序列化结构检测方案.md`)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
业务仓库 `jnpf-java-cloud` 是多模块 Java 微服务项目,广泛使用 Redis 缓存业务对象。当开发者修改 VO/DTO 字段、调整序列化包装结构、或变更 Redis 写入逻辑时,线上 Redis 中可能仍存在旧结构数据,导致:
|
||||
|
||||
- 反序列化失败
|
||||
- 字段读取为空
|
||||
- 新旧结构并存引发隐蔽 Bug
|
||||
|
||||
典型变更示例(租户库信息缓存):
|
||||
|
||||
**变更前(逻辑上等价于直接缓存 `TenantVO`):**
|
||||
|
||||
```json
|
||||
{
|
||||
"dbName": "",
|
||||
"linkList": [{ "id": "", "serviceName": "", "...": "..." }]
|
||||
}
|
||||
```
|
||||
|
||||
**变更后(`TenantDbContentCacheHelper` 包装为 `CacheEnvelope`):**
|
||||
|
||||
```json
|
||||
{
|
||||
"vo": {
|
||||
"dbName": "",
|
||||
"linkList": [{ "id": "", "serviceName": "", "...": "..." }]
|
||||
},
|
||||
"expiresAtMs": 0
|
||||
}
|
||||
```
|
||||
|
||||
该变更在业务代码中已有真实对应:
|
||||
|
||||
- Key:`tenant:db:content:{encode}`
|
||||
- 写入类:`jnpf.util.TenantDbContentCacheHelper#cacheSuccess`
|
||||
- Value 类型:`CacheEnvelope { TenantVO vo; Long expiresAtMs; }`
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
在 **push 时** 自动执行检测:
|
||||
|
||||
1. 对比两次提交(`old-sha` vs `new-sha`)之间的代码差异
|
||||
2. 识别 Redis value 序列化结构是否发生变更
|
||||
3. 生成结构化变更报告
|
||||
4. 通过企微机器人发送通知
|
||||
5. 通过开关控制 **仅通知** 或 **阻断流水线**
|
||||
|
||||
### 1.3 非目标
|
||||
|
||||
- 不连接真实 Redis 实例做运行时校验
|
||||
- 不扫描 Maven 依赖 jar 中的类(仅分析业务仓库源码)
|
||||
- 不做全量历史扫描(仅 diff 触发)
|
||||
- 不替代 CodeChecker / AI Code Review 等现有能力
|
||||
|
||||
---
|
||||
|
||||
## 2. 业务仓库调研结论
|
||||
|
||||
基于对 `jnpf-java-cloud` 的静态检索,得出以下判断(作为方案输入,不再向业务方重复确认):
|
||||
|
||||
### 2.1 Redis 序列化方式
|
||||
|
||||
| 类型 | 出现频率 | 策略 |
|
||||
|------|----------|------|
|
||||
| `JSON.toJSONString(obj)` / `JSONObject.toJSONString(obj)` | 高 | **已支持**(W01/W02) |
|
||||
| `JsonUtil.getObjectToString(obj)` | 高 | **已支持**(W03) |
|
||||
| `RedisTemplate.opsForValue().set(key, obj)` 直接写对象 | 中 | **已支持**(W04) |
|
||||
| `RedisTemplate.opsForHash().put(key, field, obj)` | 中 | **已支持**(W05) |
|
||||
| `StringRedisTemplate` 写 JSON 字符串 | 高 | **已支持** |
|
||||
| 锁 / 计数器 / token 简单值 | 高 | **默认忽略** |
|
||||
|
||||
### 2.2 Key 与实体映射
|
||||
|
||||
- 不存在统一的「Key → 类型」注册中心
|
||||
- 存在大量 `static final String`、`String.format(...)`、`buildCacheKey(...)` 等模式
|
||||
- 因此采用:**写入点静态推断 + 可选 YAML 人工补充映射**
|
||||
|
||||
### 2.3 多模块特征
|
||||
|
||||
- 根 `pom.xml` 下约 30+ 顶层模块、200+ 子模块
|
||||
- Java 版本混用(8/9/10/11),工具统一使用 **JDK 11** 编译运行
|
||||
- 公共工具类 `RedisUtil`、`JsonUtil` 来自外部依赖,不在业务仓源码内 — 仅分析调用方,不深入依赖实现
|
||||
|
||||
---
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
### 3.1 交付形态
|
||||
|
||||
|
||||
```text
|
||||
schemaCheck 仓库
|
||||
├── 开发 Java 分析工具
|
||||
├── mvn package 打 fat-jar
|
||||
├── 发布到 Nexus:com.codechecker:serialization-schema-checker:{version}
|
||||
└── 提供默认配置模板
|
||||
|
||||
jnpf-java-cloud 仓库
|
||||
├── .gitea/workflows/serialization-schema-check.yaml
|
||||
├── .gitea/config/serialization-schema-check-config.yaml
|
||||
└── push 时下载 jar 并执行检测
|
||||
```
|
||||
|
||||
### 3.2 架构图
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Gitea["Gitea Push Pipeline"]
|
||||
A[push 事件] --> B[浅克隆 old/new 提交]
|
||||
B --> C[下载 serialization-schema-checker.jar]
|
||||
C --> D[java -jar 执行检测]
|
||||
end
|
||||
|
||||
subgraph Checker["serialization-schema-checker (JDK 11)"]
|
||||
D --> E[GitDiffScanner]
|
||||
E --> F[RedisWritePointDetector]
|
||||
F --> G[JavaSchemaExtractor]
|
||||
G --> H[SchemaDiffer]
|
||||
H --> I{有结构变更?}
|
||||
I -->|否| J[exit 0]
|
||||
I -->|是| K[ReportBuilder]
|
||||
K --> L[WeComNotifier]
|
||||
L --> M{mode=block 且含变更?}
|
||||
M -->|是| N[exit 1]
|
||||
M -->|否| J
|
||||
end
|
||||
```
|
||||
|
||||
### 3.3 核心设计原则
|
||||
|
||||
1. **纯静态分析**:基于 Java 源码 AST + 符号解析,不启动 Spring 容器
|
||||
2. **Diff 驱动**:只分析本次 push 变更涉及的文件及其关联类型
|
||||
3. **本仓限定**:类型解析仅在业务仓库 `src/main/java` 范围内
|
||||
4. **可配置**:忽略规则、通知开关、阻断开关均可 YAML 配置
|
||||
5. **可演进**:已覆盖 JSON 字符串写入与 Template 直写 / Hash;后续可扩展读路径反向确认、报告落盘等
|
||||
|
||||
---
|
||||
|
||||
## 4. 技术选型
|
||||
|
||||
| 组件 | 选型 | 版本建议 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| 语言 | Java | 11 | 与 CI Runner `jdk11` 对齐 |
|
||||
| 构建 | Maven | 3.8+ | 与现有私库发布流程一致 |
|
||||
| AST 解析 | JavaParser | 3.25.x | 完整 Java 语法树 |
|
||||
| 符号解析 | javaparser-symbol-solver-core | 3.25.x | 跨文件类型推断 |
|
||||
| 配置 | SnakeYAML | 2.x | 读取检测配置 |
|
||||
| HTTP 通知 | JDK HttpClient / OkHttp | 11 内置 / 4.x | 企微 Webhook |
|
||||
| 报告 | Jackson | 2.15.x | JSON/Markdown 报告序列化 |
|
||||
| 测试 | JUnit 5 | 5.10.x | 单元测试 + 夹具样本 |
|
||||
|
||||
**Lombok 处理策略**:基于源码字段 + `@Data` 等注解推断序列化字段;对 `@Builder`、`@SuperBuilder` 等复杂场景标记为低置信度提示。后续可选集成 `lombok.ast` 或 delombok 预处理。
|
||||
|
||||
---
|
||||
|
||||
## 5. 工程结构(schemaCheck 仓库)
|
||||
|
||||
```text
|
||||
schemaCheck/
|
||||
├── pom.xml # 单模块工程(无父子结构)
|
||||
├── docs/
|
||||
│ ├── 实施方案.md
|
||||
│ ├── 配置说明.md
|
||||
│ ├── CI集成说明.md
|
||||
│ └── MQ序列化结构检测方案.md # MQ 消息体 Schema 扩展(方案)
|
||||
├── src/
|
||||
│ ├── main/
|
||||
│ │ ├── resources/
|
||||
│ │ │ └── default-config.yaml # 内置默认配置(随 jar 发布)
|
||||
│ │ └── java/com/codechecker/cache/
|
||||
│ │ ├── cli/ # 命令行入口
|
||||
│ │ ├── config/ # 配置模型
|
||||
│ │ ├── git/ # Git 操作
|
||||
│ │ ├── analyze/ # 编排与工作树扫描
|
||||
│ │ ├── detector/ # Redis 写入点检测(W01~W05)
|
||||
│ │ ├── schema/ # Schema 提取与注解
|
||||
│ │ ├── diff/ # 结构对比
|
||||
│ │ ├── key/ # Key 推断
|
||||
│ │ ├── report/ # 报告 / 企微 Markdown
|
||||
│ │ └── notify/ # 企微通知
|
||||
│ └── test/
|
||||
│ ├── resources/fixtures/{tenant,lock,template}/
|
||||
│ └── java/...
|
||||
├── .gitea/
|
||||
│ ├── workflows/serialization-schema-check.yaml
|
||||
│ └── config/serialization-schema-check-config.yaml
|
||||
└── target/ # 构建产物
|
||||
```
|
||||
|
||||
### 5.1 Maven 坐标
|
||||
|
||||
```xml
|
||||
<groupId>com.codechecker</groupId>
|
||||
<artifactId>serialization-schema-checker</artifactId>
|
||||
<version>1.0.0</version>
|
||||
```
|
||||
|
||||
打包为 **shaded/fat jar**,主类:`com.codechecker.cache.cli.SerializationSchemaCheckerMain`
|
||||
|
||||
---
|
||||
|
||||
## 6. 执行流程详解
|
||||
|
||||
### 6.1 CLI 参数
|
||||
|
||||
```bash
|
||||
java -jar serialization-schema-checker-1.0.0.jar \
|
||||
--config .gitea/config/serialization-schema-check-config.yaml \
|
||||
--repo-root /path/to/jnpf-java-cloud \
|
||||
--old-sha abc123 \
|
||||
--new-sha def456 \
|
||||
--branch feature/xxx \
|
||||
--modifier zhangsan \
|
||||
--modify-time "2026-07-13 14:00:00"
|
||||
```
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--config` | 是 | 检测配置文件路径 |
|
||||
| `--repo-root` | 是 | 业务仓库根目录 |
|
||||
| `--old-sha` | 是 | 对比基准提交 |
|
||||
| `--new-sha` | 是 | 当前提交 |
|
||||
| `--branch` | 否 | 分支名,用于报告展示 |
|
||||
| `--modifier` | 否 | 提交人(Gitea actor) |
|
||||
| `--modify-time` | 否 | 提交时间 |
|
||||
|
||||
### 6.2 对比基准(old-sha)获取策略
|
||||
|
||||
流水线使用 **push 区间累计对比**(方案:`before` → `after`):
|
||||
|
||||
1. `--new-sha` = `gitea.sha`(push 后 tip)
|
||||
2. `--old-sha` = `gitea.event.before`(push 前 tip)
|
||||
3. `before` 为空或全 `0`(新分支首次 push)→ 跳过检测,`exit 0`
|
||||
4. `workflow_dispatch` 无 before 时回退 `HEAD~1`
|
||||
5. 浅克隆当前 tip(`--depth 1`),再按需 `git fetch --depth 1 <before>`;统计 commit 数时 deepen 至 `before` 为祖先,或直接用事件 `commits` 数组长度(**无需全量历史**)
|
||||
|
||||
> 一次 push 含多个 commit 时,只做 **一次** 检测,对比区间为整次 push 的累计 diff(`before..after`),不会漏掉中间 commit 留下的结构变更。
|
||||
> 不做「每个 commit 各告警一条」;中间引入又被末 commit 改回的净无变更,累计结果可能为「无变更」(符合阻断「最终结构」的目标)。
|
||||
> 日志中的 commit 数:优先 `gitea.event.commits`;勿在未 deepen 时用浅库 `rev-list`(会少算)。
|
||||
|
||||
### 6.3 处理步骤
|
||||
|
||||
#### Step 1:加载配置
|
||||
|
||||
读取 `serialization-schema-check-config.yaml`,合并默认值(见 `docs/配置说明.md`)。
|
||||
|
||||
#### Step 2:Git Diff 扫描
|
||||
|
||||
```bash
|
||||
git diff --name-only {old-sha} {new-sha} -- '*.java'
|
||||
```
|
||||
|
||||
输出变更 Java 文件列表。同时记录 diff hunks,用于判断「是否仅注释/格式变更」。
|
||||
|
||||
#### Step 3:构建双版本源码索引
|
||||
|
||||
对 `old-sha` 和 `new-sha` 分别:
|
||||
|
||||
1. `git show {sha}:path/to/File.java` 提取文件内容(无需完整 checkout 两个 worktree)
|
||||
2. 解析为 `CompilationUnit`
|
||||
3. 建立 `类全名 → CompilationUnit` 索引(仅本仓 `src/main/java`)
|
||||
|
||||
#### Step 4:Redis 写入点检测
|
||||
|
||||
在 **变更文件** 中扫描以下 AST 模式:
|
||||
|
||||
| 模式 ID | 匹配表达式 | 提取信息 | 状态 |
|
||||
|---------|------------|----------|------|
|
||||
| W01 | `redisUtil.insert(key, JSON.toJSONString(expr), ttl)` | key 表达式、value 表达式 | ✅ |
|
||||
| W02 | `redisTemplate.opsForValue().set(key, JSON.toJSONString(expr), ...)` | 同上 | ✅ |
|
||||
| W03 | `stringRedisTemplate.opsForValue().set(key, JsonUtil.getObjectToString(expr), ...)` | 同上 | ✅ |
|
||||
| W04 | `redisTemplate.opsForValue().set(key, expr, ...)` 且 expr 非字面量 | 直写对象类型 | ✅ |
|
||||
| W05 | `redisTemplate.opsForHash().put(key, field, expr)` | Hash 写出 value 类型 | ✅ |
|
||||
|
||||
**忽略规则**(自动):
|
||||
|
||||
- value 为字符串字面量、数字、`UUID`、`"1"` 等琐碎值
|
||||
- 方法名含 `setIfAbsent`、`increment`、`delete`、`remove`、`expire` 等
|
||||
- key 匹配 `ignore.key_patterns` 配置(锁 / token / 登录计数等)
|
||||
|
||||
#### Step 4b:读侧类型辅助(W06,非写入模式)
|
||||
|
||||
W06 **不产生独立告警**,只扫描反序列化调用,用读到的 `Xxx.class` **补强**同文件(或同 key)写入点的 value 类型 / 根数组标记。
|
||||
|
||||
| 匹配示例 | 作用 |
|
||||
|----------|------|
|
||||
| `JSON.parseObject(raw, Xxx.class)` | 补强对象类型 |
|
||||
| `JSON.parseArray(raw, Xxx.class)` / `JsonUtil.getJsonToList` | 补强 `List<Xxx>`(rootArray) |
|
||||
| `JsonUtil.getJsonToBean(raw, Xxx.class)` | 同上 |
|
||||
| 可关联到 `redisUtil.getString(key)` / `opsForValue().get(key)` | 同时补强 key 模式 |
|
||||
|
||||
开关:`detection.read_hints_enabled`(默认 `true`)。优先级:`manual_mappings` > W06 补强 > 写侧 AST 推断。
|
||||
|
||||
#### Step 5:类型推断
|
||||
|
||||
对每个写入点的 `expr`,使用 JavaParser Symbol Solver 推断类型:
|
||||
|
||||
```java
|
||||
// 示例:TenantDbContentCacheHelper.cacheSuccess
|
||||
CacheEnvelope envelope = new CacheEnvelope();
|
||||
envelope.setVo(vo);
|
||||
envelope.setExpiresAtMs(expiresAtMs);
|
||||
redisUtil.insert(buildCacheKey(encode), JSON.toJSONString(envelope), ttl);
|
||||
```
|
||||
|
||||
推断链:
|
||||
|
||||
1. `JSON.toJSONString(envelope)` → 实参类型 `CacheEnvelope`
|
||||
2. 定位 `CacheEnvelope` 类(内部类需支持 `Outer$Inner`)
|
||||
3. 读取字段 `vo: TenantVO`、`expiresAtMs: Long`
|
||||
4. 递归展开 `TenantVO` → `dbName: String`、`linkList: List<TenantLinkModel>`
|
||||
5. 继续展开 `TenantLinkModel` 全部字段
|
||||
|
||||
**注解处理**:
|
||||
|
||||
| 注解 | 行为 | 状态 |
|
||||
|------|------|------|
|
||||
| `@JSONField(serialize = false)` | 排除字段 | ✅ |
|
||||
| `@JSONField(name = "xxx")` | 字段名映射 | ✅ |
|
||||
| `@JsonIgnore` | 排除字段 | ✅ |
|
||||
| `@JsonProperty("xxx")` | 字段名映射 | ✅ |
|
||||
| `@JsonIgnoreProperties({...})` | 类级忽略字段 | ✅ |
|
||||
| `@Schema` | 忽略(不影响序列化) | ✅ |
|
||||
|
||||
#### Step 6:生成 JSON Schema
|
||||
|
||||
将 Java 类型转为统一的 `TypeSchema` 树:
|
||||
|
||||
```json
|
||||
{
|
||||
"typeName": "jnpf.util.TenantDbContentCacheHelper.CacheEnvelope",
|
||||
"fields": [
|
||||
{
|
||||
"path": "vo",
|
||||
"javaType": "jnpf.model.TenantVO",
|
||||
"jsonType": "object",
|
||||
"children": [
|
||||
{ "path": "vo.dbName", "jsonType": "string" },
|
||||
{ "path": "vo.linkList", "jsonType": "array", "itemType": "jnpf.model.TenantLinkModel" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "expiresAtMs",
|
||||
"javaType": "java.lang.Long",
|
||||
"jsonType": "number"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 7:Schema Diff
|
||||
|
||||
对比同一写入点在 old/new 两个版本的 `TypeSchema`,输出 `SchemaChange` 列表。
|
||||
|
||||
**变更类型**(有结构差异即告警;`block` 下任意变更均阻断):
|
||||
|
||||
| 变更类型 | 示例 |
|
||||
|----------|------|
|
||||
| `FIELD_REMOVED` | 删除 `dbName` |
|
||||
| `TYPE_CHANGED` | `linkList` 从数组变对象 |
|
||||
| `WRAPPER_ADDED` | 顶层增加 `vo` 包装 |
|
||||
| `FIELD_PATH_MOVED` | `dbName` → `vo.dbName` |
|
||||
| `FIELD_ADDED` | 新增 `expiresAtMs` |
|
||||
| `KEY_PATTERN_CHANGED` | key 常量变更 |
|
||||
| `WRITE_POINT_REMOVED` | 删除缓存写入 |
|
||||
| `WRITE_POINT_ADDED` | 新增缓存写入 |
|
||||
| `LOW_CONFIDENCE` | 类型推断失败(仍提示,置信度较低) |
|
||||
|
||||
#### Step 8:报告与通知
|
||||
|
||||
生成 `CheckReport`,包含:
|
||||
|
||||
- 仓库名、分支、old/new sha、提交人、时间
|
||||
- 按 Key 聚合的结构变更(骨架 before/after)+ 字段级明细
|
||||
- 每项通用展示:**Key**、**位置**(`Class#method:line`)、**类型**、旧/新序列化骨架
|
||||
|
||||
企微 Markdown 规则:
|
||||
|
||||
- 抬头不含 mode 汇总;正文按 Key 展示骨架
|
||||
- **删除字段**:旧骨架中橙色 `<font color="warning">`
|
||||
- **新增字段**:新骨架中绿色 `<font color="info">`
|
||||
- key 未解析时展示源码表达式 + 灰色「(key 未解析)」
|
||||
- 单条超 4096 UTF-8 字节时按 Key 拆成多条依次发送
|
||||
|
||||
CI 控制台额外输出字段级变更明细(变更类型 + 位置 + 摘要),再打印与企微一致的 Markdown。
|
||||
|
||||
调用企微 Webhook 发送 Markdown(支持 `--dry-run` 仅本地输出)。
|
||||
|
||||
#### Step 9:退出码
|
||||
|
||||
| 条件 | 退出码 |
|
||||
|------|--------|
|
||||
| `enabled=false` / 无变更 | 0 |
|
||||
| `mode=notify` 且存在变更 | 0(仍通知) |
|
||||
| `mode=block` 且存在任意结构变更 | 1 |
|
||||
| 配置错误 / 执行异常 | 2 |
|
||||
|
||||
---
|
||||
|
||||
## 7. Redis Key 推断策略
|
||||
|
||||
### 7.1 自动推断
|
||||
|
||||
| 优先级 | 模式 | 示例 | 结果 |
|
||||
|--------|------|------|------|
|
||||
| 1 | 字符串字面量 | `"tenant:db:content:" + encode` | `tenant:db:content:*` |
|
||||
| 2 | 常量引用 | `CACHE_KEY_PREFIX + encode` | 追溯常量值 |
|
||||
| 3 | `String.format(CONST, args)` | `String.format(ATTENDANCE_BASE_SETTING_CACHE_KEY, tenantId)` | `fbt:attendance:base_setting:cache:*` |
|
||||
| 4 | 方法调用 | `buildCacheKey(encode)` | 读取方法内 return 表达式 |
|
||||
| 5 | 变量 | `redisKey` | `unknown-key` |
|
||||
|
||||
### 7.2 人工补充(配置)
|
||||
|
||||
```yaml
|
||||
manual_mappings:
|
||||
- id: tenant-db-content
|
||||
writer_method: "jnpf.util.TenantDbContentCacheHelper#cacheSuccess"
|
||||
key_pattern: "tenant:db:content:*"
|
||||
value_type: "jnpf.util.TenantDbContentCacheHelper.CacheEnvelope"
|
||||
```
|
||||
|
||||
当自动推断置信度低时,以 `manual_mappings` 为准。
|
||||
|
||||
---
|
||||
|
||||
## 8. 配置与开关设计
|
||||
|
||||
采用 **双层配置合并** 策略:
|
||||
|
||||
| 层级 | 位置 | 职责 |
|
||||
|------|------|------|
|
||||
| 默认配置 | 工具 jar 内 `default-config.yaml` | 检测模式、忽略规则等默认值 |
|
||||
| 业务覆盖 | `jnpf-java-cloud/.gitea/config/serialization-schema-check-config.yaml` | mode、notify、include_modules、manual_mappings |
|
||||
|
||||
合并规则:**业务配置覆盖默认配置**,未声明的项沿用默认值。
|
||||
|
||||
CLI 调用:
|
||||
|
||||
```bash
|
||||
java -jar serialization-schema-checker.jar \
|
||||
--config .gitea/config/serialization-schema-check-config.yaml \
|
||||
...
|
||||
```
|
||||
|
||||
工具启动时自动加载 jar 内 `default-config.yaml`,再与 `--config` 指定的业务配置深度合并。
|
||||
|
||||
详见 `docs/配置说明.md`。核心开关:
|
||||
|
||||
```yaml
|
||||
# 总开关:false 时跳过检测与通知,流水线直接通过
|
||||
enabled: true
|
||||
|
||||
# 运行模式:notify(仅通知)| block(检测到结构变更即阻断流水线)
|
||||
mode: notify
|
||||
|
||||
# 是否发送企微通知
|
||||
notify:
|
||||
enabled: true
|
||||
webhook_url: "" # 企微 Webhook 完整 URL;兼容旧字段 webhook_env
|
||||
```
|
||||
|
||||
企微消息约定见 `docs/配置说明.md` §5:**位置/类型**为每个 Key 的通用项;删除字段橙色、新增字段绿色。
|
||||
|
||||
---
|
||||
|
||||
## 9. CI 集成方案
|
||||
|
||||
详见 `docs/CI集成说明.md`。核心流程:
|
||||
|
||||
```yaml
|
||||
# jnpf-java-cloud/.gitea/workflows/serialization-schema-check.yaml(要点)
|
||||
# 检出:浅克隆 tip(depth 1)
|
||||
# 检测:--old-sha = gitea.event.before,--new-sha = gitea.sha
|
||||
# 按需 fetch before 提交对象,覆盖一次 push 的多 commit 累计 diff
|
||||
```
|
||||
|
||||
完整模板见 `docs/CI集成说明.md` / `.gitea/workflows/serialization-schema-check.yaml`。
|
||||
|
||||
---
|
||||
|
||||
## 10. 分阶段交付计划
|
||||
|
||||
### Phase 1 — MVP✅
|
||||
|
||||
**目标**:跑通端到端链路,覆盖租户缓存典型场景。
|
||||
|
||||
| 任务 | 产出 | 状态 |
|
||||
|------|------|------|
|
||||
| Maven 工程骨架 + CLI | 可执行 fat-jar | ✅ |
|
||||
| Git diff 扫描 | 变更文件列表 | ✅ |
|
||||
| W01~W03 写入点检测 | 覆盖 JSON 字符串写入 | ✅ |
|
||||
| 基础 Schema 提取 | 支持普通类、内部类、List、嵌套 | ✅ |
|
||||
| Schema Diff | 字段增删、包装、路径迁移 | ✅ |
|
||||
| 企微通知 | 按 Key 骨架 Markdown | ✅ |
|
||||
| notify/block / enabled | 配置驱动 | ✅ |
|
||||
| 样本夹具测试 | TenantVO/CacheEnvelope 等 | ✅ |
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- 对 `TenantDbContentCacheHelper` 的结构变更能输出报告并通知
|
||||
- 流水线 push 后能收到企微通知
|
||||
- `mode=block` 时任意结构变更导致 exit 1
|
||||
|
||||
### Phase 2 — 增强✅
|
||||
|
||||
| 任务 | 说明 | 状态 |
|
||||
|------|------|------|
|
||||
| W04/W05 模式 | RedisTemplate 直写对象、Hash 写入 | ✅ |
|
||||
| 注解完整支持 | Fastjson/Jackson(含 `@JsonIgnoreProperties`) | ✅ |
|
||||
| Key 推断增强 | `String.format`、常量拼接、`buildXxxKey` | ✅ |
|
||||
| 忽略规则完善 | 锁/计数器/token/字面量/setIfAbsent | ✅ |
|
||||
| 多模块性能 | 并行读文件、索引批量装载、`manual_mappings` | ✅ |
|
||||
| 企微高亮 | 删除橙 `warning` / 新增绿 `info`;位置+类型通用项 | ✅ |
|
||||
| W06 读侧辅助 | `parseObject`/`parseArray` 等补强写入 value 类型 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 11. 测试策略
|
||||
|
||||
### 11.1 单元测试
|
||||
|
||||
- `SchemaDifferTest`:纯字段路径对比逻辑
|
||||
- `JavaSchemaExtractorTest`:类字段展开、注解、内部类
|
||||
- `RedisWritePointDetectorTest`:各种写入 AST 模式匹配
|
||||
- `MqWritePointDetectorTest`:RocketMQ/Kafka 投递(含 MQ04 MessageBuilder、MQ-K03 ProducerRecord)
|
||||
- `MqReadHintDetectorTest`:MQ-R Listener / parseObject 补强
|
||||
- `RedisKeyResolverTest`:常量、format、拼接推断
|
||||
|
||||
### 11.2 样本夹具测试(fixtures)
|
||||
|
||||
「夹具」= 放在测试资源里的**脱敏源码样本**(不是连真实 Redis / 不是起 Gitea 流水线)。
|
||||
|
||||
路径:`src/test/resources/fixtures/`。测试代码加载这些 `.txt`/Java 片段,在内存中跑检测器 / Schema 对比,用来验证典型业务场景是否被正确识别。
|
||||
|
||||
| 夹具目录 | 验证点 |
|
||||
|----------|--------|
|
||||
| `fixtures/tenant/` | 包装结构变更(TenantVO → CacheEnvelope) |
|
||||
| `fixtures/lock/` | 锁/计数器/token 应被忽略 |
|
||||
| `fixtures/template/` | W04 Template 直写 |
|
||||
| `fixtures/mq/rocket-wallet/` | RocketMQ syncSend(MQ01) |
|
||||
| `fixtures/mq/rocket-im/` | MessageBuilder + asyncSend(MQ04)、convertAndSend(MQ03) |
|
||||
| `fixtures/mq/kafka-patrol/` | Kafka List 根数组(MQ-K01) |
|
||||
| `fixtures/mq/kafka-record/` | ProducerRecord(MQ-K03)、JSON 字符串 send(MQ-K04) |
|
||||
|
||||
### 11.3 端到端测试
|
||||
|
||||
在 `jnpf-java-cloud` 开测试分支,故意提交一个 VO 字段变更,验证流水线 + 企微通知。
|
||||
|
||||
---
|
||||
|
||||
## 12. 风险与限制
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| 类型推断失败 | 漏报 | 标记 `LOW_CONFIDENCE`,配置 `manual_mappings` |
|
||||
| Lombok 复杂注解 | 字段遗漏 | 基于源码字段 + 注解;后续 delombok |
|
||||
| 同一 key 多分支写不同类型 | 误报 | 报告注明置信度;人工 suppression |
|
||||
| 浅克隆拿不到 before | 漏检 / exit 2 | 按 SHA `fetch --depth 1` + deepen 兜底;见 CI 说明 |
|
||||
| 一次 push 多 commit | 旧方案仅看末 commit 会漏检 | 已改为 `before..after` 累计对比 |
|
||||
| 依赖 jar 中的类型 | 字段展开不完整 | 配置 `manual_mappings` 补充 |
|
||||
| JsonUtil 实现不可见 | 序列化规则猜测 | 默认按字段名序列化;与 Fastjson 对齐 |
|
||||
|
||||
---
|
||||
|
||||
## 13. 已确认决策(Grill 共识)
|
||||
|
||||
| # | 决策项 | 结论 |
|
||||
|---|--------|------|
|
||||
| 1 | 阻断范围 | `block` 模式下 **任意结构变更均阻断**(exit 1) |
|
||||
| 2 | 发布坐标 | 独立产物 `com.codechecker:serialization-schema-checker:1.0.0` |
|
||||
| 3 | 配置归属 | **双层配置**:jar 内 `default-config.yaml` + 业务仓覆盖合并 |
|
||||
| 4 | 上线策略 | 先 `notify` ,稳定后手动切 `block` |
|
||||
| 5 | 检测范围 | **仅 `src/main/java`**,不扫描测试代码 |
|
||||
|
||||
以上决策已纳入实施方案;**Phase 1 / Phase 2 已交付**。缓存侧可进入 Phase 3;MQ 侧以 [`MQ序列化结构检测方案.md`](MQ序列化结构检测实施方案.md) 为准评审后开发。
|
||||
|
||||
相关文档:
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| `docs/配置说明.md` | 缓存检测双层配置 |
|
||||
| `docs/CI集成说明.md` | 流水线 before/after、排障 |
|
||||
| `docs/MQ序列化结构检测方案.md` | MQ 消息体 Schema 监控方案(扩展) |
|
||||
|
||||
---
|
||||
|
||||
## 14. 附录:关键类设计草图
|
||||
|
||||
### WritePoint
|
||||
|
||||
```java
|
||||
public class WritePoint {
|
||||
String filePath;
|
||||
int lineNumber;
|
||||
String enclosingClass;
|
||||
String enclosingMethod;
|
||||
String keyExpression; // 原始 AST 表达式
|
||||
String resolvedKeyPattern; // 推断结果,如 tenant:db:content:*
|
||||
String valueExpression;
|
||||
String resolvedValueType; // 全限定类名
|
||||
double confidence; // 0.0 ~ 1.0
|
||||
}
|
||||
```
|
||||
|
||||
### SchemaChange
|
||||
|
||||
```java
|
||||
public class SchemaChange {
|
||||
ChangeType changeType; // FIELD_REMOVED, WRAPPER_ADDED, ...
|
||||
String keyPattern;
|
||||
String writeLocation; // class#method:line
|
||||
String fieldPath; // 如 vo.dbName
|
||||
String oldValue;
|
||||
String newValue;
|
||||
String message; // 人类可读描述
|
||||
}
|
||||
```
|
||||
|
||||
### CheckReport
|
||||
|
||||
```java
|
||||
public class CheckReport {
|
||||
String repository;
|
||||
String branch;
|
||||
String oldSha;
|
||||
String newSha;
|
||||
String modifier;
|
||||
String modifyTime;
|
||||
String mode;
|
||||
List<SchemaChange> changes;
|
||||
boolean blocked;
|
||||
int exitCode;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user