Compare commits
2 Commits
89a48a98aa
...
fd5aab5e7f
| Author | SHA1 | Date | |
|---|---|---|---|
| fd5aab5e7f | |||
| 8ef5abc262 |
@@ -1,10 +1,10 @@
|
||||
# ============================================================
|
||||
# 缓存序列化结构变更检测 — 业务仓库配置
|
||||
# 序列化结构变更检测 — 业务仓库配置
|
||||
# ============================================================
|
||||
# 说明:
|
||||
# - 本配置文件为业务覆盖配置,会与 jar 内 default-config.yaml 深度合并
|
||||
# - 未声明的项沿用工具内置默认值(忽略规则、检测模式等)
|
||||
# - 当前实现以 Redis 写入检测为主,后续可扩展其他缓存
|
||||
# - 当前以 Redis 缓存写入检测为主;MQ(RocketMQ/Kafka)方案见 docs/MQ序列化结构检测方案.md
|
||||
|
||||
# 总开关 true-执行检测 false-跳过检测(流水线直接通过,不发通知)
|
||||
enabled: true
|
||||
@@ -17,7 +17,7 @@ notify:
|
||||
enabled: true
|
||||
webhook_url: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=fa14f0b3-e01a-40f6-96bd-e18beb94e85e
|
||||
notify_on_clean: false
|
||||
title_prefix: "【缓存结构变更】"
|
||||
title_prefix: "【序列化结构变更】"
|
||||
|
||||
# 观察期:先只扫描 jnpf-tenant 模块,稳定后改为 include_modules: []
|
||||
include_modules:
|
||||
@@ -1,17 +1,17 @@
|
||||
name: 缓存序列化结构检查
|
||||
run-name: ${{ gitea.actor }}的缓存结构检查
|
||||
name: 序列化结构检查
|
||||
run-name: ${{ gitea.actor }}的序列化结构检查
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# cache-schema-checker 私库坐标:com.codechecker:cache-schema-checker:1.0.0
|
||||
CACHE_SCHEMA_CHECKER_VERSION: "1.0.0"
|
||||
CACHE_SCHEMA_CHECKER_REPO_URL: "http://192.168.3.25:18081/nexus/repository/maven-releases"
|
||||
# serialization-schema-checker 私库坐标:com.codechecker:serialization-schema-checker:1.0.0
|
||||
SERIALIZATION_SCHEMA_CHECKER_VERSION: "1.0.0"
|
||||
SERIALIZATION_SCHEMA_CHECKER_REPO_URL: "http://192.168.3.25:18081/nexus/repository/maven-releases"
|
||||
|
||||
jobs:
|
||||
cache-schema-check:
|
||||
serialization-schema-check:
|
||||
if: ${{ gitea.ref != 'refs/heads/pre' && gitea.ref != 'refs/heads/dev' && gitea.ref != 'refs/heads/master-2.0' }}
|
||||
runs-on: jdk11
|
||||
steps:
|
||||
@@ -27,30 +27,30 @@ jobs:
|
||||
git clone --depth 1 --single-branch --branch "${BRANCH}" "${REPO_URL}" .
|
||||
git checkout -B "${BRANCH}" "${NEW_SHA}"
|
||||
|
||||
echo "${NEW_SHA}" > /tmp/cache-schema-new-sha.txt
|
||||
echo "${NEW_SHA}" > /tmp/serialization-schema-new-sha.txt
|
||||
|
||||
- name: 检查配置文件
|
||||
run: |
|
||||
if [ ! -f .gitea/config/cache-schema-check-config.yaml ]; then
|
||||
echo "错误: 缺少 .gitea/config/cache-schema-check-config.yaml"
|
||||
if [ ! -f .gitea/config/serialization-schema-check-config.yaml ]; then
|
||||
echo "错误: 缺少 .gitea/config/serialization-schema-check-config.yaml"
|
||||
exit 1
|
||||
fi
|
||||
# 顶层总开关 enabled: false 时跳过后续步骤(与 notify.enabled 区分,仅匹配行首)
|
||||
if grep -Eq '^enabled:[[:space:]]*false([[:space:]]|#|$)' .gitea/config/cache-schema-check-config.yaml; then
|
||||
echo "总开关 enabled=false,跳过缓存结构检查"
|
||||
touch /tmp/cache-schema-check.skip
|
||||
if grep -Eq '^enabled:[[:space:]]*false([[:space:]]|#|$)' .gitea/config/serialization-schema-check-config.yaml; then
|
||||
echo "总开关 enabled=false,跳过序列化结构检查"
|
||||
touch /tmp/serialization-schema-check.skip
|
||||
fi
|
||||
|
||||
- name: 从 Nexus 私库下载 cache-schema-checker
|
||||
- name: 从 Nexus 私库下载 serialization-schema-checker
|
||||
run: |
|
||||
if [ -f /tmp/cache-schema-check.skip ]; then
|
||||
if [ -f /tmp/serialization-schema-check.skip ]; then
|
||||
echo "总开关已关闭,跳过下载"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
GROUP_PATH="com/codechecker/cache-schema-checker"
|
||||
JAR_NAME="cache-schema-checker-${CACHE_SCHEMA_CHECKER_VERSION}.jar"
|
||||
JAR_URL="${CACHE_SCHEMA_CHECKER_REPO_URL}/${GROUP_PATH}/${CACHE_SCHEMA_CHECKER_VERSION}/${JAR_NAME}"
|
||||
GROUP_PATH="com/codechecker/serialization-schema-checker"
|
||||
JAR_NAME="serialization-schema-checker-${SERIALIZATION_SCHEMA_CHECKER_VERSION}.jar"
|
||||
JAR_URL="${SERIALIZATION_SCHEMA_CHECKER_REPO_URL}/${GROUP_PATH}/${SERIALIZATION_SCHEMA_CHECKER_VERSION}/${JAR_NAME}"
|
||||
JAR_PATH="/tmp/${JAR_NAME}"
|
||||
|
||||
echo "下载: ${JAR_URL}"
|
||||
@@ -72,25 +72,25 @@ jobs:
|
||||
|
||||
- name: 验证 JDK
|
||||
run: |
|
||||
if [ -f /tmp/cache-schema-check.skip ]; then
|
||||
if [ -f /tmp/serialization-schema-check.skip ]; then
|
||||
echo "总开关已关闭,跳过"
|
||||
exit 0
|
||||
fi
|
||||
echo "Java: $(java -version 2>&1 | head -1)"
|
||||
|
||||
- name: 执行缓存序列化结构检测
|
||||
- name: 执行序列化结构检测
|
||||
env:
|
||||
# push 前 tip;新分支首次 push 时为全 0。workflow_dispatch 可能为空,下方会回退。
|
||||
PUSH_BEFORE: ${{ gitea.event.before }}
|
||||
# push 事件携带的 commits 列表(JSON);用于准确统计 commit 数(不受浅克隆影响)
|
||||
PUSH_COMMITS_JSON: ${{ toJson(gitea.event.commits) }}
|
||||
run: |
|
||||
if [ -f /tmp/cache-schema-check.skip ]; then
|
||||
if [ -f /tmp/serialization-schema-check.skip ]; then
|
||||
echo "总开关已关闭,跳过检测"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
NEW_SHA=$(cat /tmp/cache-schema-new-sha.txt)
|
||||
NEW_SHA=$(cat /tmp/serialization-schema-new-sha.txt)
|
||||
OLD_SHA="${PUSH_BEFORE}"
|
||||
|
||||
# 新分支首次 push(before 全 0)→ 跳过
|
||||
@@ -199,8 +199,8 @@ jobs:
|
||||
|
||||
COMMIT_TIME=$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S' "${NEW_SHA}")
|
||||
|
||||
java -jar "/tmp/cache-schema-checker-${CACHE_SCHEMA_CHECKER_VERSION}.jar" \
|
||||
--config .gitea/config/cache-schema-check-config.yaml \
|
||||
java -jar "/tmp/serialization-schema-checker-${SERIALIZATION_SCHEMA_CHECKER_VERSION}.jar" \
|
||||
--config .gitea/config/serialization-schema-check-config.yaml \
|
||||
--repo-root . \
|
||||
--old-sha "$OLD_SHA" \
|
||||
--new-sha "$NEW_SHA" \
|
||||
6
.idea/compiler.xml
generated
6
.idea/compiler.xml
generated
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
@@ -6,11 +6,11 @@
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="cache-schema-checker" />
|
||||
<module name="serialization-schema-checker" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel>
|
||||
<module name="cache-schema-checker" target="11" />
|
||||
<module name="serialization-schema-checker" target="11" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 缓存序列化结构检测 — CI 集成说明
|
||||
# 序列化结构检测 — CI 集成说明
|
||||
|
||||
---
|
||||
|
||||
@@ -11,7 +11,7 @@ Gitea Actions 触发
|
||||
↓
|
||||
浅克隆业务仓库 tip(depth=1)+ 按需取 push 前 tip(before)
|
||||
↓
|
||||
从 Nexus 下载 cache-schema-checker.jar
|
||||
从 Nexus 下载 serialization-schema-checker.jar
|
||||
↓
|
||||
java -jar 执行(对比 before → after,累计 diff)
|
||||
↓
|
||||
@@ -46,7 +46,7 @@ mode=block 且含任意结构变更 → exit 1(流水线失败)
|
||||
|----|------|
|
||||
| Gitea Runner | 标签 `jdk11`,已安装 Java 11 |
|
||||
| Nexus 私库 | 可访问 `http://192.168.3.25:18081/nexus/repository/maven-releases` |
|
||||
| 工具 JAR | `com.codechecker:cache-schema-checker:1.0.0` 已发布 |
|
||||
| 工具 JAR | `com.codechecker:serialization-schema-checker:1.0.0` 已发布 |
|
||||
|
||||
---
|
||||
|
||||
@@ -58,18 +58,18 @@ mode=block 且含任意结构变更 → exit 1(流水线失败)
|
||||
jnpf-java-cloud/
|
||||
├── .gitea/
|
||||
│ ├── workflows/
|
||||
│ │ └── cache-schema-check.yaml # 流水线
|
||||
│ │ └── serialization-schema-check.yaml # 流水线
|
||||
│ └── config/
|
||||
│ └── cache-schema-check-config.yaml # 检测配置
|
||||
│ └── serialization-schema-check-config.yaml # 检测配置
|
||||
```
|
||||
|
||||
请以本仓库 `.gitea/workflows/cache-schema-check.yaml` 为模板同步到业务仓。
|
||||
请以本仓库 `.gitea/workflows/serialization-schema-check.yaml` 为模板同步到业务仓。
|
||||
|
||||
---
|
||||
|
||||
## 4. 流水线模板(要点)
|
||||
|
||||
完整可运行版本见:`.gitea/workflows/cache-schema-check.yaml`。
|
||||
完整可运行版本见:`.gitea/workflows/serialization-schema-check.yaml`。
|
||||
|
||||
核心逻辑摘要:
|
||||
|
||||
@@ -84,7 +84,7 @@ git fetch --depth 1 origin "$OLD_SHA" # 优先
|
||||
# 或 git fetch --deepen N # 兜底
|
||||
|
||||
# 4) 执行
|
||||
java -jar cache-schema-checker-1.0.0.jar \
|
||||
java -jar serialization-schema-checker-1.0.0.jar \
|
||||
--old-sha "$OLD_SHA" \
|
||||
--new-sha "$NEW_SHA" \
|
||||
...
|
||||
@@ -98,7 +98,7 @@ java -jar cache-schema-checker-1.0.0.jar \
|
||||
|--------|------|------|
|
||||
| `demo.yaml` (AI代码质量分析) | AI Code Review | 并行,互不影响 |
|
||||
| `code-check` (CodeChecker) | 通用变更检测 | **同模式**,可并列执行 |
|
||||
| `cache-schema-check` | 缓存结构检测 | 新增 |
|
||||
| `serialization-schema-check` | 缓存结构检测 | 新增 |
|
||||
|
||||
|
||||
---
|
||||
@@ -116,9 +116,9 @@ mvn clean deploy -DskipTests
|
||||
发布产物:
|
||||
|
||||
```text
|
||||
com/codechecker/cache-schema-checker/1.0.0/
|
||||
├── cache-schema-checker-1.0.0.jar # 可执行 fat-jar
|
||||
└── cache-schema-checker-1.0.0.pom
|
||||
com/codechecker/serialization-schema-checker/1.0.0/
|
||||
├── serialization-schema-checker-1.0.0.jar # 可执行 fat-jar
|
||||
└── serialization-schema-checker-1.0.0.pom
|
||||
```
|
||||
|
||||
---
|
||||
@@ -157,8 +157,8 @@ com/codechecker/cache-schema-checker/1.0.0/
|
||||
OLD_SHA=$(git rev-parse origin/$(git branch --show-current)~3) # 示例:假设 ahead 3
|
||||
NEW_SHA=$(git rev-parse HEAD)
|
||||
|
||||
java -jar /path/to/cache-schema-checker-1.0.0.jar \
|
||||
--config .gitea/config/cache-schema-check-config.yaml \
|
||||
java -jar /path/to/serialization-schema-checker-1.0.0.jar \
|
||||
--config .gitea/config/serialization-schema-check-config.yaml \
|
||||
--repo-root . \
|
||||
--old-sha "$OLD_SHA" \
|
||||
--new-sha "$NEW_SHA" \
|
||||
@@ -169,3 +169,13 @@ java -jar /path/to/cache-schema-checker-1.0.0.jar \
|
||||
```
|
||||
|
||||
单 commit 自测仍可用 `--old-sha HEAD~1 --new-sha HEAD`。
|
||||
|
||||
---
|
||||
|
||||
## 10. 相关文档
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [实施方案.md](./实施方案.md) | 缓存检测总体方案 |
|
||||
| [配置说明.md](./配置说明.md) | YAML 配置项 |
|
||||
| [MQ序列化结构检测方案.md](./MQ序列化结构检测方案.md) | MQ 消息体 Schema(RocketMQ + Kafka,方案已落地) |
|
||||
|
||||
339
docs/MQ序列化结构检测方案.md
Normal file
339
docs/MQ序列化结构检测方案.md
Normal file
@@ -0,0 +1,339 @@
|
||||
# MQ 消息体序列化结构变更检测 — 方案
|
||||
|
||||
> 版本:v0.2
|
||||
> 日期:2026-07-15
|
||||
> 状态:**方案已落地(含 Kafka),开发未启动**
|
||||
> 关联:复用 `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` | 视封装而定 | **纳入** |
|
||||
| 先 `JSON.toJSONString` 再发 String | 较低 | unwrap 后取类型 |
|
||||
| 只发 `String` / `byte[]` / `MessageExt` | 有 | **默认忽略** |
|
||||
|
||||
样本(资金钱包):
|
||||
|
||||
```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) |
|
||||
| `@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」等价物(destination)
|
||||
|
||||
| 中间件 | 聚合键形态 | 来源 |
|
||||
|--------|------------|------|
|
||||
| RocketMQ | `topic:tag` | 字面量、常量、`TOPIC + ":" + TAG` |
|
||||
| Kafka | `topic` | 字面量、常量、`topicBuilder.xxx(tenantId)` → 前缀+`*` |
|
||||
|
||||
无法解析时:展示表达式 + `<font color="comment">(destination 未解析)</font>`。
|
||||
|
||||
### 2.4 读侧补强(类比 W06)
|
||||
|
||||
| 中间件 | 补强来源 |
|
||||
|--------|----------|
|
||||
| RocketMQ | `RocketMQListener<T>`、`onMessage(T)` + `@RocketMQMessageListener` |
|
||||
| Kafka | `@KafkaListener` 方法参数类型;或方法内 `parseObject/parseArray(..., Xxx.class)`(与现有 W06 共享解析能力) |
|
||||
|
||||
---
|
||||
|
||||
## 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~)+ Kafka(MQ-K*)
|
||||
+ 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 | payload 类型 |
|
||||
| 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)` / `ListenableFuture` 封装若可解析 | 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)` | 补强(可复用 W06 检测器) |
|
||||
|
||||
开关:`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` |
|
||||
|
||||
**验收**:
|
||||
|
||||
1. 删 `WalletDeductReq` 字段 → 企微出现 RocketMQ Topic 骨架变更
|
||||
2. 删 `CheckItemDetailVo` 字段 → 企微出现 Kafka Topic 骨架变更
|
||||
|
||||
### Phase M2 — 增强
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| MQ03~MQ05、MQ-K03/K04 | convertAndSend、MessageBuilder、JSON 字符串发送、ProducerRecord |
|
||||
| MQ-R01~R03 | RocketMQ Listener + Kafka `@KafkaListener` / parse 补强 |
|
||||
| List 根数组骨架 | 巡店 `List<CheckItemDetailVo>` 等 |
|
||||
|
||||
### Phase M3 — 运营
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| 积压风险提示文案 | 统一提示评估消费 lag / 积压 |
|
||||
| 更多夹具 | 值班 Kafka、中差评 Listener、IM Favorite 等 |
|
||||
| 分 webhook / 标题前缀 | 缓存 vs MQ 可选拆分 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 风险与限制
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|------|------|
|
||||
| Kafka topic 按租户动态拼接 | 归一 `prefix:*`;`manual_mappings` |
|
||||
| RocketMQ / Kafka 混用同一 VO | 各投递点独立告警(符合预期) |
|
||||
| Listener 收 String、parse 在方法深处 | MQ-R03 + 复用 W06 AST |
|
||||
| 生产/消费跨模块对不齐 | 同仓索引 + destination 对齐;失败则仅写侧 |
|
||||
| Jackson / Fastjson / Kafka JsonSerializer 细节差 | 首版字段名;必要时方言或 mapping |
|
||||
| 只改消费未改生产类型 | 不告警(工具职责是消息体 Schema) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 决策对齐
|
||||
|
||||
| 项 | 结论 |
|
||||
|----|------|
|
||||
| 中间件范围 | **RocketMQ + Kafka**(本仓已确认);Rabbit 暂不纳入 |
|
||||
| 对比区间 | `gitea.event.before` → `gitea.sha` |
|
||||
| 阻断 | 与现网 `mode` 共用 |
|
||||
| 级别 | 不引入 P0/P1/P2 产品展示 |
|
||||
| 交付 | 同一 jar;patterns 区分 Redis / MQ(含 MQ-K*) |
|
||||
|
||||
---
|
||||
|
||||
## 10. 下一步
|
||||
|
||||
1. 评审本方案(RocketMQ + Kafka 模式表)
|
||||
2. 按 **Phase M1** 开发(MQ01/02 + MQ-K01/K02)
|
||||
3. 回写 `配置说明.md` / `CI集成说明.md` 正式配置项
|
||||
4. 验收 Topic 建议:`capital-topic:WALLET_DEDUCT`(RocketMQ)、巡店/值班 Kafka topic
|
||||
76
docs/实施方案.md
76
docs/实施方案.md
@@ -1,10 +1,10 @@
|
||||
# 缓存序列化结构变更检测 — 实施方案
|
||||
# 序列化结构变更检测 — 实施方案
|
||||
|
||||
> 版本:v0.2
|
||||
> 日期:2026-07-14
|
||||
> 版本:v0.3
|
||||
> 日期:2026-07-15
|
||||
> 技术栈:Java 11 + Maven + JavaParser
|
||||
> 目标仓库:`redisCheck`(工具) / `jnpf-java-cloud`(被检测业务仓库)
|
||||
> 当前阶段:**Phase 1 + Phase 2 已完成**,Phase 3 待做
|
||||
> 目标仓库:`schemaCheck`(工具) / `jnpf-java-cloud`(被检测业务仓库)
|
||||
> 当前阶段:**Phase 1 + Phase 2 已完成**;Phase 3 运营待做;**MQ 扩展方案已文档落地**(见 `docs/MQ序列化结构检测方案.md`)
|
||||
|
||||
---
|
||||
|
||||
@@ -104,12 +104,12 @@
|
||||
schemaCheck 仓库
|
||||
├── 开发 Java 分析工具
|
||||
├── mvn package 打 fat-jar
|
||||
├── 发布到 Nexus:com.codechecker:cache-schema-checker:{version}
|
||||
├── 发布到 Nexus:com.codechecker:serialization-schema-checker:{version}
|
||||
└── 提供默认配置模板
|
||||
|
||||
jnpf-java-cloud 仓库
|
||||
├── .gitea/workflows/cache-schema-check.yaml
|
||||
├── .gitea/config/cache-schema-check-config.yaml
|
||||
├── .gitea/workflows/serialization-schema-check.yaml
|
||||
├── .gitea/config/serialization-schema-check-config.yaml
|
||||
└── push 时下载 jar 并执行检测
|
||||
```
|
||||
|
||||
@@ -119,11 +119,11 @@ jnpf-java-cloud 仓库
|
||||
flowchart TB
|
||||
subgraph Gitea["Gitea Push Pipeline"]
|
||||
A[push 事件] --> B[浅克隆 old/new 提交]
|
||||
B --> C[下载 cache-schema-checker.jar]
|
||||
B --> C[下载 serialization-schema-checker.jar]
|
||||
C --> D[java -jar 执行检测]
|
||||
end
|
||||
|
||||
subgraph Checker["cache-schema-checker (JDK 11)"]
|
||||
subgraph Checker["serialization-schema-checker (JDK 11)"]
|
||||
D --> E[GitDiffScanner]
|
||||
E --> F[RedisWritePointDetector]
|
||||
F --> G[JavaSchemaExtractor]
|
||||
@@ -175,7 +175,8 @@ schemaCheck/
|
||||
├── docs/
|
||||
│ ├── 实施方案.md
|
||||
│ ├── 配置说明.md
|
||||
│ └── CI集成说明.md
|
||||
│ ├── CI集成说明.md
|
||||
│ └── MQ序列化结构检测方案.md # MQ 消息体 Schema 扩展(方案)
|
||||
├── src/
|
||||
│ ├── main/
|
||||
│ │ ├── resources/
|
||||
@@ -195,8 +196,8 @@ schemaCheck/
|
||||
│ ├── resources/fixtures/{tenant,lock,template}/
|
||||
│ └── java/...
|
||||
├── .gitea/
|
||||
│ ├── workflows/cache-schema-check.yaml
|
||||
│ └── config/cache-schema-check-config.yaml
|
||||
│ ├── workflows/serialization-schema-check.yaml
|
||||
│ └── config/serialization-schema-check-config.yaml
|
||||
└── target/ # 构建产物
|
||||
```
|
||||
|
||||
@@ -204,11 +205,11 @@ schemaCheck/
|
||||
|
||||
```xml
|
||||
<groupId>com.codechecker</groupId>
|
||||
<artifactId>cache-schema-checker</artifactId>
|
||||
<artifactId>serialization-schema-checker</artifactId>
|
||||
<version>1.0.0</version>
|
||||
```
|
||||
|
||||
打包为 **shaded/fat jar**,主类:`com.codechecker.cache.cli.CacheSchemaCheckerMain`
|
||||
打包为 **shaded/fat jar**,主类:`com.codechecker.cache.cli.SerializationSchemaCheckerMain`
|
||||
|
||||
---
|
||||
|
||||
@@ -217,8 +218,8 @@ schemaCheck/
|
||||
### 6.1 CLI 参数
|
||||
|
||||
```bash
|
||||
java -jar cache-schema-checker-1.0.0.jar \
|
||||
--config .gitea/config/cache-schema-check-config.yaml \
|
||||
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 \
|
||||
@@ -255,7 +256,7 @@ java -jar cache-schema-checker-1.0.0.jar \
|
||||
|
||||
#### Step 1:加载配置
|
||||
|
||||
读取 `cache-schema-check-config.yaml`,合并默认值(见 `docs/配置说明.md`)。
|
||||
读取 `serialization-schema-check-config.yaml`,合并默认值(见 `docs/配置说明.md`)。
|
||||
|
||||
#### Step 2:Git Diff 扫描
|
||||
|
||||
@@ -443,15 +444,15 @@ manual_mappings:
|
||||
| 层级 | 位置 | 职责 |
|
||||
|------|------|------|
|
||||
| 默认配置 | 工具 jar 内 `default-config.yaml` | 检测模式、忽略规则等默认值 |
|
||||
| 业务覆盖 | `jnpf-java-cloud/.gitea/config/cache-schema-check-config.yaml` | mode、notify、include_modules、manual_mappings |
|
||||
| 业务覆盖 | `jnpf-java-cloud/.gitea/config/serialization-schema-check-config.yaml` | mode、notify、include_modules、manual_mappings |
|
||||
|
||||
合并规则:**业务配置覆盖默认配置**,未声明的项沿用默认值。
|
||||
|
||||
CLI 调用:
|
||||
|
||||
```bash
|
||||
java -jar cache-schema-checker.jar \
|
||||
--config .gitea/config/cache-schema-check-config.yaml \
|
||||
java -jar serialization-schema-checker.jar \
|
||||
--config .gitea/config/serialization-schema-check-config.yaml \
|
||||
...
|
||||
```
|
||||
|
||||
@@ -481,13 +482,13 @@ notify:
|
||||
详见 `docs/CI集成说明.md`。核心流程:
|
||||
|
||||
```yaml
|
||||
# jnpf-java-cloud/.gitea/workflows/cache-schema-check.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/cache-schema-check.yaml`。
|
||||
完整模板见 `docs/CI集成说明.md` / `.gitea/workflows/serialization-schema-check.yaml`。
|
||||
|
||||
---
|
||||
|
||||
@@ -534,6 +535,23 @@ notify:
|
||||
| 误报反馈 | `suppressions` 按写入点 / change_types 精细忽略 |
|
||||
| 更多业务场景覆盖 | 考勤、文件下载进度等 |
|
||||
|
||||
### Phase 4 — MQ 消息体结构检测(方案已落地,含 Kafka,开发待启)
|
||||
|
||||
业务仓同时存在:
|
||||
|
||||
- **RocketMQ**:`RocketMQTemplate.syncSend(topic:tag, dto)` / `RocketMQListener<T>`
|
||||
- **Kafka**:`KafkaTemplate.send(topic, vo|List)` / `@KafkaListener` + `parseObject`
|
||||
|
||||
消息体字段变更会导致积压旧消息反序列化失败,风险模型与 Redis 同类。
|
||||
|
||||
| 任务 | 说明 | 状态 |
|
||||
|------|------|------|
|
||||
| 方案文档 | RocketMQ + Kafka 模式、destination、复用 Schema Diff/企微 | ✅ 见专用文档 |
|
||||
| Phase M1 | RocketMQ syncSend + Kafka send + 骨架通知 + 夹具 | 待启 |
|
||||
| Phase M2/M3 | Listener/parse 补强、更多投递形态、运营 | 待启 |
|
||||
|
||||
**专用方案:** [`docs/MQ序列化结构检测方案.md`](./MQ序列化结构检测方案.md)
|
||||
|
||||
---
|
||||
|
||||
## 11. 测试策略
|
||||
@@ -582,12 +600,20 @@ notify:
|
||||
| # | 决策项 | 结论 |
|
||||
|---|--------|------|
|
||||
| 1 | 阻断范围 | `block` 模式下 **任意结构变更均阻断**(exit 1) |
|
||||
| 2 | 发布坐标 | 独立产物 `com.codechecker:cache-schema-checker:1.0.0` |
|
||||
| 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 或业务仓全量观察。
|
||||
以上决策已纳入实施方案;**Phase 1 / Phase 2 已交付**。缓存侧可进入 Phase 3;MQ 侧以 [`MQ序列化结构检测方案.md`](./MQ序列化结构检测方案.md) 为准评审后开发。
|
||||
|
||||
相关文档:
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| `docs/配置说明.md` | 缓存检测双层配置 |
|
||||
| `docs/CI集成说明.md` | 流水线 before/after、排障 |
|
||||
| `docs/MQ序列化结构检测方案.md` | MQ 消息体 Schema 监控方案(扩展) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
30
docs/配置说明.md
30
docs/配置说明.md
@@ -1,8 +1,8 @@
|
||||
# 缓存序列化结构检测 — 配置说明
|
||||
# 序列化结构检测 — 配置说明
|
||||
|
||||
> **双层配置**:工具 jar 内置 `default-config.yaml`(默认) + 业务仓库 `.gitea/config/cache-schema-check-config.yaml`(覆盖)
|
||||
> 工具坐标:`com.codechecker:cache-schema-checker:1.0.0`
|
||||
> 主类:`com.codechecker.cache.cli.CacheSchemaCheckerMain`
|
||||
> **双层配置**:工具 jar 内置 `default-config.yaml`(默认) + 业务仓库 `.gitea/config/serialization-schema-check-config.yaml`(覆盖)
|
||||
> 工具坐标:`com.codechecker:serialization-schema-checker:1.0.0`
|
||||
> 主类:`com.codechecker.cache.cli.SerializationSchemaCheckerMain`
|
||||
|
||||
---
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
```text
|
||||
jar 内 default-config.yaml(工具仓维护)
|
||||
↓ 深度合并
|
||||
业务仓 cache-schema-check-config.yaml(业务仓维护)
|
||||
业务仓 serialization-schema-check-config.yaml(业务仓维护)
|
||||
↓
|
||||
最终生效配置
|
||||
```
|
||||
@@ -23,7 +23,7 @@ jar 内 default-config.yaml(工具仓维护)
|
||||
### 1.1 业务仓最小配置示例
|
||||
|
||||
```yaml
|
||||
# jnpf-java-cloud/.gitea/config/cache-schema-check-config.yaml
|
||||
# jnpf-java-cloud/.gitea/config/serialization-schema-check-config.yaml
|
||||
enabled: true
|
||||
mode: notify
|
||||
|
||||
@@ -78,7 +78,7 @@ notify:
|
||||
# 无变更时是否也发通知(一般 false)
|
||||
notify_on_clean: false
|
||||
# 消息标题前缀
|
||||
title_prefix: "[缓存结构变更]"
|
||||
title_prefix: "[序列化结构变更]"
|
||||
|
||||
# 忽略规则
|
||||
ignore:
|
||||
@@ -182,7 +182,7 @@ mode: block
|
||||
| `webhook_url` | string | `""` | 企微机器人 Webhook 完整 URL(优先) |
|
||||
| `webhook_env` | string | — | 兼容旧字段;值为 `http` 开头时当作 URL |
|
||||
| `notify_on_clean` | boolean | false | 无变更时是否通知 |
|
||||
| `title_prefix` | string | [缓存结构变更] | 消息标题前缀 |
|
||||
| `title_prefix` | string | [序列化结构变更] | 消息标题前缀 |
|
||||
|
||||
### 3.4 ignore.key_patterns
|
||||
|
||||
@@ -299,7 +299,7 @@ suppressions:
|
||||
### 5.3 示例(已解析 key)
|
||||
|
||||
```markdown
|
||||
## [缓存结构变更] jnpf-java-cloud
|
||||
## [序列化结构变更] jnpf-java-cloud
|
||||
|
||||
> **分支**: code/redis_change_detection_v1.0
|
||||
> **提交**: cedd161c → 67c8a6eb
|
||||
@@ -342,4 +342,14 @@ notify:
|
||||
webhook_url: "" # 由流水线写入真实 Webhook
|
||||
|
||||
include_modules: [] # 全仓
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 扩展:MQ 消息体检测(方案阶段)
|
||||
|
||||
MQ(**RocketMQ + Kafka**)消息体 Schema 变更监控方案已单独成文,**实现前不要求业务仓增配**。
|
||||
|
||||
详见:[MQ序列化结构检测方案.md](./MQ序列化结构检测方案.md)
|
||||
|
||||
届时预计新增:`detection.mq_patterns`(含 `MQ01~` 与 `MQ-K01~`)、`detection.mq_read_hints_enabled`、`ignore.mq_destinations`。
|
||||
10
pom.xml
10
pom.xml
@@ -5,12 +5,12 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.codechecker</groupId>
|
||||
<artifactId>cache-schema-checker</artifactId>
|
||||
<artifactId>serialization-schema-checker</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>cache-schema-checker</name>
|
||||
<description>基于 JavaParser 的缓存 value 序列化结构变更检测器</description>
|
||||
<name>serialization-schema-checker</name>
|
||||
<description>基于 JavaParser 的缓存/MQ value 序列化结构变更检测器</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
@@ -74,7 +74,7 @@
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>cache-schema-checker-${project.version}</finalName>
|
||||
<finalName>serialization-schema-checker-${project.version}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -101,7 +101,7 @@
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.codechecker.cache.cli.CacheSchemaCheckerMain</mainClass>
|
||||
<mainClass>com.codechecker.cache.cli.SerializationSchemaCheckerMain</mainClass>
|
||||
</transformer>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
|
||||
@@ -17,11 +17,11 @@ import java.util.concurrent.Callable;
|
||||
/**
|
||||
* 命令行入口。退出码:0 通过 / 1 阻断 / 2 执行错误。
|
||||
*/
|
||||
@Command(name = "cache-schema-checker",
|
||||
@Command(name = "serialization-schema-checker",
|
||||
mixinStandardHelpOptions = true,
|
||||
version = "cache-schema-checker 1.0.0",
|
||||
description = "检测两次提交间缓存 value 序列化结构变更并通过企微机器人通知。")
|
||||
public class CacheSchemaCheckerMain implements Callable<Integer> {
|
||||
version = "serialization-schema-checker 1.0.0",
|
||||
description = "检测两次提交间缓存/MQ 等 value 序列化结构变更并通过企微机器人通知。")
|
||||
public class SerializationSchemaCheckerMain implements Callable<Integer> {
|
||||
|
||||
@Option(names = "--config", required = true, description = "业务仓库检测配置文件路径")
|
||||
private Path configPath;
|
||||
@@ -56,12 +56,12 @@ public class CacheSchemaCheckerMain implements Callable<Integer> {
|
||||
CheckerConfig config = ConfigLoader.load(configPath);
|
||||
|
||||
if (!config.isEnabled()) {
|
||||
System.out.println("[cache-schema-checker] 总开关 enabled=false,跳过检测。");
|
||||
System.out.println("[serialization-schema-checker] 总开关 enabled=false,跳过检测。");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (oldSha == null || oldSha.trim().isEmpty()) {
|
||||
System.out.println("[cache-schema-checker] 无对比基准提交,跳过检测。");
|
||||
System.out.println("[serialization-schema-checker] 无对比基准提交,跳过检测。");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ public class CacheSchemaCheckerMain implements Callable<Integer> {
|
||||
report.setRepository(repository != null ? repository : root.getFileName().toString());
|
||||
|
||||
ReportBuilder builder = new ReportBuilder(config.getNotify().getTitlePrefix());
|
||||
// CI:字段明细 + 完整企微 Markdown
|
||||
System.out.println(builder.toConsole(report));
|
||||
|
||||
boolean shouldNotify = config.getNotify().isEnabled()
|
||||
@@ -84,24 +83,24 @@ public class CacheSchemaCheckerMain implements Callable<Integer> {
|
||||
String webhook = config.getNotify().getWebhookUrl();
|
||||
List<String> messages = builder.toWeComMessages(report);
|
||||
int ok = new WeComNotifier().sendMarkdownMessages(webhook, messages);
|
||||
System.out.println("[cache-schema-checker] 企微通知发送: "
|
||||
System.out.println("[serialization-schema-checker] 企微通知发送: "
|
||||
+ ok + "/" + messages.size()
|
||||
+ (messages.size() > 1 ? "(已按 key 拆分)" : ""));
|
||||
}
|
||||
|
||||
if (report.isBlocked()) {
|
||||
System.out.println("[cache-schema-checker] block 模式命中,流水线将被阻断(exit 1)。");
|
||||
System.out.println("[serialization-schema-checker] block 模式命中,流水线将被阻断(exit 1)。");
|
||||
}
|
||||
return report.getExitCode();
|
||||
} catch (Exception e) {
|
||||
System.err.println("[cache-schema-checker] 执行错误: " + e.getMessage());
|
||||
System.err.println("[serialization-schema-checker] 执行错误: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int exitCode = new CommandLine(new CacheSchemaCheckerMain()).execute(args);
|
||||
int exitCode = new CommandLine(new SerializationSchemaCheckerMain()).execute(args);
|
||||
System.exit(exitCode);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public class CheckerConfig {
|
||||
/** 企微机器人 Webhook 完整 URL */
|
||||
private String webhookUrl = "";
|
||||
private boolean notifyOnClean = false;
|
||||
private String titlePrefix = "[缓存结构变更]";
|
||||
private String titlePrefix = "[序列化结构变更]";
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class ConfigLoader {
|
||||
n.setEnabled(bool(notify, "enabled", true));
|
||||
n.setWebhookUrl(resolveWebhookUrl(notify));
|
||||
n.setNotifyOnClean(bool(notify, "notify_on_clean", false));
|
||||
n.setTitlePrefix(str(notify, "title_prefix", "[缓存结构变更]"));
|
||||
n.setTitlePrefix(str(notify, "title_prefix", "[序列化结构变更]"));
|
||||
|
||||
Map<String, Object> ignore = asMap(map.get("ignore"));
|
||||
CheckerConfig.Ignore ig = config.getIgnore();
|
||||
|
||||
@@ -28,7 +28,7 @@ public class ReportBuilder {
|
||||
private final String titlePrefix;
|
||||
|
||||
public ReportBuilder(String titlePrefix) {
|
||||
this.titlePrefix = titlePrefix == null ? "[缓存结构变更]" : titlePrefix;
|
||||
this.titlePrefix = titlePrefix == null ? "[序列化结构变更]" : titlePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +63,7 @@ public class ReportBuilder {
|
||||
String header = buildHeader(report);
|
||||
List<String> bodies = buildKeyBodies(report);
|
||||
if (bodies.isEmpty()) {
|
||||
return Collections.singletonList(header + "未检测到缓存序列化结构变更。\n");
|
||||
return Collections.singletonList(header + "未检测到序列化结构变更。\n");
|
||||
}
|
||||
|
||||
StringBuilder combined = new StringBuilder(header);
|
||||
@@ -182,7 +182,7 @@ public class ReportBuilder {
|
||||
public String toConsole(CheckReport report) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!report.hasChanges()) {
|
||||
sb.append("未检测到缓存序列化结构变更。\n");
|
||||
sb.append("未检测到序列化结构变更。\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cache-schema-checker 内置默认配置
|
||||
# serialization-schema-checker 内置默认配置
|
||||
# 业务仓库通过 --config 指定的配置会与本文件深度合并(业务配置优先)。
|
||||
|
||||
# 总开关:false 时不执行检测、不发通知、流水线直接通过
|
||||
@@ -19,7 +19,7 @@ notify:
|
||||
enabled: true
|
||||
webhook_url: ""
|
||||
notify_on_clean: false
|
||||
title_prefix: "[缓存结构变更]"
|
||||
title_prefix: "[序列化结构变更]"
|
||||
|
||||
# 忽略规则
|
||||
ignore:
|
||||
|
||||
@@ -56,7 +56,7 @@ class ReportBuilderTest {
|
||||
key.getFieldDetails().add(addedErr);
|
||||
report.getKeyChanges().add(key);
|
||||
|
||||
String md = new ReportBuilder("[缓存结构变更]").toMarkdown(report);
|
||||
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
|
||||
|
||||
assertTrue(md.contains("- Key --> `saas:period-config:migration:current`"));
|
||||
assertFalse(md.contains("(key 无法解析)"));
|
||||
@@ -133,7 +133,7 @@ class ReportBuilderTest {
|
||||
key.getFieldDetails().add(detail);
|
||||
report.getKeyChanges().add(key);
|
||||
|
||||
String console = new ReportBuilder("[缓存结构变更]").toConsole(report);
|
||||
String console = new ReportBuilder("[序列化结构变更]").toConsole(report);
|
||||
assertTrue(console.contains("======== 字段明细 ========"));
|
||||
assertTrue(console.contains("**删除字段**: x"));
|
||||
assertTrue(console.contains("<font color=\"warning\">\"x\":\"\"</font>"));
|
||||
@@ -161,7 +161,7 @@ class ReportBuilderTest {
|
||||
key.getFieldDetails().add(added);
|
||||
report.getKeyChanges().add(key);
|
||||
|
||||
String md = new ReportBuilder("[缓存结构变更]").toMarkdown(report);
|
||||
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
|
||||
assertTrue(md.contains("- Key --> `req.getKey()` <font color=\"comment\">(key 无法解析)</font>"));
|
||||
assertTrue(md.contains("> **位置**: `ClockInXxxService#export:128`"));
|
||||
assertTrue(md.contains("> **类型**: `List<ClockInExportVo>`"));
|
||||
@@ -185,7 +185,7 @@ class ReportBuilderTest {
|
||||
report.getKeyChanges().add(simpleKey("k-a", "{\"a\":\"\"}", "{\"a\":\"\",\"x\":\"\"}", "x"));
|
||||
report.getKeyChanges().add(simpleKey("k-b", "{\"b\":\"\"}", "{\"b\":\"\",\"y\":\"\"}", "y"));
|
||||
|
||||
List<String> messages = new ReportBuilder("[缓存结构变更]").toWeComMessages(report);
|
||||
List<String> messages = new ReportBuilder("[序列化结构变更]").toWeComMessages(report);
|
||||
assertEquals(1, messages.size());
|
||||
assertTrue(messages.get(0).contains("k-a"));
|
||||
assertTrue(messages.get(0).contains("k-b"));
|
||||
@@ -200,7 +200,7 @@ class ReportBuilderTest {
|
||||
report.getKeyChanges().add(simpleKey("fat-key-1", fat, fat + "1", null));
|
||||
report.getKeyChanges().add(simpleKey("fat-key-2", fat, fat + "2", null));
|
||||
|
||||
List<String> messages = new ReportBuilder("[缓存结构变更]").toWeComMessages(report);
|
||||
List<String> messages = new ReportBuilder("[序列化结构变更]").toWeComMessages(report);
|
||||
assertEquals(2, messages.size(), "超长应按 key 拆成 2 条");
|
||||
assertTrue(messages.get(0).contains("fat-key-1"));
|
||||
assertFalse(messages.get(0).contains("fat-key-2"));
|
||||
@@ -209,10 +209,10 @@ class ReportBuilderTest {
|
||||
assertTrue(ReportBuilder.utf8Bytes(messages.get(0)) <= ReportBuilder.WECOM_MARKDOWN_MAX_BYTES);
|
||||
assertTrue(ReportBuilder.utf8Bytes(messages.get(1)) <= ReportBuilder.WECOM_MARKDOWN_MAX_BYTES);
|
||||
// 抬头在每条中重复
|
||||
assertTrue(messages.get(0).contains("## [缓存结构变更] jnpf-java-cloud"));
|
||||
assertTrue(messages.get(1).contains("## [缓存结构变更] jnpf-java-cloud"));
|
||||
assertTrue(messages.get(0).contains("## [序列化结构变更] jnpf-java-cloud"));
|
||||
assertTrue(messages.get(1).contains("## [序列化结构变更] jnpf-java-cloud"));
|
||||
|
||||
String console = new ReportBuilder("[缓存结构变更]").toConsole(report);
|
||||
String console = new ReportBuilder("[序列化结构变更]").toConsole(report);
|
||||
assertTrue(console.contains("超长已按 key 拆为 2 条"));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user