From b39fbddd8451cc38d56c983d42143c45db865c13 Mon Sep 17 00:00:00 2001 From: gongzhiyong Date: Thu, 23 Apr 2026 23:57:41 +0800 Subject: [PATCH] feat: add 5 more team assets (upstream sync, migration review, pre-commit hook) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commands: - /sync-upstream [--dry-run] — casdoor-internal upstream sync with commit classification - /check-migrations [repo|all] — Alembic/Prisma/Drizzle consistency checker (focuses on xiaoshou pending migrations) Specialist agents: - migration-reviewer — Critical/High/Low severity review for DB schema changes across all 6 repos (Alembic, Prisma, Drizzle, xorm Sync2, raw SQL) Playbooks: - playbooks/casdoor-upstream-rebase.md — quarterly upstream rebase flow with commit classification, batched merging, cross-repo JWT compat check, rollback criteria Hooks (active by default via settings.json): - .claude/hooks/pre-commit-check.sh — PreToolUse on Bash: * blocks inline secrets in command strings (10+ patterns: sk-ant-, ghp_, AKIA, PEM, etc.) * on git commit, scans staged diff for same patterns * blocks diffs > 5000 lines (override with [huge-diff-ok] in commit msg) - settings.json: wire PreToolUse hook --- .claude/agents/migration-reviewer.md | 172 +++++++++++++++++++ .claude/commands/check-migrations.md | 116 +++++++++++++ .claude/commands/sync-upstream.md | 149 ++++++++++++++++ .claude/hooks/pre-commit-check.sh | 76 +++++++++ .claude/settings.json | 13 ++ playbooks/casdoor-upstream-rebase.md | 243 +++++++++++++++++++++++++++ 6 files changed, 769 insertions(+) create mode 100644 .claude/agents/migration-reviewer.md create mode 100644 .claude/commands/check-migrations.md create mode 100644 .claude/commands/sync-upstream.md create mode 100755 .claude/hooks/pre-commit-check.sh create mode 100644 playbooks/casdoor-upstream-rebase.md diff --git a/.claude/agents/migration-reviewer.md b/.claude/agents/migration-reviewer.md new file mode 100644 index 0000000..ac63a9e --- /dev/null +++ b/.claude/agents/migration-reviewer.md @@ -0,0 +1,172 @@ +--- +name: migration-reviewer +description: 数据库 migration 深度审查专家。主 Agent 遇到 Alembic / Prisma / Drizzle / 手写 SQL 的 migration 改动时必须派给我。 +tools: Read, Bash, Grep, Glob +--- + +你是 migration 专家。涉及数据库 schema 变更的 PR / 改动必须经你审查。 + +## 你守护的 6 个仓库 + +| 仓库 | 工具 | 关键点 | +|---|---|---| +| xiaoshou | Alembic | 有"pending migrations for production"遗留;`lifecycle_stage` 字段最近加的 | +| CloudCostbrank | Alembic | 27 个 SQLAlchemy model,Celery 异步依赖 schema | +| gongdan | Prisma | Node 后端,支持 PG/MySQL/SQLite 多后端 | +| chat-gw | 手写 SQL | 3 个 seed migration 在 `db/migrations/` | +| lobechat-enterprise | Drizzle + pgvector | 向量列不可粗暴改维度 | +| casdoor-internal | xorm Sync2 | 运行时 auto-sync,没有显式 migration 文件 | + +## 审查检查清单(按严重度降序) + +### 🔴 Critical — 必须阻断 + +- [ ] **数据丢失**:`DROP COLUMN`、`DROP TABLE`、`TRUNCATE` 无显式备份方案 +- [ ] **不可逆**:`op.drop_column` 没配对的 `downgrade()` 实现 +- [ ] **长时阻塞锁**:生产大表(>10M 行)上加非 CONCURRENTLY 索引、非在线 ALTER TYPE +- [ ] **NOT NULL 加到非空表**:没配默认值 or 没 backfill 步骤 +- [ ] **外键级联 DELETE**:新建外键带 `ON DELETE CASCADE` 且上游表数据庞大 +- [ ] **重命名字段/表但代码未同步**:ORM model 还在用旧名 +- [ ] **pgvector 维度变化**(lobechat):破坏既有 embedding + +### 🟡 High — 建议阻断或要求显式说明 + +- [ ] 字段类型转换(`VARCHAR → TEXT` OK;`INT → BIGINT` 要验证下游代码;`TEXT → INT` 🔴) +- [ ] 新增 UNIQUE 约束没先检查去重 +- [ ] 新增索引但没评估写入放大 +- [ ] Alembic `autogenerate` 生成但没人工审查的 migration(看 PR 描述) +- [ ] 跨 migration 的顺序依赖(revision 链条断裂) + +### 🟢 Low — 提建议 + +- [ ] Migration 文件名不含业务语义(`add_column.py` → `add_customer_lifecycle_stage.py`) +- [ ] `downgrade()` 留空 pass(虽然常见,建议实现) +- [ ] 缺注释说明为什么加这个字段 +- [ ] 用 `op.execute("RAW SQL")` 而不是 Alembic helpers + +## 必跑验证命令 + +面对任何 migration 改动,**顺序执行**: + +### Alembic(xiaoshou / CloudCostbrank) +```bash +cd /workspace/ +# 1. 历史链条完整 +alembic history --verbose + +# 2. 能正向 apply +alembic upgrade head + +# 3. 能反向 downgrade +alembic downgrade -1 + +# 4. 再次 upgrade(验证幂等) +alembic upgrade head + +# 5. autogenerate 不再产生 diff(model 和 migration 同步) +alembic revision --autogenerate -m __final_check__ --rev-id __check__ +grep -E "op\.(add|drop|alter|create)" alembic/versions/__check___*.py +# 应无输出;然后: +rm alembic/versions/__check___*.py +``` + +### Prisma(gongdan) +```bash +cd /workspace/gongdan/ticket-system/backend +npx prisma validate +npx prisma migrate reset --skip-seed # 在测试 DB 上! +npx prisma migrate deploy +npx prisma migrate diff \ + --from-schema-datasource prisma/schema.prisma \ + --to-schema-datamodel prisma/schema.prisma +# 应为空 +``` + +### Drizzle(lobechat-enterprise) +```bash +cd /workspace/lobechat-enterprise +pnpm drizzle-kit check +pnpm drizzle-kit migrate --dry-run +``` + +## 特殊场景 + +### 场景 1:加 NOT NULL 字段到已有表 + +必须分 3 步 migration: +1. 加可空字段 + 默认值 +2. backfill(单独 SQL or 代码跑批) +3. 改为 NOT NULL + +一步到位直接 NOT NULL → 🔴 Critical 阻断。 + +### 场景 2:改字段类型 + +| 类型变化 | 风险 | +|---|---| +| VARCHAR(50) → VARCHAR(100) | 🟢 OK | +| VARCHAR → TEXT | 🟡 PG 上 ALTER 需要重写表,大表慎 | +| INT → BIGINT | 🟡 下游代码需确认(ORM 通常 OK) | +| BIGINT → INT | 🔴 可能截断数据 | +| TIMESTAMP → TIMESTAMPTZ | 🟡 时区语义变化,验证既有数据 | +| 任何类型 → ENUM | 🔴 不可逆(PG ENUM 加值可以,删值几乎不可能) | + +### 场景 3:大表加索引(> 10M 行) + +必须用: +```sql +CREATE INDEX CONCURRENTLY idx_xxx ON big_table(col); +``` + +Alembic 要用 `op.create_index(..., postgresql_concurrently=True)`。 +不加 CONCURRENTLY → 🔴 会阻塞写入几十秒甚至几分钟。 + +### 场景 4:casdoor-internal 的 xorm + +xorm 运行时 `Sync2()` 自动建表,但**不删列、不改类型**。如果改了 `object/*.go` struct 里字段: +- 加字段 → Sync2 会加 → OK +- 改 tag → Sync2 不会改已有列 → 需要手写 upgrade SQL,放 `conf/init_data.json` 或独立脚本 +- 删字段 → Sync2 不删 → 需要手写 DROP,但要确认生产不再引用 + +审查 casdoor 的 object 改动时必须问:"这个改动需要配套一个人工执行的 DDL 吗?" + +### 场景 5:chat-gw 的手写 SQL + +`db/migrations/*.sql` 3 个现有文件。新增必须: +- 有升级版本号前缀(如 `004_*.sql`) +- 显式 `IF NOT EXISTS` 做幂等 +- 配套 rollback 文件(如 `004_*_down.sql`)虽然不自动跑,但留下来给 ops + +## 输出格式 + +给主 Agent 的审查回执: + +```markdown +## Migration Review Result + +### 评级 +🔴 Critical / 🟡 High / 🟢 Low / ✅ Clean + +### 发现 +1. `alembic/versions/xxx.py:L42` — 🔴 **数据丢失**:drop_column('customers', 'legacy_status') 且 downgrade 未恢复 +2. ... + +### 必跑的验证结果 +- upgrade → downgrade → upgrade:✅ 通过 +- autogenerate 二次对比:❌ 仍有 diff(见附录) + +### 阻断结论 +- [x] 有 🔴,建议打回 +- [ ] 只有 🟡,附带修复建议可放行 +- [ ] ✅ clean,批准 + +### 给作者的修复建议 +1. ... +``` + +## 红线 + +- ❌ 不要自己跑 `alembic upgrade` 到生产数据库 +- ❌ 不要修改他人的 migration 文件(你是审查者,不是修改者) +- ❌ 不要对 🔴 问题"想办法放行",评级即结论 +- ❌ 不要接受"先合了再改"的理由 —— migration 合到 main 后很难回退 diff --git a/.claude/commands/check-migrations.md b/.claude/commands/check-migrations.md new file mode 100644 index 0000000..1f52a29 --- /dev/null +++ b/.claude/commands/check-migrations.md @@ -0,0 +1,116 @@ +--- +description: 巡检 Python 仓库的 Alembic / Node 仓库的 Prisma 的 model vs migration 一致性 +argument-hint: [repo-name | all] +--- + +检查 `$1` 的 DB migration 一致性(`$1` 为空或 `all` 则全部)。 + +## 覆盖仓库 + +| 仓库 | 工具 | Model 位置 | Migration 位置 | +|---|---|---|---| +| xiaoshou | Alembic (SQLAlchemy 2) | `app/models/*.py` | `alembic/versions/*.py` | +| CloudCostbrank | Alembic (SQLAlchemy 2) | `app/models/*.py` | `alembic/versions/*.py` | +| chat-gw | 纯 SQL migrations | `db/*.py` | `db/migrations/*.sql` (3 个) | +| gongdan | Prisma | `ticket-system/backend/prisma/schema.prisma` | `ticket-system/backend/prisma/migrations/` | +| lobechat-enterprise | Drizzle | `packages/database/schemas/*.ts` | `packages/database/migrations/` | +| casdoor-internal | xorm Sync2(无显式 migration) | `object/*.go` | 运行时 auto-sync | + +**xiaoshou 是本命令的主要目标** —— CLAUDE.md 已记录"pending migrations for production"遗留问题。 + +## 检查项目(按仓库类型) + +### Alembic 仓库(xiaoshou / CloudCostbrank) + +```bash +cd /workspace/ + +# 1. 当前数据库相比 head revision 是否落后 +alembic current +alembic heads +# 若 current != head → 有未应用的 migration + +# 2. model 相比最后一次 revision 是否有新差异 +alembic revision --autogenerate -m "__check_only__" --rev-id __temp__ +# 看生成文件是否为空: +grep -E "op\.(add_column|drop_column|alter_column|create_table|drop_table)" \ + alembic/versions/__temp___*.py +# 有操作 → model 和 migration 脱节 +# 无论结果如何,删掉这个临时文件: +rm alembic/versions/__temp___*.py +``` + +### Prisma 仓库(gongdan) + +```bash +cd /workspace/gongdan/ticket-system/backend +npx prisma validate # schema 语法 +npx prisma migrate status # 已应用 vs 待应用 +npx prisma migrate diff \ + --from-schema-datasource prisma/schema.prisma \ + --to-schema-datamodel prisma/schema.prisma +# 输出非空 → schema 和 migrations 脱节 +``` + +### Drizzle 仓库(lobechat-enterprise) + +```bash +cd /workspace/lobechat-enterprise +pnpm drizzle-kit check # schema vs migration +pnpm drizzle-kit generate --dry-run # 查看会生成什么 +``` + +### SQL 手写仓库(chat-gw) + +```bash +cd /workspace/chat-gw +ls db/migrations/ # 确认 3 个 SQL 文件都在 +# 读 db/*.py 找所有 Table 定义 +# 和 SQL 文件里的 CREATE TABLE 对比 +# (手工对比,不能 autogenerate) +``` + +### xorm 仓库(casdoor-internal) + +```bash +# xorm 靠运行时 Sync2 自动建表,没有 migration 文件 +# 只检查:object/*.go 里的 struct tag 是否一致 +grep -rE 'xorm:"pk|xorm:"unique' object/*.go | wc -l +# 产出"可能需要 schema 变更"的字段清单,不报错 +``` + +## 输出报告 + +生成 `/workspace/ai-ops/reports/migrations-$(date +%Y-%m-%d).md`: + +```markdown +# Migration 一致性报告 YYYY-MM-DD + +## 汇总 +- 健康仓库:N 个 +- 有脱节:M 个 +- 高危(涉及数据丢失风险):K 个 + +## 详情 + +### xiaoshou — ⚠️ 脱节 +- `app/models/customer.py`:L42 新增字段 `lifecycle_stage`,但 migrations/ 里找不到对应 `add_column` +- 建议:在相应仓库下跑 `alembic revision --autogenerate -m "add lifecycle_stage"`,人工审查生成文件,再 apply + +### CloudCostbrank — ✅ 健康 +- current == head +- 无 autogenerate 差异 + +### gongdan — ⚠️ 待应用 +- 有 1 个 migration 未应用到本地数据库:`20250820_add_api_keys` +- 建议:`npx prisma migrate deploy` + +... +``` + +## 红线 + +- ❌ **不要自动运行 `alembic upgrade` 或 `prisma migrate deploy`** —— 只报告,不修改数据库 +- ❌ **不要提 migration PR** —— 生成 migration 文件要人类审查(可能含不可逆 DROP) +- ❌ 不要写入到任何仓库的 `alembic/versions/` 或 `prisma/migrations/`(除了 `__check_only__` 临时文件,用完立刻删) +- ✅ 只生成报告,把决策交给人类 diff --git a/.claude/commands/sync-upstream.md b/.claude/commands/sync-upstream.md new file mode 100644 index 0000000..f38fd7a --- /dev/null +++ b/.claude/commands/sync-upstream.md @@ -0,0 +1,149 @@ +--- +description: 把 casdoor-internal 同步上游 casdoor/casdoor 的最新改动,冲突自动分析但不暴力解决 +argument-hint: [--dry-run] +--- + +把 `/workspace/casdoor-internal` 和上游 `https://github.com/casdoor/casdoor` 同步。若传 `--dry-run`,只出报告不动代码。 + +## 前置检查 + +```bash +cd /workspace/casdoor-internal +git fetch upstream main 2>&1 || { + echo "没配 upstream remote,先加:" + echo " git remote add upstream https://github.com/casdoor/casdoor.git" + exit 1 +} +``` + +如果 `.github/workflows/sync.yml` 最近跑过,优先读它的日志看它做到哪一步。 + +## 第一阶段:侦察(永远执行) + +```bash +git log --oneline ^HEAD upstream/main +``` + +对每个上游 commit 打标签: + +| 标签 | 判断规则 | 处理 | +|---|---|---| +| 🟢 safe | 纯 bugfix、只动测试、改注释/文档 | 可直接 cherry-pick | +| 🟡 careful | 碰了 `controllers/`、`object/`、`routers/`、`authz/` | 手动 merge,hunk 级审查 | +| 🔴 dangerous | 改了 `conf/app.conf`、`build.sh`、`docker-compose.yml`(我们的 skip-worktree 文件) | 手动决策,绝不粗暴 merge | +| 🔵 feature | 新文件、新 API 端点 | 直接吸收,但要跑测试 | +| ⚫ breaking | go.mod 主版本升级、数据库 schema 变更、删 API | 标记到报告,等人类决策 | + +## 第二阶段:如果 `--dry-run` + +生成 `/workspace/ai-ops/reports/casdoor-sync-$(date +%Y-%m-%d).md`: + +```markdown +# Casdoor 上游同步计划 YYYY-MM-DD + +## 上游新增 commits: N 个 +| Hash | 类别 | 描述 | 建议动作 | +|---|---|---|---| +... + +## 高风险项 +- ⚫ commit xxx 升级了 Beego v2 → v3(我们的 Makefile 锁死在 v2.3.8,拒绝) +- 🔴 commit yyy 改了 conf/app.conf(我们 skip-worktree,需要人工评估) + +## 建议 merge 顺序 +1. 先 cherry-pick 所有 🟢 +2. 再按文件分组处理 🟡 +3. 🔴/⚫ 单独列 PR 等人类决策 + +## 影响评估 +- 是否破坏 ACA 部署?<是/否 + 理由> +- 是否影响 chat-gw/xiaoshou/gongdan/lobechat 的 JWT 解析?<评估> +``` + +**到此结束,dry-run 不动代码。** + +## 第三阶段:真干(没传 --dry-run 才执行) + +只处理 🟢 + 🟡 + 🔵。🔴 和 ⚫ 永远不动。 + +### 3.1 拉分支 +```bash +git checkout -b chore/sync-upstream-$(date +%Y-%m-%d) +``` + +### 3.2 分批 merge(不要一次性 merge upstream/main) + +**批次 A:纯 bugfix(🟢)** +```bash +for hash in <🟢 列表>; do + git cherry-pick $hash || { + echo "冲突于 $hash,跳过,记录到报告" + git cherry-pick --abort + } +done +make fmt && make vet && make ut +``` + +**批次 B:feature(🔵)** +cherry-pick,每个都跑 `make ut`,失败就 revert。 + +**批次 C:careful(🟡)** +逐个 commit,逐个 hunk 审查: +- 上游改了我们动过的代码 → 保留我们的,手动吸收上游新逻辑 +- 上游重命名 → 在本地也重命名 +- 上游删除 → 只在确认本地没引用后才删 + +### 3.3 前端改动 +如果有 `web/*` 冲突: +```bash +cd web +yarn && yarn build # 必须通过 +cd .. +``` + +### 3.4 Docker 构建验证 +```bash +sh ./build.sh +make docker-build +``` + +## 第四阶段:产出 PR + +```bash +git push -u origin chore/sync-upstream-$(date +%Y-%m-%d) +gh pr create --title "chore: sync with upstream casdoor $(git rev-parse --short upstream/main)" --body-file /tmp/sync-pr-body.md +``` + +PR body 必须包含: + +```markdown +## 同步范围 +- 上游 commit 区间:.. +- 吸收 X 个 safe、Y 个 feature、Z 个 careful +- **拒绝/挂起** N 个 dangerous + breaking(见下方"需人工决策") + +## 本地验证 +- [x] `make fmt` +- [x] `make vet` +- [x] `make ut` +- [x] `yarn build` +- [x] `make docker-build` + +## 风险评估 +- JWT claim 字段:<是否变化> +- ACA 部署配置:<是否需要改 build-and-deploy.yml> +- 对下游仓库(chat-gw/xiaoshou/gongdan/lobechat)的影响:<无/需同步改动> + +## 需人工决策 +- commit xxx(Beego 升级):已挂起 +- conf/app.conf 上游改动:已挂起,等确认 Azure PG 连接逻辑是否受影响 +``` + +## 红线 + +- ❌ 禁止 `git merge -X theirs upstream/main` +- ❌ 禁止 `git merge -X ours upstream/main` +- ❌ 禁止修改 `conf/app.conf` 或其他 `skip-worktree` 文件 +- ❌ 禁止升级 `go.mod` 里 beego、xorm 主版本 +- ❌ 禁止在冲突无法判断时"猜一个",应挂起到 PR 的"需人工决策"段落 +- ✅ 任何 🔴/⚫ 改动一律转化为"PR 描述里的决策项",绝不自动执行 diff --git a/.claude/hooks/pre-commit-check.sh b/.claude/hooks/pre-commit-check.sh new file mode 100755 index 0000000..129bfd6 --- /dev/null +++ b/.claude/hooks/pre-commit-check.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Claude Code PreToolUse hook: +# - 扫描 Bash 命令字符串本身是否含疑似密钥 +# - 对 git commit,追加扫描 staged diff +# - 拒绝超大 diff(防止 Agent 一次改太多) +# 通过方式:exit 0;阻断:exit 2(Claude 会把 stderr 作为拒绝理由展示) + +set -uo pipefail + +input=$(cat) +command=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "") + +# 空命令直接放行(其他工具触发时 command 为空) +if [[ -z "$command" ]]; then + exit 0 +fi + +# 疑似密钥 / 凭证 模式 +patterns=( + 'sk-ant-[A-Za-z0-9_-]{20,}' + 'ghp_[A-Za-z0-9]{30,}' + 'github_pat_[A-Za-z0-9_]{40,}' + 'xox[pbar]-[A-Za-z0-9-]{20,}' + 'AKIA[0-9A-Z]{16}' + 'AIza[0-9A-Za-z_-]{30,}' + '-----BEGIN[[:space:]]+(RSA|OPENSSH|EC|PGP|DSA|PRIVATE)[[:space:]]+PRIVATE' + 'DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]{20,}' + 'SharedAccessKey=[A-Za-z0-9+/=]{20,}' + 'mongodb(\+srv)?://[^:]+:[^@]+@' + 'postgres(ql)?://[^:]+:[^@]+@' +) + +# 1) 检查命令字符串本身(捕获内联密钥,如 git commit -m "my key sk-ant-xxx") +for p in "${patterns[@]}"; do + if echo "$command" | grep -qE -- "$p"; then + echo "[hook-block] Bash 命令字符串中检测到疑似密钥 / 连接串。已阻断。" >&2 + echo "[hook-block] 匹配模式: $p" >&2 + exit 2 + fi +done + +# 2) 如果是 git commit,追加扫描 staged diff +if [[ "$command" =~ (^|[[:space:];&|])git[[:space:]]+commit ]]; then + # 尝试提取 "cd &&" 前缀,否则用 $PWD + cd_path=$(echo "$command" | grep -oE '^[[:space:]]*cd[[:space:]]+[^&;]+' | awk '{print $2}' | tr -d '\n' || true) + pushd_done=0 + if [[ -n "$cd_path" && -d "$cd_path" ]]; then + pushd "$cd_path" >/dev/null 2>&1 && pushd_done=1 + fi + + diff_content=$(git diff --cached 2>/dev/null || true) + + for p in "${patterns[@]}"; do + if echo "$diff_content" | grep -qE -- "$p"; then + echo "[hook-block] Staged diff 包含疑似密钥。请先 'git restore --staged ' 移除涉事文件。" >&2 + echo "[hook-block] 匹配模式: $p" >&2 + [[ $pushd_done -eq 1 ]] && popd >/dev/null 2>&1 + exit 2 + fi + done + + # 超大 diff 保护 + diff_lines=$(echo "$diff_content" | wc -l | tr -d ' ') + if [[ "$diff_lines" =~ ^[0-9]+$ ]] && [[ $diff_lines -gt 5000 ]]; then + echo "[hook-block] Staged diff 超过 5000 行 (实际 $diff_lines 行)。请拆分为更小粒度的提交。" >&2 + echo "[hook-block] 若确实是合法的大改动,绕过:在 commit message 加 '[huge-diff-ok]'" >&2 + if ! echo "$command" | grep -q '\[huge-diff-ok\]'; then + [[ $pushd_done -eq 1 ]] && popd >/dev/null 2>&1 + exit 2 + fi + fi + + [[ $pushd_done -eq 1 ]] && popd >/dev/null 2>&1 +fi + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json index 5c5751f..cad3e9e 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -97,5 +97,18 @@ "env": { "DISABLE_TELEMETRY": "0", "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "32000" + }, + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/pre-commit-check.sh" + } + ] + } + ] } } diff --git a/playbooks/casdoor-upstream-rebase.md b/playbooks/casdoor-upstream-rebase.md new file mode 100644 index 0000000..9258419 --- /dev/null +++ b/playbooks/casdoor-upstream-rebase.md @@ -0,0 +1,243 @@ +# Casdoor 上游 Rebase Playbook + +**频率:** 每季度一次,或上游发布新 release 时触发 +**负责人:** 你派给 `casdoor-specialist` 执行,人类审批 +**预期耗时:** 半天到 1 天 +**风险等级:** 🔴 高(影响全团队认证) + +--- + +## 前置条件(全部 ✅ 才能开始) + +- [ ] `casdoor-internal/` 当前 main 分支干净(`git status` 无未提交) +- [ ] 最近 3 次 CI(`.github/workflows/build.yml` + `build-and-deploy.yml`)都通过 +- [ ] 生产环境 Casdoor 最近 48 小时没有告警 +- [ ] 已通知团队"即将 rebase,请暂缓改动 casdoor-internal" +- [ ] 生产数据库已做备份(ACA PG 自动快照,确认最近 24h 有快照) + +## 阶段 1:侦察(1-2 小时) + +```bash +cd /workspace/casdoor-internal +git fetch upstream main +git log --oneline ^HEAD upstream/main | tee /tmp/upstream-commits.txt +``` + +对每个 commit 分类(见 `/sync-upstream` 命令的分类规则)。 + +**产出**:`reports/casdoor-rebase-plan-.md`,包含: +- 上游 commit 总数 +- 各等级分类统计 +- 🔴/⚫ 清单(需要决策) +- 建议执行顺序 + +**暂停点**:把报告发到团队 review,得到 **"继续 / 修改计划 / 放弃"** 的明确答复再往下走。 + +## 阶段 2:准备工作区(30 分钟) + +```bash +# 拉 rebase 分支 +git checkout -b chore/upstream-rebase-$(date +%Y%m%d) + +# 记录当前状态 +git rev-parse HEAD > /tmp/rebase-baseline-hash +git diff HEAD upstream/main --stat > /tmp/rebase-diff-stat.txt +``` + +## 阶段 3:分批 merge(核心,2-4 小时) + +### 批次 A:🟢 safe commits + +```bash +while read hash; do + git cherry-pick "$hash" || git cherry-pick --abort +done < <(grep -E "^🟢" /tmp/upstream-commits.txt | awk '{print $2}') + +# 验证 +make fmt && make vet && make ut +``` + +失败的 commit → 跳过,追加到 `/tmp/rebase-skipped.txt`,不要逼自己过。 + +### 批次 B:🔵 feature commits + +逐个 cherry-pick + 跑测试。feature 之间有依赖就按时间序。 + +### 批次 C:🟡 careful commits + +**这是最危险的批次。** 每个 commit 单独处理: + +```bash +for hash in <🟡 列表>; do + git cherry-pick "$hash" + # 冲突?进入下面流程 +done +``` + +#### 🟡 冲突处理流程 + +1. `git status` 看冲突文件列表 +2. 对每个文件: + - 读上游改动:`git show $hash:` + - 读我们版本:`git show HEAD:` + - 理解上游意图(读 commit message + diff) + - 决策三选一: + - **全接受上游**:`git checkout --theirs `(仅当我们没碰过这文件) + - **保留我们**:`git checkout --ours `(仅当上游改动与我们无关) + - **人工合并**:手动编辑,确保既有我们的改动又吸收上游新逻辑 +3. `git add ` +4. 全部解完 `git cherry-pick --continue` +5. 跑 `make fmt && make vet` 立即验证不挂 + +**绝不**粗暴 `-X theirs` 或 `-X ours` 一把梭。 + +### 批次 D:🔴 / ⚫ —— **不做** + +这批永远不在自动 rebase 范围内。PR 描述里列清楚,交给人类决策。 + +## 阶段 4:前端同步(30 分钟) + +```bash +cd web +yarn install # 拉可能的新依赖 +yarn build # 必须通过 +yarn test # 如果有测试 +cd .. +``` + +前端有冲突时参考上面"冲突处理流程"。**重点关注**: +- `web/src/App.js` 路由 +- `web/src/Setting.js` 全局配置 +- i18n 文件 `web/src/locales/*/data.json` + +## 阶段 5:全量验证(1 小时) + +```bash +# Backend +make fmt +make vet +make lint-install && golangci-lint run +make ut + +# Docker 构建 +sh ./build.sh # 本地 patch 脚本 +make docker-build + +# 集成烟测(如果本地能起完整栈) +docker compose up -d +sleep 30 +curl -f http://localhost:8000/api/get-global-providers || echo "FAIL" +curl -f http://localhost:8000/api/health || echo "FAIL" +docker compose down +``` + +## 阶段 6:跨仓库兼容性验证 + +上游改动可能影响下游 JWT 解析: + +```bash +# 1. 找到 JWT claim 结构变化 +git diff HEAD~..HEAD -- object/token.go object/application.go | grep -E "(struct|field)" + +# 2. 如果 claim 字段变了,必须同步改: +rg -l "casdoor.*jwt|jwt.*casdoor" /workspace/chat-gw/ +rg -l "casdoor.*jwt|jwt.*casdoor" /workspace/xiaoshou/ +rg -l "casdoor.*jwt|jwt.*casdoor" /workspace/gongdan/ +rg -l "casdoor.*jwt|jwt.*casdoor" /workspace/lobechat-enterprise/ +``` + +**如果发现 JWT 字段变化且下游使用:** rebase PR 必须引用配套的下游 PR。 + +## 阶段 7:出 PR + +```bash +git push -u origin chore/upstream-rebase-$(date +%Y%m%d) +``` + +PR 描述模板: + +```markdown +## 目标 +同步 upstream casdoor/casdoor 到 () + +## 执行范围 +- 上游 commit 区间:`..` 共 N 个 commit +- 吸收:X safe + Y feature + Z careful +- 挂起:K dangerous + M breaking(见"需人工决策") + +## 本地验证 +- [x] make fmt / vet / ut / golangci-lint +- [x] yarn build(web) +- [x] sh ./build.sh + make docker-build +- [x] 本地 docker compose 起栈烟测 +- [x] 跨仓库 JWT claim 兼容性扫描:<结果> + +## 对下游的影响 +- chat-gw:<影响/无影响> +- xiaoshou:<...> +- gongdan:<...> +- lobechat-enterprise:<...> +- 配套下游 PR:<链接或"N/A"> + +## 需人工决策(🔴/⚫) +- [ ] Commit abc: 上游升级 Beego 到 v3 → 我们拒绝(Makefile 锁 v2.3.8) +- [ ] Commit def: 上游改 conf/app.conf 结构 → 需确认 ACA 部署配置 + +## 跳过的 commit(冲突放弃) +- abc123:package renaming 太复杂 +- def456:删了我们重度依赖的 helper + +## 回滚方案 +基线 hash:`` +回滚命令:`git reset --hard ` + 重部署 ACA +``` + +## 阶段 8:合并后监控(24 小时) + +合并后的生产部署要监控: + +- [ ] ACA 部署成功(`az containerapp revision list`) +- [ ] Casdoor 首次启动日志无报错 +- [ ] `/api/get-global-providers` 响应正常 +- [ ] 下游 4 个仓库的 JWT 校验正常(用 chat-gw `/healthz` 做代理指标) +- [ ] 24 小时内登录成功率无异常跌落 + +**如果 24 小时内出现异常 → 立刻回滚到基线 hash**。 + +--- + +## 常见翻车场景(前人踩过的坑) + +### 场景 1:上游改了 `object/user.go` 的字段 tag +症状:Sync2 不会改已有列,新 tag 不生效 +应对:rebase PR 附带一个人工执行的 DDL SQL 给 DBA + +### 场景 2:上游新增 OAuth provider +症状:需要 secrets 配置,本地开发没配会启动失败 +应对:在 `conf/app.conf.example` 加空配置(skip-worktree 实际文件由 ops 维护) + +### 场景 3:前端路由新增 /discover 或类似 +症状:我们之前可能 de-brand 删过 +应对:检查 `web/src/App.js`,决定吸收还是保留删除 + +### 场景 4:`controllers/account.go` 上游改了登录流程 +症状:可能破坏我们与下游系统的 SSO 集成 +应对:读上游 commit 原文 + 手动测试 Casdoor → chat-gw → xiaoshou 整条链路 + +--- + +## 放弃阈值 + +遇到以下情况,立即停止本次 rebase,回 baseline: + +- 🛑 `make ut` 失败数 > 10 且 30 分钟修不好 +- 🛑 JWT claim 字段变化但下游 PR 需要 > 2 天才能出 +- 🛑 上游删除了我们系统依赖的 helper / 接口 +- 🛑 需要 Beego / xorm 主版本升级(当前不支持) + +回滚: +```bash +git checkout main +git branch -D chore/upstream-rebase-$(date +%Y%m%d) +# 写一个复盘记录到 reports/casdoor-rebase-abandoned-.md +```