Compare commits
2 Commits
6e06cbf5b5
...
96a3bd46cf
| Author | SHA1 | Date | |
|---|---|---|---|
| 96a3bd46cf | |||
| 1f119edb7c |
@@ -15,14 +15,19 @@ jobs:
|
|||||||
if: ${{ gitea.ref != 'refs/heads/pre' && gitea.ref != 'refs/heads/dev' && gitea.ref != 'refs/heads/master-2.0' }}
|
if: ${{ gitea.ref != 'refs/heads/pre' && gitea.ref != 'refs/heads/dev' && gitea.ref != 'refs/heads/master-2.0' }}
|
||||||
runs-on: jdk11
|
runs-on: jdk11
|
||||||
steps:
|
steps:
|
||||||
# 浅克隆:仅需 HEAD 与 HEAD~1,避免全量拉取
|
# 浅克隆当前 tip;对比基准用 push before(覆盖一次 push 的多 commit 累计 diff)
|
||||||
# 指定 --branch 确保非默认分支推送时也能正确检出 gitea.sha
|
# 不必全量历史:只需能 git show before / after 两边的文件内容
|
||||||
- name: 检出代码
|
- name: 检出代码
|
||||||
run: |
|
run: |
|
||||||
git config --global http.sslVerify false
|
git config --global http.sslVerify false
|
||||||
git clone --depth 2 --single-branch --branch "${{ gitea.ref_name }}" \
|
REPO_URL="https://${{ gitea.token }}@git.niujiekeji.com/${{ gitea.repository }}.git"
|
||||||
"https://${{ gitea.token }}@git.niujiekeji.com/${{ gitea.repository }}.git" .
|
BRANCH="${{ gitea.ref_name }}"
|
||||||
git checkout -B "${{ gitea.ref_name }}" "${{ gitea.sha }}"
|
NEW_SHA="${{ gitea.sha }}"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
- name: 检查配置文件
|
- name: 检查配置文件
|
||||||
run: |
|
run: |
|
||||||
@@ -74,25 +79,81 @@ jobs:
|
|||||||
echo "Java: $(java -version 2>&1 | head -1)"
|
echo "Java: $(java -version 2>&1 | head -1)"
|
||||||
|
|
||||||
- name: 执行缓存序列化结构检测
|
- name: 执行缓存序列化结构检测
|
||||||
|
env:
|
||||||
|
# push 前 tip;新分支首次 push 时为全 0。workflow_dispatch 可能为空,下方会回退。
|
||||||
|
PUSH_BEFORE: ${{ gitea.event.before }}
|
||||||
run: |
|
run: |
|
||||||
if [ -f /tmp/cache-schema-check.skip ]; then
|
if [ -f /tmp/cache-schema-check.skip ]; then
|
||||||
echo "总开关已关闭,跳过检测"
|
echo "总开关已关闭,跳过检测"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
NEW_SHA=$(cat /tmp/cache-schema-new-sha.txt)
|
||||||
|
OLD_SHA="${PUSH_BEFORE}"
|
||||||
|
|
||||||
|
# 新分支首次 push(before 全 0)→ 跳过
|
||||||
|
if [ -z "$OLD_SHA" ] || echo "$OLD_SHA" | grep -Eq '^0+$'; then
|
||||||
|
# 手动触发:无 before,回退为 HEAD~1(加深 1 层后取父提交)
|
||||||
|
if [ "${{ gitea.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
git fetch --deepen 1 || true
|
||||||
OLD_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "")
|
OLD_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "")
|
||||||
if [ -z "$OLD_SHA" ]; then
|
if [ -z "$OLD_SHA" ]; then
|
||||||
echo "首次提交,跳过检测"
|
echo "手动触发且无父提交,跳过检测"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "workflow_dispatch:无 push before,回退使用 HEAD~1=${OLD_SHA}"
|
||||||
|
else
|
||||||
|
echo "新分支首次 push(before 为空/全 0),跳过检测"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$OLD_SHA" = "$NEW_SHA" ]; then
|
||||||
|
echo "before 与 after 相同,无需检测"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMMIT_TIME=$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S')
|
# 确保 before 提交对象可读(按 SHA 浅取,无需全量历史)
|
||||||
|
ensure_commit() {
|
||||||
|
local sha="$1"
|
||||||
|
if git cat-file -e "${sha}^{commit}" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "本地缺少 ${sha},尝试按 SHA 浅取…"
|
||||||
|
if git fetch --depth 1 origin "${sha}"; then
|
||||||
|
git cat-file -e "${sha}^{commit}" 2>/dev/null && return 0
|
||||||
|
fi
|
||||||
|
# 兜底:逐步 deepen(应对部分服务端不允许直接 fetch SHA)
|
||||||
|
for d in 10 30 50 100; do
|
||||||
|
echo "deepen ${d}…"
|
||||||
|
git fetch --deepen "${d}" || true
|
||||||
|
if git cat-file -e "${sha}^{commit}" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! ensure_commit "$OLD_SHA"; then
|
||||||
|
echo "错误: 无法获取 push 前 tip ${OLD_SHA},请检查 shallow/权限"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if ! ensure_commit "$NEW_SHA"; then
|
||||||
|
echo "错误: 无法解析当前 tip ${NEW_SHA}"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "对比区间: ${OLD_SHA} → ${NEW_SHA}"
|
||||||
|
COMMIT_COUNT=$(git rev-list --count "${OLD_SHA}..${NEW_SHA}" 2>/dev/null || echo "?")
|
||||||
|
echo "本次 push 累计 commit 数(约): ${COMMIT_COUNT}"
|
||||||
|
|
||||||
|
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" \
|
java -jar "/tmp/cache-schema-checker-${CACHE_SCHEMA_CHECKER_VERSION}.jar" \
|
||||||
--config .gitea/config/cache-schema-check-config.yaml \
|
--config .gitea/config/cache-schema-check-config.yaml \
|
||||||
--repo-root . \
|
--repo-root . \
|
||||||
--old-sha "$OLD_SHA" \
|
--old-sha "$OLD_SHA" \
|
||||||
--new-sha "$(git rev-parse HEAD)" \
|
--new-sha "$NEW_SHA" \
|
||||||
--branch "${{ gitea.ref_name }}" \
|
--branch "${{ gitea.ref_name }}" \
|
||||||
--modifier "${{ gitea.actor }}" \
|
--modifier "${{ gitea.actor }}" \
|
||||||
--modify-time "$COMMIT_TIME"
|
--modify-time "$COMMIT_TIME"
|
||||||
|
|||||||
156
docs/CI集成说明.md
156
docs/CI集成说明.md
@@ -5,21 +5,38 @@
|
|||||||
## 1. 集成概览
|
## 1. 集成概览
|
||||||
|
|
||||||
```text
|
```text
|
||||||
开发者 push 代码
|
开发者 push 代码(可含多个 commit)
|
||||||
↓
|
↓
|
||||||
Gitea Actions 触发
|
Gitea Actions 触发
|
||||||
↓
|
↓
|
||||||
浅克隆业务仓库(depth=2)
|
浅克隆业务仓库 tip(depth=1)+ 按需取 push 前 tip(before)
|
||||||
↓
|
↓
|
||||||
从 Nexus 下载 cache-schema-checker.jar
|
从 Nexus 下载 cache-schema-checker.jar
|
||||||
↓
|
↓
|
||||||
java -jar 执行(对比 HEAD~1 与 HEAD)
|
java -jar 执行(对比 before → after,累计 diff)
|
||||||
↓
|
↓
|
||||||
有 P0/P1 变更 → 企微通知
|
有结构变更 → 企微通知(按 Key 骨架;删除橙/新增绿)
|
||||||
↓
|
↓
|
||||||
mode=block 且含 P0/P1/P2 任一变更 → exit 1(流水线失败)
|
mode=block 且含任意结构变更 → exit 1(流水线失败)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 1.1 对比区间(重要)
|
||||||
|
|
||||||
|
| 参数 | 取值 | 含义 |
|
||||||
|
|------|------|------|
|
||||||
|
| `--old-sha` | `gitea.event.before` | 本次 push **前**的远端 tip |
|
||||||
|
| `--new-sha` | `gitea.sha` | 本次 push **后**的 tip |
|
||||||
|
|
||||||
|
一次 push 推了多个 commit 时,只跑 **一次** 检测,覆盖整次 push 的累计代码差,**不会**因为「变更发生在中间 commit、最后一个 commit 没改相关文件」而漏检。
|
||||||
|
|
||||||
|
不需要全量历史:工作树是当前 tip;`before` 通过 `git fetch --depth 1 <sha>`(或 deepen)取到即可。
|
||||||
|
|
||||||
|
边界:
|
||||||
|
|
||||||
|
- `before` 全 `0` / 空 → 新分支首次 push,跳过
|
||||||
|
- `workflow_dispatch` 无 before → 回退 `HEAD~1`
|
||||||
|
- 中间 commit 改坏又被末 commit 改回 → 累计可能无告警(以最终结构为准)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 前置条件
|
## 2. 前置条件
|
||||||
@@ -29,7 +46,6 @@ mode=block 且含 P0/P1/P2 任一变更 → exit 1(流水线失败)
|
|||||||
| Gitea Runner | 标签 `jdk11`,已安装 Java 11 |
|
| Gitea Runner | 标签 `jdk11`,已安装 Java 11 |
|
||||||
| Nexus 私库 | 可访问 `http://192.168.3.25:18081/nexus/repository/maven-releases` |
|
| Nexus 私库 | 可访问 `http://192.168.3.25:18081/nexus/repository/maven-releases` |
|
||||||
| 工具 JAR | `com.codechecker:cache-schema-checker:1.0.0` 已发布 |
|
| 工具 JAR | `com.codechecker:cache-schema-checker:1.0.0` 已发布 |
|
||||||
| 仓库 Secret | `WECOM_ROBOT_WEBHOOK` 已配置 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,88 +62,31 @@ jnpf-java-cloud/
|
|||||||
│ └── cache-schema-check-config.yaml # 检测配置
|
│ └── cache-schema-check-config.yaml # 检测配置
|
||||||
```
|
```
|
||||||
|
|
||||||
|
请以本仓库 `.gitea/workflows/cache-schema-check.yaml` 为模板同步到业务仓。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 流水线模板
|
## 4. 流水线模板(要点)
|
||||||
|
|
||||||
```yaml
|
完整可运行版本见:`.gitea/workflows/cache-schema-check.yaml`。
|
||||||
name: 缓存序列化结构检查
|
|
||||||
run-name: ${{ gitea.actor }}的缓存结构检查
|
|
||||||
|
|
||||||
on:
|
核心逻辑摘要:
|
||||||
push:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
```bash
|
||||||
CACHE_SCHEMA_CHECKER_VERSION: "1.0.0"
|
# 1) 浅拉 tip
|
||||||
CACHE_SCHEMA_CHECKER_REPO_URL: "http://192.168.3.25:18081/nexus/repository/maven-releases"
|
git clone --depth 1 --single-branch --branch "$BRANCH" "$REPO_URL" .
|
||||||
|
git checkout -B "$BRANCH" "$NEW_SHA" # NEW_SHA = gitea.sha
|
||||||
|
|
||||||
jobs:
|
# 2) OLD_SHA = gitea.event.before(全 0 则跳过;手动触发回退 HEAD~1)
|
||||||
cache-schema-check:
|
# 3) 本地没有 OLD_SHA 时:
|
||||||
if: ${{ gitea.ref != 'refs/heads/pre' && gitea.ref != 'refs/heads/dev' && gitea.ref != 'refs/heads/master-2.0' }}
|
git fetch --depth 1 origin "$OLD_SHA" # 优先
|
||||||
runs-on: jdk11
|
# 或 git fetch --deepen N # 兜底
|
||||||
steps:
|
|
||||||
- name: 检出代码
|
|
||||||
run: |
|
|
||||||
git config --global http.sslVerify false
|
|
||||||
git clone --depth 2 --single-branch --branch "${{ gitea.ref_name }}" \
|
|
||||||
"https://${{ gitea.token }}@git.niujiekeji.com/${{ gitea.repository }}.git" .
|
|
||||||
git checkout -B "${{ gitea.ref_name }}" "${{ gitea.sha }}"
|
|
||||||
|
|
||||||
- name: 检查配置文件
|
# 4) 执行
|
||||||
run: |
|
java -jar cache-schema-checker-1.0.0.jar \
|
||||||
if [ ! -f .gitea/config/cache-schema-check-config.yaml ]; then
|
|
||||||
echo "错误: 缺少 .gitea/config/cache-schema-check-config.yaml"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: 从 Nexus 下载检测工具
|
|
||||||
run: |
|
|
||||||
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}"
|
|
||||||
JAR_PATH="/tmp/${JAR_NAME}"
|
|
||||||
|
|
||||||
echo "下载: ${JAR_URL}"
|
|
||||||
if command -v curl >/dev/null 2>&1; then
|
|
||||||
curl -fsSL -o "${JAR_PATH}" "${JAR_URL}"
|
|
||||||
elif command -v wget >/dev/null 2>&1; then
|
|
||||||
wget -q -O "${JAR_PATH}" "${JAR_URL}"
|
|
||||||
else
|
|
||||||
echo "错误: Runner 缺少 curl 或 wget"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -s "${JAR_PATH}" ]; then
|
|
||||||
echo "错误: 下载失败或文件为空"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ls -lh "${JAR_PATH}"
|
|
||||||
|
|
||||||
- name: 验证 JDK
|
|
||||||
run: java -version
|
|
||||||
|
|
||||||
- name: 执行 缓存结构检测
|
|
||||||
env:
|
|
||||||
WECOM_ROBOT_WEBHOOK: ${{ secrets.WECOM_ROBOT_WEBHOOK }}
|
|
||||||
run: |
|
|
||||||
OLD_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "")
|
|
||||||
if [ -z "$OLD_SHA" ]; then
|
|
||||||
echo "首次提交,跳过检测"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
COMMIT_TIME=$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S')
|
|
||||||
|
|
||||||
java -jar "/tmp/cache-schema-checker-${CACHE_SCHEMA_CHECKER_VERSION}.jar" \
|
|
||||||
--config .gitea/config/cache-schema-check-config.yaml \
|
|
||||||
--repo-root . \
|
|
||||||
--old-sha "$OLD_SHA" \
|
--old-sha "$OLD_SHA" \
|
||||||
--new-sha "$(git rev-parse HEAD)" \
|
--new-sha "$NEW_SHA" \
|
||||||
--branch "${{ gitea.ref_name }}" \
|
...
|
||||||
--modifier "${{ gitea.actor }}" \
|
|
||||||
--modify-time "$COMMIT_TIME"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -140,7 +99,6 @@ jobs:
|
|||||||
| `code-check` (CodeChecker) | 通用变更检测 | **同模式**,可并列执行 |
|
| `code-check` (CodeChecker) | 通用变更检测 | **同模式**,可并列执行 |
|
||||||
| `cache-schema-check` | 缓存结构检测 | 新增 |
|
| `cache-schema-check` | 缓存结构检测 | 新增 |
|
||||||
|
|
||||||
建议:三个 job 独立并行,各自 exit code 独立。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -150,8 +108,8 @@ jobs:
|
|||||||
# 在 redisCheck 仓库根目录
|
# 在 redisCheck 仓库根目录
|
||||||
mvn clean package -DskipTests
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
# 发布到 Nexus(需配置 settings.xml,并开启 deploy-nexus profile)
|
# 发布到 Nexus(需配置 settings.xml)
|
||||||
mvn clean deploy -Dnexus.deploy.enabled=true -DskipTests
|
mvn clean deploy -DskipTests
|
||||||
```
|
```
|
||||||
|
|
||||||
发布产物:
|
发布产物:
|
||||||
@@ -168,9 +126,9 @@ com/codechecker/cache-schema-checker/1.0.0/
|
|||||||
|
|
||||||
| 退出码 | 含义 | 流水线表现 |
|
| 退出码 | 含义 | 流水线表现 |
|
||||||
|--------|------|------------|
|
|--------|------|------------|
|
||||||
| 0 | 通过(含 notify 模式下的告警) | 绿色 |
|
| 0 | 通过(含 notify 模式下的告警) / 跳过 | 绿色 |
|
||||||
| 1 | 阻断(block 模式 + P0/P1/P2 任一变更) | 红色 |
|
| 1 | 阻断(block 模式) | 红色 |
|
||||||
| 2 | 执行错误(配置缺失、jar 异常等) | 红色 |
|
| 2 | 执行错误(配置缺失、无法取 before、jar 异常等) | 红色 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -178,27 +136,35 @@ com/codechecker/cache-schema-checker/1.0.0/
|
|||||||
|
|
||||||
| 现象 | 可能原因 | 处理 |
|
| 现象 | 可能原因 | 处理 |
|
||||||
|------|----------|------|
|
|------|----------|------|
|
||||||
| 首次提交跳过 | 无 HEAD~1 | 正常行为 |
|
| 新分支首次 push 跳过 | `before` 全 0 | 正常行为 |
|
||||||
|
| 无法获取 before / exit 2 | 浅克隆未取到对象、服务端禁 fetch SHA | 看日志中的 deepen;确认 Gitea 允许按 SHA fetch |
|
||||||
| 下载 jar 失败 | Nexus 地址/版本错误 | 检查 env 变量 |
|
| 下载 jar 失败 | Nexus 地址/版本错误 | 检查 env 变量 |
|
||||||
| 未收到企微 | Secret 未配 / notify.enabled=false | 检查配置 |
|
| 未收到企微 | Secret 未配 / notify.enabled=false / webhook_url 空 | 检查配置 |
|
||||||
| 大量误报 | 锁/计数器未过滤 | 补充 ignore.key_patterns |
|
| 大量误报 | 锁/计数器未过滤 | 补充 ignore.key_patterns |
|
||||||
| 漏报 | 写入模式未覆盖 | 启用 W04/W05 或补充 manual_mappings |
|
| 漏报(多 commit) | 业务仓仍用旧版 `HEAD~1` 流水线 | 同步本仓库最新 workflow(`before..after`) |
|
||||||
| 类型展开不完整 | 类型在依赖 jar 中 | 补充 manual_mappings.value_type |
|
| 漏报(模式/模块) | W0x 未开 / include_modules 过窄 | 确认 W01~W05;检查模块过滤 |
|
||||||
|
| 类型展开不完整 | 类型在依赖 jar 中 | 补充 `manual_mappings.value_type` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. 本地调试
|
## 9. 本地调试
|
||||||
|
|
||||||
|
模拟一次「多 commit push」的累计区间:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 在 jnpf-java-cloud 根目录
|
# OLD = 推送前 tip,NEW = 当前 tip(可用 origin/branch@{1} 或显式 sha)
|
||||||
|
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 \
|
java -jar /path/to/cache-schema-checker-1.0.0.jar \
|
||||||
--config .gitea/config/cache-schema-check-config.yaml \
|
--config .gitea/config/cache-schema-check-config.yaml \
|
||||||
--repo-root . \
|
--repo-root . \
|
||||||
--old-sha HEAD~1 \
|
--old-sha "$OLD_SHA" \
|
||||||
--new-sha HEAD \
|
--new-sha "$NEW_SHA" \
|
||||||
--branch $(git branch --show-current) \
|
--branch $(git branch --show-current) \
|
||||||
--modifier "$(git log -1 --format=%an)" \
|
--modifier "$(git log -1 --format=%an)" \
|
||||||
--modify-time "$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S')"
|
--modify-time "$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S')" \
|
||||||
|
--dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
可加 `--dry-run`(Phase 2 实现)仅输出报告不发企微。
|
单 commit 自测仍可用 `--old-sha HEAD~1 --new-sha HEAD`。
|
||||||
|
|||||||
216
docs/实施方案.md
216
docs/实施方案.md
@@ -1,9 +1,10 @@
|
|||||||
# 缓存序列化结构变更检测 — 实施方案
|
# 缓存序列化结构变更检测 — 实施方案
|
||||||
|
|
||||||
> 版本:v0.1
|
> 版本:v0.2
|
||||||
> 日期:2026-07-13
|
> 日期:2026-07-14
|
||||||
> 技术栈:Java 11 + Maven + JavaParser
|
> 技术栈:Java 11 + Maven + JavaParser
|
||||||
> 目标仓库:`redisCheck`(工具) / `jnpf-java-cloud`(被检测业务仓库)
|
> 目标仓库:`redisCheck`(工具) / `jnpf-java-cloud`(被检测业务仓库)
|
||||||
|
> 当前阶段:**Phase 1 + Phase 2 已完成**,Phase 3 待做
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@
|
|||||||
4. 通过企微机器人发送通知
|
4. 通过企微机器人发送通知
|
||||||
5. 通过开关控制 **仅通知** 或 **阻断流水线**
|
5. 通过开关控制 **仅通知** 或 **阻断流水线**
|
||||||
|
|
||||||
### 1.3 非目标(第一版不做)
|
### 1.3 非目标
|
||||||
|
|
||||||
- 不连接真实 Redis 实例做运行时校验
|
- 不连接真实 Redis 实例做运行时校验
|
||||||
- 不扫描 Maven 依赖 jar 中的类(仅分析业务仓库源码)
|
- 不扫描 Maven 依赖 jar 中的类(仅分析业务仓库源码)
|
||||||
@@ -71,20 +72,20 @@
|
|||||||
|
|
||||||
### 2.1 Redis 序列化方式
|
### 2.1 Redis 序列化方式
|
||||||
|
|
||||||
| 类型 | 出现频率 | 第一版策略 |
|
| 类型 | 出现频率 | 策略 |
|
||||||
|------|----------|------------|
|
|------|----------|------|
|
||||||
| `JSON.toJSONString(obj)` / `JSONObject.toJSONString(obj)` | 高 | **重点支持** |
|
| `JSON.toJSONString(obj)` / `JSONObject.toJSONString(obj)` | 高 | **已支持**(W01/W02) |
|
||||||
| `JsonUtil.getObjectToString(obj)` | 高 | **重点支持**(按 Fastjson/Jackson 默认字段规则推断) |
|
| `JsonUtil.getObjectToString(obj)` | 高 | **已支持**(W03) |
|
||||||
| `RedisTemplate.opsForValue().set(key, obj)` 直接写对象 | 中 | 第二阶段支持 |
|
| `RedisTemplate.opsForValue().set(key, obj)` 直接写对象 | 中 | **已支持**(W04) |
|
||||||
| `RedisTemplate.opsForHash().put(key, field, obj)` | 中 | 第二阶段支持 |
|
| `RedisTemplate.opsForHash().put(key, field, obj)` | 中 | **已支持**(W05) |
|
||||||
| `StringRedisTemplate` 写 JSON 字符串 | 高 | **重点支持** |
|
| `StringRedisTemplate` 写 JSON 字符串 | 高 | **已支持** |
|
||||||
| 锁 / 计数器 / token 简单值 | 高 | **默认忽略** |
|
| 锁 / 计数器 / token 简单值 | 高 | **默认忽略** |
|
||||||
|
|
||||||
### 2.2 Key 与实体映射
|
### 2.2 Key 与实体映射
|
||||||
|
|
||||||
- 不存在统一的「Key → 类型」注册中心
|
- 不存在统一的「Key → 类型」注册中心
|
||||||
- 存在大量 `static final String`、`String.format(...)`、`buildCacheKey(...)` 等模式
|
- 存在大量 `static final String`、`String.format(...)`、`buildCacheKey(...)` 等模式
|
||||||
- 第一版采用:**写入点静态推断 + 可选 YAML 人工补充映射**
|
- 因此采用:**写入点静态推断 + 可选 YAML 人工补充映射**
|
||||||
|
|
||||||
### 2.3 多模块特征
|
### 2.3 多模块特征
|
||||||
|
|
||||||
@@ -98,10 +99,9 @@
|
|||||||
|
|
||||||
### 3.1 交付形态
|
### 3.1 交付形态
|
||||||
|
|
||||||
沿用现有 `code-checker` 模式(见 `redisCheck/.gitea/demo.yaml`):
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
redisCheck 仓库
|
schemaCheck 仓库
|
||||||
├── 开发 Java 分析工具
|
├── 开发 Java 分析工具
|
||||||
├── mvn package 打 fat-jar
|
├── mvn package 打 fat-jar
|
||||||
├── 发布到 Nexus:com.codechecker:cache-schema-checker:{version}
|
├── 发布到 Nexus:com.codechecker:cache-schema-checker:{version}
|
||||||
@@ -144,7 +144,7 @@ flowchart TB
|
|||||||
2. **Diff 驱动**:只分析本次 push 变更涉及的文件及其关联类型
|
2. **Diff 驱动**:只分析本次 push 变更涉及的文件及其关联类型
|
||||||
3. **本仓限定**:类型解析仅在业务仓库 `src/main/java` 范围内
|
3. **本仓限定**:类型解析仅在业务仓库 `src/main/java` 范围内
|
||||||
4. **可配置**:忽略规则、严重级别、通知开关、阻断开关均可 YAML 配置
|
4. **可配置**:忽略规则、严重级别、通知开关、阻断开关均可 YAML 配置
|
||||||
5. **可演进**:第一版聚焦 JSON 字符串写入,后续扩展 Template 直写对象
|
5. **可演进**:已覆盖 JSON 字符串写入与 Template 直写 / Hash;后续可扩展读路径反向确认、报告落盘等
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ flowchart TB
|
|||||||
## 5. 工程结构(redisCheck 仓库)
|
## 5. 工程结构(redisCheck 仓库)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
redisCheck/
|
schemaCheck/
|
||||||
├── pom.xml # 单模块工程(无父子结构)
|
├── pom.xml # 单模块工程(无父子结构)
|
||||||
├── docs/
|
├── docs/
|
||||||
│ ├── 实施方案.md
|
│ ├── 实施方案.md
|
||||||
@@ -180,19 +180,19 @@ redisCheck/
|
|||||||
│ ├── main/
|
│ ├── main/
|
||||||
│ │ ├── resources/
|
│ │ ├── resources/
|
||||||
│ │ │ └── default-config.yaml # 内置默认配置(随 jar 发布)
|
│ │ │ └── default-config.yaml # 内置默认配置(随 jar 发布)
|
||||||
│ │ └── java/com/codechecker/redis/
|
│ │ └── java/com/codechecker/cache/
|
||||||
│ │ ├── cli/ # 命令行入口
|
│ │ ├── cli/ # 命令行入口
|
||||||
│ │ ├── config/ # 配置模型
|
│ │ ├── config/ # 配置模型
|
||||||
│ │ ├── git/ # Git 操作
|
│ │ ├── git/ # Git 操作
|
||||||
│ │ ├── analyze/ # 编排与工作树扫描
|
│ │ ├── analyze/ # 编排与工作树扫描
|
||||||
│ │ ├── detector/ # Redis 写入点检测
|
│ │ ├── detector/ # Redis 写入点检测(W01~W05)
|
||||||
│ │ ├── schema/ # Schema 提取
|
│ │ ├── schema/ # Schema 提取与注解
|
||||||
│ │ ├── diff/ # 结构对比
|
│ │ ├── diff/ # 结构对比
|
||||||
│ │ ├── key/ # Key 推断
|
│ │ ├── key/ # Key 推断
|
||||||
│ │ ├── report/ # 报告
|
│ │ ├── report/ # 报告 / 企微 Markdown
|
||||||
│ │ └── notify/ # 企微通知
|
│ │ └── notify/ # 企微通知
|
||||||
│ └── test/
|
│ └── test/
|
||||||
│ ├── resources/fixtures/tenant/
|
│ ├── resources/fixtures/{tenant,lock,template}/
|
||||||
│ └── java/...
|
│ └── java/...
|
||||||
├── .gitea/
|
├── .gitea/
|
||||||
│ ├── workflows/cache-schema-check.yaml
|
│ ├── workflows/cache-schema-check.yaml
|
||||||
@@ -205,7 +205,7 @@ redisCheck/
|
|||||||
```xml
|
```xml
|
||||||
<groupId>com.codechecker</groupId>
|
<groupId>com.codechecker</groupId>
|
||||||
<artifactId>cache-schema-checker</artifactId>
|
<artifactId>cache-schema-checker</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.0.0</version>
|
||||||
```
|
```
|
||||||
|
|
||||||
打包为 **shaded/fat jar**,主类:`com.codechecker.cache.cli.CacheSchemaCheckerMain`
|
打包为 **shaded/fat jar**,主类:`com.codechecker.cache.cli.CacheSchemaCheckerMain`
|
||||||
@@ -239,13 +239,18 @@ java -jar cache-schema-checker-1.0.0.jar \
|
|||||||
|
|
||||||
### 6.2 对比基准(old-sha)获取策略
|
### 6.2 对比基准(old-sha)获取策略
|
||||||
|
|
||||||
与 `demo.yaml` 保持一致,优先级:
|
流水线使用 **push 区间累计对比**(方案:`before` → `after`):
|
||||||
|
|
||||||
1. 流水线显式传入 `--old-sha`(通常为 `HEAD~1`)
|
1. `--new-sha` = `gitea.sha`(push 后 tip)
|
||||||
2. 若 `HEAD~1` 不存在(首次提交)→ 跳过检测,`exit 0`
|
2. `--old-sha` = `gitea.event.before`(push 前 tip)
|
||||||
3. 浅克隆 `--depth 2` 确保 `HEAD~1` 可用
|
3. `before` 为空或全 `0`(新分支首次 push)→ 跳过检测,`exit 0`
|
||||||
|
4. `workflow_dispatch` 无 before 时回退 `HEAD~1`
|
||||||
|
5. 浅克隆当前 tip(`--depth 1`),再按需 `git fetch --depth 1 <before>` / `deepen`,**无需全量历史**
|
||||||
|
|
||||||
> 不支持一次 push 多个 commit 时逐个分析;第一版仅对比 `HEAD~1..HEAD`。后续可扩展为 `before..after` 范围分析。
|
> 一次 push 含多个 commit 时,只做 **一次** 检测,对比区间为整次 push 的累计 diff(`before..after`),不会漏掉中间 commit 留下的结构变更。
|
||||||
|
> 不做「每个 commit 各告警一条」;中间引入又被末 commit 改回的净无变更,累计结果可能为「无变更」(符合阻断「最终结构」的目标)。
|
||||||
|
|
||||||
|
详见 `docs/CI集成说明.md`。
|
||||||
|
|
||||||
### 6.3 处理步骤
|
### 6.3 处理步骤
|
||||||
|
|
||||||
@@ -273,20 +278,20 @@ git diff --name-only {old-sha} {new-sha} -- '*.java'
|
|||||||
|
|
||||||
在 **变更文件** 中扫描以下 AST 模式:
|
在 **变更文件** 中扫描以下 AST 模式:
|
||||||
|
|
||||||
| 模式 ID | 匹配表达式 | 提取信息 |
|
| 模式 ID | 匹配表达式 | 提取信息 | 状态 |
|
||||||
|---------|------------|----------|
|
|---------|------------|----------|------|
|
||||||
| W01 | `redisUtil.insert(key, JSON.toJSONString(expr), ttl)` | key 表达式、value 表达式 |
|
| W01 | `redisUtil.insert(key, JSON.toJSONString(expr), ttl)` | key 表达式、value 表达式 | ✅ |
|
||||||
| W02 | `redisTemplate.opsForValue().set(key, JSON.toJSONString(expr), ...)` | 同上 |
|
| W02 | `redisTemplate.opsForValue().set(key, JSON.toJSONString(expr), ...)` | 同上 | ✅ |
|
||||||
| W03 | `stringRedisTemplate.opsForValue().set(key, JsonUtil.getObjectToString(expr), ...)` | 同上 |
|
| W03 | `stringRedisTemplate.opsForValue().set(key, JsonUtil.getObjectToString(expr), ...)` | 同上 | ✅ |
|
||||||
| W04 | `redisTemplate.opsForValue().set(key, expr, ...)` 且 expr 非字面量 | 第二阶段 |
|
| W04 | `redisTemplate.opsForValue().set(key, expr, ...)` 且 expr 非字面量 | 直写对象类型 | ✅ Phase 2 |
|
||||||
| W05 | `redisTemplate.opsForHash().put(key, field, expr)` | 第二阶段 |
|
| W05 | `redisTemplate.opsForHash().put(key, field, expr)` | Hash 写出 value 类型 | ✅ Phase 2 |
|
||||||
| W06 | `JSON.parseObject(cacheValue, Xxx.class)` | 辅助反向确认读取类型 |
|
| W06 | `JSON.parseObject(cacheValue, Xxx.class)` | 辅助反向确认读取类型 | 未做 |
|
||||||
|
|
||||||
**忽略规则**(自动):
|
**忽略规则**(自动):
|
||||||
|
|
||||||
- value 为字符串字面量、数字、`UUID`、`"1"` 等
|
- value 为字符串字面量、数字、`UUID`、`"1"` 等琐碎值
|
||||||
- 方法名含 `setIfAbsent`、`increment`、`delete`、`remove`、`expire`
|
- 方法名含 `setIfAbsent`、`increment`、`delete`、`remove`、`expire` 等
|
||||||
- key 匹配 `ignore_key_patterns` 配置
|
- key 匹配 `ignore.key_patterns` 配置(锁 / token / 登录计数等)
|
||||||
|
|
||||||
#### Step 5:类型推断
|
#### Step 5:类型推断
|
||||||
|
|
||||||
@@ -308,15 +313,16 @@ redisUtil.insert(buildCacheKey(encode), JSON.toJSONString(envelope), ttl);
|
|||||||
4. 递归展开 `TenantVO` → `dbName: String`、`linkList: List<TenantLinkModel>`
|
4. 递归展开 `TenantVO` → `dbName: String`、`linkList: List<TenantLinkModel>`
|
||||||
5. 继续展开 `TenantLinkModel` 全部字段
|
5. 继续展开 `TenantLinkModel` 全部字段
|
||||||
|
|
||||||
**注解处理**(第一版):
|
**注解处理**:
|
||||||
|
|
||||||
| 注解 | 行为 |
|
| 注解 | 行为 | 状态 |
|
||||||
|------|------|
|
|------|------|------|
|
||||||
| `@JSONField(serialize = false)` | 排除字段 |
|
| `@JSONField(serialize = false)` | 排除字段 | ✅ |
|
||||||
| `@JSONField(name = "xxx")` | 字段名映射 |
|
| `@JSONField(name = "xxx")` | 字段名映射 | ✅ |
|
||||||
| `@JsonIgnore` | 排除字段 |
|
| `@JsonIgnore` | 排除字段 | ✅ |
|
||||||
| `@JsonProperty("xxx")` | 字段名映射 |
|
| `@JsonProperty("xxx")` | 字段名映射 | ✅ |
|
||||||
| `@Schema` | 忽略(不影响序列化) |
|
| `@JsonIgnoreProperties({...})` | 类级忽略字段 | ✅ Phase 2 |
|
||||||
|
| `@Schema` | 忽略(不影响序列化) | ✅ |
|
||||||
|
|
||||||
#### Step 6:生成 JSON Schema
|
#### Step 6:生成 JSON Schema
|
||||||
|
|
||||||
@@ -367,18 +373,28 @@ redisUtil.insert(buildCacheKey(encode), JSON.toJSONString(envelope), ttl);
|
|||||||
生成 `CheckReport`,包含:
|
生成 `CheckReport`,包含:
|
||||||
|
|
||||||
- 仓库名、分支、old/new sha、提交人、时间
|
- 仓库名、分支、old/new sha、提交人、时间
|
||||||
- 变更列表(按严重级别排序)
|
- 按 Key 聚合的结构变更(骨架 before/after)+ 字段级明细
|
||||||
- 每项:Key 模式、写入位置(类#方法:行号)、旧结构、新结构、变更摘要
|
- 每项通用展示:**Key**、**位置**(`Class#method:line`)、**类型**、旧/新序列化骨架
|
||||||
|
|
||||||
调用企微 Webhook 发送 Markdown 消息。
|
企微 Markdown 规则:
|
||||||
|
|
||||||
|
- 抬头不含 mode / P0~P2 汇总;正文按 Key 展示骨架
|
||||||
|
- **删除字段**:旧骨架中橙色 `<font color="warning">`
|
||||||
|
- **新增字段**:新骨架中绿色 `<font color="info">`
|
||||||
|
- key 未解析时展示源码表达式 + 灰色「(key 未解析)」
|
||||||
|
- 单条超 4096 UTF-8 字节时按 Key 拆成多条依次发送
|
||||||
|
|
||||||
|
CI 控制台额外输出字段明细(含严重级别),再打印与企微一致的 Markdown。
|
||||||
|
|
||||||
|
调用企微 Webhook 发送 Markdown(支持 `--dry-run` 仅本地输出)。
|
||||||
|
|
||||||
#### Step 9:退出码
|
#### Step 9:退出码
|
||||||
|
|
||||||
| 条件 | 退出码 |
|
| 条件 | 退出码 |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| 无变更 / 仅 P2 | 0 |
|
| `enabled=false` / 无变更 | 0 |
|
||||||
| `mode=notify` 且存在 P0/P1 | 0(仍通知) |
|
| `mode=notify` 且存在变更 | 0(仍通知) |
|
||||||
| `mode=block` 且存在 P0/P1/P2 | 1 |
|
| `mode=block` 且存在任意结构变更 | 1 |
|
||||||
| 配置错误 / 执行异常 | 2 |
|
| 配置错误 / 执行异常 | 2 |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -442,9 +458,11 @@ mode: notify
|
|||||||
# 是否发送企微通知
|
# 是否发送企微通知
|
||||||
notify:
|
notify:
|
||||||
enabled: true
|
enabled: true
|
||||||
webhook_env: WECOM_ROBOT_WEBHOOK
|
webhook_url: "" # 企微 Webhook 完整 URL;兼容旧字段 webhook_env
|
||||||
```
|
```
|
||||||
|
|
||||||
|
企微消息约定见 `docs/配置说明.md` §5:**位置/类型**为每个 Key 的通用项;删除字段橙色、新增字段绿色。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. CI 集成方案
|
## 9. CI 集成方案
|
||||||
@@ -452,83 +470,56 @@ notify:
|
|||||||
详见 `docs/CI集成说明.md`。核心流程:
|
详见 `docs/CI集成说明.md`。核心流程:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# jnpf-java-cloud/.gitea/workflows/cache-schema-check.yaml
|
# jnpf-java-cloud/.gitea/workflows/cache-schema-check.yaml(要点)
|
||||||
name: 缓存序列化结构检查
|
# 检出:浅克隆 tip(depth 1)
|
||||||
on: [push]
|
# 检测:--old-sha = gitea.event.before,--new-sha = gitea.sha
|
||||||
|
# 按需 fetch before 提交对象,覆盖一次 push 的多 commit 累计 diff
|
||||||
jobs:
|
|
||||||
cache-schema-check:
|
|
||||||
if: ${{ gitea.ref != 'refs/heads/pre' && gitea.ref != 'refs/heads/dev' && gitea.ref != 'refs/heads/master-2.0' }}
|
|
||||||
runs-on: jdk11
|
|
||||||
steps:
|
|
||||||
- name: 检出代码
|
|
||||||
run: |
|
|
||||||
git clone --depth 2 --single-branch --branch "${{ gitea.ref_name }}" \
|
|
||||||
"https://${{ gitea.token }}@git.niujiekeji.com/${{ gitea.repository }}.git" .
|
|
||||||
git checkout -B "${{ gitea.ref_name }}" "${{ gitea.sha }}"
|
|
||||||
|
|
||||||
- name: 下载检测工具
|
|
||||||
run: |
|
|
||||||
# 从 Nexus 下载 cache-schema-checker jar
|
|
||||||
...
|
|
||||||
|
|
||||||
- name: 执行检测
|
|
||||||
env:
|
|
||||||
WECOM_ROBOT_WEBHOOK: ${{ secrets.WECOM_ROBOT_WEBHOOK }}
|
|
||||||
run: |
|
|
||||||
OLD_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "")
|
|
||||||
[ -z "$OLD_SHA" ] && exit 0
|
|
||||||
java -jar /tmp/cache-schema-checker-1.0.0.jar \
|
|
||||||
--config .gitea/config/cache-schema-check-config.yaml \
|
|
||||||
--repo-root . \
|
|
||||||
--old-sha "$OLD_SHA" \
|
|
||||||
--new-sha "$(git rev-parse HEAD)" \
|
|
||||||
--branch "${{ gitea.ref_name }}" \
|
|
||||||
--modifier "${{ gitea.actor }}" \
|
|
||||||
--modify-time "$(git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S')"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
完整模板见 `docs/CI集成说明.md` / `.gitea/workflows/cache-schema-check.yaml`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. 分阶段交付计划
|
## 10. 分阶段交付计划
|
||||||
|
|
||||||
### Phase 1 — MVP(约 1.5 周)
|
### Phase 1 — MVP(约 1.5 周)✅
|
||||||
|
|
||||||
**目标**:跑通端到端链路,覆盖租户缓存典型场景。
|
**目标**:跑通端到端链路,覆盖租户缓存典型场景。
|
||||||
|
|
||||||
| 任务 | 产出 |
|
| 任务 | 产出 | 状态 |
|
||||||
|------|------|
|
|------|------|------|
|
||||||
| Maven 工程骨架 + CLI | 可执行 fat-jar |
|
| Maven 工程骨架 + CLI | 可执行 fat-jar | ✅ |
|
||||||
| Git diff 扫描 | 变更文件列表 |
|
| Git diff 扫描 | 变更文件列表 | ✅ |
|
||||||
| W01~W03 写入点检测 | 覆盖 JSON 字符串写入 |
|
| W01~W03 写入点检测 | 覆盖 JSON 字符串写入 | ✅ |
|
||||||
| 基础 Schema 提取 | 支持普通类、内部类、List、嵌套 |
|
| 基础 Schema 提取 | 支持普通类、内部类、List、嵌套 | ✅ |
|
||||||
| Schema Diff P0/P1 | 字段增删、包装、路径迁移 |
|
| Schema Diff P0/P1 | 字段增删、包装、路径迁移 | ✅ |
|
||||||
| 企微通知 | Markdown 消息 |
|
| 企微通知 | 按 Key 骨架 Markdown | ✅ |
|
||||||
| notify/block 开关 | 配置驱动 |
|
| notify/block / enabled | 配置驱动 | ✅ |
|
||||||
| 夹具测试 | TenantVO/CacheEnvelope 样本 |
|
| 夹具测试 | TenantVO/CacheEnvelope 样本 | ✅ |
|
||||||
|
|
||||||
**验收标准**:
|
**验收标准**:
|
||||||
|
|
||||||
- 对 `TenantDbContentCacheHelper` 的结构变更能输出 P0 报告
|
- 对 `TenantDbContentCacheHelper` 的结构变更能输出报告并通知
|
||||||
- 流水线 push 后能收到企微通知
|
- 流水线 push 后能收到企微通知
|
||||||
- `mode=block` 时任意 P0/P1/P2 变更导致 exit 1
|
- `mode=block` 时任意结构变更导致 exit 1
|
||||||
|
|
||||||
### Phase 2 — 增强(约 1 周)✅
|
### Phase 2 — 增强(约 1 周)✅
|
||||||
|
|
||||||
| 任务 | 说明 | 状态 |
|
| 任务 | 说明 | 状态 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| W04/W05 模式 | RedisTemplate 直写对象、Hash 写入 | 已完成 |
|
| W04/W05 模式 | RedisTemplate 直写对象、Hash 写入 | ✅ |
|
||||||
| 注解完整支持 | Fastjson/Jackson 注解(含 `@JsonIgnoreProperties`) | 已完成 |
|
| 注解完整支持 | Fastjson/Jackson(含 `@JsonIgnoreProperties`) | ✅ |
|
||||||
| Key 推断增强 | `String.format`、常量拼接追溯、`buildXxxKey` | 已完成 |
|
| Key 推断增强 | `String.format`、常量拼接、`buildXxxKey` | ✅ |
|
||||||
| 忽略规则完善 | 锁/计数器/token/字面量/setIfAbsent 自动过滤 | 已完成 |
|
| 忽略规则完善 | 锁/计数器/token/字面量/setIfAbsent | ✅ |
|
||||||
| 多模块性能优化 | 并行读文件、索引批量装载、manual_mappings 覆盖 | 已完成 |
|
| 多模块性能 | 并行读文件、索引批量装载、`manual_mappings` | ✅ |
|
||||||
|
| 企微高亮 | 删除橙 `warning` / 新增绿 `info`;位置+类型通用项 | ✅ |
|
||||||
|
|
||||||
### Phase 3 — 运营(约 0.5 周)
|
### Phase 3 — 运营(约 0.5 周)
|
||||||
|
|
||||||
| 任务 | 说明 |
|
| 任务 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 报告落盘 | 可选输出 JSON 报告文件 |
|
| 报告落盘 | 可选输出 JSON 报告文件 |
|
||||||
| 误报反馈 | `suppressions` 配置支持按写入点忽略 |
|
| 误报反馈 | `suppressions` 按写入点 / change_types 精细忽略 |
|
||||||
| 更多业务场景覆盖 | 考勤、文件下载进度等 |
|
| 更多业务场景覆盖 | 考勤、文件下载进度等 |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -548,10 +539,9 @@ jobs:
|
|||||||
|
|
||||||
| 夹具 | 验证点 |
|
| 夹具 | 验证点 |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| `tenant-cache/` | 包装结构变更 P0 |
|
| `fixtures/tenant/` | 包装结构变更(TenantVO → CacheEnvelope) |
|
||||||
| `attendance-base-setting/` | Map 结构缓存 |
|
| `fixtures/lock/` | 锁/计数器/token 应被忽略 |
|
||||||
| `evaluate-config/` | VO 字段新增 P1 |
|
| `fixtures/template/` | W04 Template 直写 |
|
||||||
| `lock-only/` | 应被忽略 |
|
|
||||||
|
|
||||||
### 11.3 端到端测试
|
### 11.3 端到端测试
|
||||||
|
|
||||||
@@ -566,8 +556,8 @@ jobs:
|
|||||||
| 类型推断失败 | 漏报 | 标记 `LOW_CONFIDENCE`,配置 `manual_mappings` |
|
| 类型推断失败 | 漏报 | 标记 `LOW_CONFIDENCE`,配置 `manual_mappings` |
|
||||||
| Lombok 复杂注解 | 字段遗漏 | 基于源码字段 + 注解;后续 delombok |
|
| Lombok 复杂注解 | 字段遗漏 | 基于源码字段 + 注解;后续 delombok |
|
||||||
| 同一 key 多分支写不同类型 | 误报 | 报告注明置信度;人工 suppression |
|
| 同一 key 多分支写不同类型 | 误报 | 报告注明置信度;人工 suppression |
|
||||||
| 浅克隆 parent 不可用 | 跳过检测 | `--depth 2`;文档明确要求 |
|
| 浅克隆拿不到 before | 漏检 / exit 2 | 按 SHA `fetch --depth 1` + deepen 兜底;见 CI 说明 |
|
||||||
| 一次 push 多 commit | 仅检最后一个 | 文档说明;后续扩展 range |
|
| 一次 push 多 commit | 旧方案仅看末 commit 会漏检 | 已改为 `before..after` 累计对比 |
|
||||||
| 依赖 jar 中的类型 | 字段展开不完整 | 配置 `manual_mappings` 补充 |
|
| 依赖 jar 中的类型 | 字段展开不完整 | 配置 `manual_mappings` 补充 |
|
||||||
| JsonUtil 实现不可见 | 序列化规则猜测 | 默认按字段名序列化;与 Fastjson 对齐 |
|
| JsonUtil 实现不可见 | 序列化规则猜测 | 默认按字段名序列化;与 Fastjson 对齐 |
|
||||||
|
|
||||||
@@ -577,13 +567,13 @@ jobs:
|
|||||||
|
|
||||||
| # | 决策项 | 结论 |
|
| # | 决策项 | 结论 |
|
||||||
|---|--------|------|
|
|---|--------|------|
|
||||||
| 1 | 阻断范围 | `block` 模式下 **P0/P1/P2 全部阻断**(exit 1) |
|
| 1 | 阻断范围 | `block` 模式下 **任意结构变更均阻断**(exit 1) |
|
||||||
| 2 | 发布坐标 | 独立产物 `com.codechecker:cache-schema-checker:1.0.0` |
|
| 2 | 发布坐标 | 独立产物 `com.codechecker:cache-schema-checker:1.0.0` |
|
||||||
| 3 | 配置归属 | **双层配置**:jar 内 `default-config.yaml` + 业务仓覆盖合并 |
|
| 3 | 配置归属 | **双层配置**:jar 内 `default-config.yaml` + 业务仓覆盖合并 |
|
||||||
| 4 | 上线策略 | 先 `notify` 观察 **1 周**,稳定后手动切 `block` |
|
| 4 | 上线策略 | 先 `notify` 观察 **1 周**,稳定后手动切 `block` |
|
||||||
| 5 | 检测范围 | **仅 `src/main/java`**,不扫描测试代码 |
|
| 5 | 检测范围 | **仅 `src/main/java`**,不扫描测试代码 |
|
||||||
|
|
||||||
以上决策已纳入实施方案,可进入开发阶段。
|
以上决策已纳入实施方案;**Phase 1 / Phase 2 已交付**,可进入 Phase 3 或业务仓全量观察。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
137
docs/配置说明.md
137
docs/配置说明.md
@@ -1,6 +1,8 @@
|
|||||||
# 缓存序列化结构检测 — 配置说明
|
# 缓存序列化结构检测 — 配置说明
|
||||||
|
|
||||||
> **双层配置**:工具 jar 内置 `default-config.yaml`(默认) + 业务仓库 `.gitea/config/cache-schema-check-config.yaml`(覆盖)
|
> **双层配置**:工具 jar 内置 `default-config.yaml`(默认) + 业务仓库 `.gitea/config/cache-schema-check-config.yaml`(覆盖)
|
||||||
|
> 工具坐标:`com.codechecker:cache-schema-checker:1.0.0`
|
||||||
|
> 主类:`com.codechecker.cache.cli.CacheSchemaCheckerMain`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -22,23 +24,28 @@ jar 内 default-config.yaml(工具仓维护)
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# jnpf-java-cloud/.gitea/config/cache-schema-check-config.yaml
|
# jnpf-java-cloud/.gitea/config/cache-schema-check-config.yaml
|
||||||
|
enabled: true
|
||||||
mode: notify
|
mode: notify
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
enabled: true
|
enabled: true
|
||||||
webhook_env: WECOM_ROBOT_WEBHOOK
|
# 推荐直接写完整 Webhook;也可用环境变量在流水线注入
|
||||||
|
webhook_url: ""
|
||||||
|
|
||||||
include_modules:
|
include_modules:
|
||||||
- jnpf-tenant
|
- jnpf-tenant
|
||||||
```
|
```
|
||||||
|
|
||||||
|
流水线通常把 Secret 注入环境或配置文件中的 `webhook_url`。兼容旧字段:`webhook_env`(值为 `http` 开头时当作 URL 使用)。
|
||||||
|
|
||||||
### 1.2 工具内置默认配置(jar 内 default-config.yaml)
|
### 1.2 工具内置默认配置(jar 内 default-config.yaml)
|
||||||
|
|
||||||
由 `redisCheck` 仓库维护,随 jar 发布,包含:
|
由 `redisCheck` 仓库维护,随 jar 发布,默认包含:
|
||||||
|
|
||||||
- `detection.patterns`(W01~W03)
|
- `detection.patterns`:**W01~W05**(JSON 字符串写入 + Template 直写 + Hash)
|
||||||
- `ignore.key_patterns`(锁/计数器/token)
|
- `ignore.key_patterns`(锁 / 计数器 / token)
|
||||||
- `detection.min_confidence`、`max_field_depth` 等
|
- `detection.min_confidence`、`max_field_depth`
|
||||||
|
- `mode: notify`、`enabled: true`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -63,8 +70,10 @@ source_roots:
|
|||||||
# 通知配置
|
# 通知配置
|
||||||
notify:
|
notify:
|
||||||
enabled: true
|
enabled: true
|
||||||
# 从环境变量读取 Webhook URL
|
# Webhook 完整 URL(优先)
|
||||||
webhook_env: WECOM_ROBOT_WEBHOOK
|
webhook_url: ""
|
||||||
|
# 兼容旧字段:值为 http 开头时视为 URL
|
||||||
|
# webhook_env: WECOM_ROBOT_WEBHOOK
|
||||||
# 无变更时是否也发通知(一般 false)
|
# 无变更时是否也发通知(一般 false)
|
||||||
notify_on_clean: false
|
notify_on_clean: false
|
||||||
# 消息标题前缀
|
# 消息标题前缀
|
||||||
@@ -76,9 +85,9 @@ ignore:
|
|||||||
key_patterns:
|
key_patterns:
|
||||||
- "*:lock"
|
- "*:lock"
|
||||||
- "*:lock:*"
|
- "*:lock:*"
|
||||||
|
- "*lock*"
|
||||||
- "loginCount:*"
|
- "loginCount:*"
|
||||||
- "Authorization:*"
|
- "Authorization:*"
|
||||||
- "Authorization:login:session:*"
|
|
||||||
|
|
||||||
# 忽略的文件路径模式
|
# 忽略的文件路径模式
|
||||||
file_patterns:
|
file_patterns:
|
||||||
@@ -89,7 +98,7 @@ ignore:
|
|||||||
|
|
||||||
# 检测规则
|
# 检测规则
|
||||||
detection:
|
detection:
|
||||||
# 启用的写入模式
|
# 启用的写入模式(默认已全部开启)
|
||||||
patterns:
|
patterns:
|
||||||
- W01 # redisUtil.insert + JSON.toJSONString
|
- W01 # redisUtil.insert + JSON.toJSONString
|
||||||
- W02 # redisTemplate.opsForValue().set + JSON.toJSONString
|
- W02 # redisTemplate.opsForValue().set + JSON.toJSONString
|
||||||
@@ -171,7 +180,8 @@ mode: block
|
|||||||
| 字段 | 类型 | 默认值 | 说明 |
|
| 字段 | 类型 | 默认值 | 说明 |
|
||||||
|------|------|--------|------|
|
|------|------|--------|------|
|
||||||
| `enabled` | boolean | true | 是否发企微 |
|
| `enabled` | boolean | true | 是否发企微 |
|
||||||
| `webhook_env` | string | WECOM_ROBOT_WEBHOOK | 环境变量名 |
|
| `webhook_url` | string | `""` | 企微机器人 Webhook 完整 URL(优先) |
|
||||||
|
| `webhook_env` | string | — | 兼容旧字段;值为 `http` 开头时当作 URL |
|
||||||
| `notify_on_clean` | boolean | false | 无变更时是否通知 |
|
| `notify_on_clean` | boolean | false | 无变更时是否通知 |
|
||||||
| `title_prefix` | string | [缓存结构变更] | 消息标题前缀 |
|
| `title_prefix` | string | [缓存结构变更] | 消息标题前缀 |
|
||||||
|
|
||||||
@@ -182,25 +192,33 @@ mode: block
|
|||||||
- `*` 匹配单层
|
- `*` 匹配单层
|
||||||
- `**` 匹配多层
|
- `**` 匹配多层
|
||||||
|
|
||||||
常见内置忽略(代码层也有硬编码兜底):
|
内置/代码层常见过滤:
|
||||||
|
|
||||||
- 分布式锁 key
|
- 分布式锁 key(配置 glob + 方法名忽略)
|
||||||
- 登录计数
|
- 登录计数、session/token
|
||||||
- session/token
|
- 琐碎 value:字面量、`"1"`、`UUID.randomUUID()` 等
|
||||||
|
- 方法:`setIfAbsent` / `increment` / `delete` / `expire` 等
|
||||||
|
|
||||||
### 3.5 detection.patterns
|
### 3.5 detection.patterns
|
||||||
|
|
||||||
| 模式 | 说明 | 阶段 |
|
| 模式 | 说明 | 状态 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| W01 | `redisUtil.insert(key, JSON.toJSONString(x), ttl)` | Phase 1 |
|
| W01 | `redisUtil.insert(key, JSON.toJSONString(x), ttl)` | 已启用 |
|
||||||
| W02 | `redisTemplate.opsForValue().set(key, JSON.toJSONString(x), ...)` | Phase 1 |
|
| W02 | `redisTemplate.opsForValue().set(key, JSON.toJSONString(x), ...)` | 已启用 |
|
||||||
| W03 | `stringRedisTemplate.opsForValue().set(key, JsonUtil.getObjectToString(x), ...)` | Phase 1 |
|
| W03 | `stringRedisTemplate.opsForValue().set(key, JsonUtil.getObjectToString(x), ...)` | 已启用 |
|
||||||
| W04 | `redisTemplate.opsForValue().set(key, obj, ...)` | Phase 2(已启用) |
|
| W04 | `redisTemplate.opsForValue().set(key, obj, ...)` 直写对象 | 已启用(Phase 2) |
|
||||||
| W05 | `redisTemplate.opsForHash().put(key, field, obj)` | Phase 2(已启用) |
|
| W05 | `redisTemplate.opsForHash().put(key, field, obj)` | 已启用(Phase 2) |
|
||||||
|
|
||||||
|
业务仓可通过只声明子集暂时关闭某些模式,例如仅保留 JSON 写入:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
detection:
|
||||||
|
patterns: [W01, W02, W03]
|
||||||
|
```
|
||||||
|
|
||||||
### 3.6 manual_mappings
|
### 3.6 manual_mappings
|
||||||
|
|
||||||
当自动推断不准确时使用。匹配优先级 **高于** 自动推断。
|
当自动推断不准确时使用。匹配优先级 **高于** 自动推断(按 `类全名#方法名` 覆盖 key 模式与 value 类型)。
|
||||||
|
|
||||||
| 字段 | 必填 | 说明 |
|
| 字段 | 必填 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
@@ -217,59 +235,97 @@ mode: block
|
|||||||
```yaml
|
```yaml
|
||||||
suppressions:
|
suppressions:
|
||||||
- id: my-suppression
|
- id: my-suppression
|
||||||
writer_method: "com.example.FooService#cacheBar"
|
key_pattern: "file:download:user:progress:*"
|
||||||
change_types:
|
change_types:
|
||||||
- FIELD_ADDED
|
- FIELD_ADDED
|
||||||
reason: "新增字段向后兼容"
|
reason: "新增字段向后兼容"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3.8 include_modules / exclude_modules
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `include_modules` | 非空时仅扫描列出的顶层模块;空表示全仓 |
|
||||||
|
| `exclude_modules` | 始终排除的顶层模块 |
|
||||||
|
|
||||||
|
顶层模块取路径第一段,例如 `jnpf-tenant/jnpf-tenant-biz/src/main/java/...` → `jnpf-tenant`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 环境变量
|
## 4. 环境变量 / Secret
|
||||||
|
|
||||||
| 变量 | 必填 | 说明 |
|
| 变量 | 必填 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `WECOM_ROBOT_WEBHOOK` | notify.enabled=true 时必填 | 企微机器人 Webhook 完整 URL |
|
| `WECOM_ROBOT_WEBHOOK` | 视流水线写法 | 可将 Secret 写入配置中的 `webhook_url`,或在启动前注入 |
|
||||||
|
|
||||||
在 Gitea 仓库 Settings → Secrets 中配置。
|
在 Gitea 仓库 Settings → Secrets 中配置。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. 企微消息格式示例
|
## 5. 企微消息格式
|
||||||
|
|
||||||
|
### 5.1 结构说明
|
||||||
|
|
||||||
|
- 抬头:仓库、分支、提交、提交人、时间(**不再**展示 mode / P0P1P2 汇总)
|
||||||
|
- 正文:按 **一个 Redis Key 一块**,展示位置、类型与前后序列化骨架
|
||||||
|
- 超长(UTF-8 > 4096 字节)时按 key **拆成多条**消息依次发送
|
||||||
|
- CI 控制台另打「字段明细」(含 P0/P1/P2),企微侧不分级别
|
||||||
|
|
||||||
|
### 5.2 字段高亮颜色
|
||||||
|
|
||||||
|
| 变更 | 企微颜色 | Markdown |
|
||||||
|
|------|----------|----------|
|
||||||
|
| 字段删除(标在旧骨架) | 橙色 | `<font color="warning">…</font>` |
|
||||||
|
| 字段新增 / 包装层(标在新骨架) | 绿色 | `<font color="info">…</font>` |
|
||||||
|
| 路径迁移 | 旧橙 / 新绿 | 同上 |
|
||||||
|
| key 未解析提示 | 灰色 | `<font color="comment">(key 未解析)</font>` |
|
||||||
|
|
||||||
|
### 5.3 示例(已解析 key)
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## [缓存结构变更] jnpf-java-cloud
|
## [缓存结构变更] jnpf-java-cloud
|
||||||
|
|
||||||
> 分支: feature/tenant-cache
|
> **分支**: code/redis_change_detection_v1.0
|
||||||
> 提交: a1b2c3d → e4f5g6h
|
> **提交**: cedd161c → 67c8a6eb
|
||||||
> 提交人: zhangsan
|
> **提交人**: dongzi
|
||||||
> 时间: 2026-07-13 14:00:00
|
> **时间**: 2026-07-13 16:54:17
|
||||||
> 模式: notify
|
|
||||||
|
|
||||||
### P0 - 顶层结构包装变更
|
- Key --> `tenant:db:content:*`
|
||||||
- **Key**: `tenant:db:content:*`
|
> **位置**: `TenantDbContentCacheHelper#cacheSuccess:92`
|
||||||
- **位置**: `TenantDbContentCacheHelper#cacheSuccess:92`
|
> **类型**: `CacheEnvelope`
|
||||||
- **变更**:
|
> **value值由:** “{"dbName":"","linkList":[{"id":""}]}”
|
||||||
- `dbName` → `vo.dbName`(字段路径迁移)
|
> **变更为:** “…(仅新增/迁移字段片段带 <font color="info">绿色</font>)…”
|
||||||
- `linkList` → `vo.linkList`(字段路径迁移)
|
|
||||||
- 新增顶层字段 `expiresAtMs`
|
|
||||||
- **影响**: 旧缓存反序列化可能失败,需评估缓存刷新策略
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
实际发送时仅对改动属性片段染色:新增 → `info`(绿),删除 → `warning`(橙)。
|
||||||
|
|
||||||
|
### 5.4 示例(key 未解析)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- Key --> `req.getKey()` <font color="comment">(key 未解析)</font>
|
||||||
|
> **位置**: `ClockInXxxService#export:128`
|
||||||
|
> **类型**: `List<ClockInExportVo>`
|
||||||
|
> **value值由:** “{"a":""}”
|
||||||
|
> **变更为:** “…新增字段带绿色高亮…”
|
||||||
|
```
|
||||||
|
|
||||||
|
未解析时按「写入位置 + key 表达式」拆分聚合,避免多个未知 key 串在一起。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. 推荐上线配置
|
## 6. 推荐上线配置
|
||||||
|
|
||||||
### 6.1 观察期(第 1 周,已确认策略)
|
### 6.1 观察期(第 1 周)
|
||||||
|
|
||||||
业务仓默认配置:
|
业务仓默认配置:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
enabled: true
|
||||||
mode: notify
|
mode: notify
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
enabled: true
|
enabled: true
|
||||||
webhook_env: WECOM_ROBOT_WEBHOOK
|
webhook_url: "" # 由流水线写入真实 Webhook
|
||||||
|
|
||||||
include_modules:
|
include_modules:
|
||||||
- jnpf-tenant
|
- jnpf-tenant
|
||||||
@@ -285,6 +341,7 @@ include_modules: [] # 扩至全仓
|
|||||||
### 6.2 全量启用(观察期结束后)
|
### 6.2 全量启用(观察期结束后)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
enabled: true
|
||||||
mode: block
|
mode: block
|
||||||
include_modules: [] # 空表示全部模块
|
include_modules: [] # 空表示全部模块
|
||||||
detection:
|
detection:
|
||||||
|
|||||||
Reference in New Issue
Block a user