Install
openclaw skills install fightingdao-code-quality-analyzer代码质量分析技能,用于分析代码仓库的周/月维度变更,生成交付报告并同步到代码质量分析系统数据库。触发场景:(1) 用户要求进行代码质量分析 (2) 生成周报/月报 (3) 统计代码变更 (4) 分析分支提交 (5) 同步分析数据到数据库。
openclaw skills install fightingdao-code-quality-analyzer分析代码仓库的变更情况,生成结构化报告并同步到数据库。
不按这个清单执行 = 必然犯错
□ 0. ⚠️ 第一步:git fetch 拉取所有项目最新变更!!!
□ 1. 周期值:用周四日期,不是当天(例:2026-03-24周一 → 周期值 20260326)
□ 2. 分支匹配:按日期匹配分支名
□ 3. 统计逻辑:找版本分支从源分支切出后的所有提交
□ 4. ⭐ AI 质量评分:自己分析评分(同步前执行)
□ 5. ⭐ AI 代码审查:生成 code_issues 数据(同步前执行)
□ 6. 同步数据:一次性同步所有数据(分析+评分+审查+commits+统计)
□ 7. 提交类型:存储中文
□ 8. fileChanges 字段:必须填充文件变更明细(不能为空数组)
□ 9. commits 数据:必须写入 code_reviews 表
□ 10. 统计数据:必须同步 team_statistics 和 project_statistics
□ 11. 验证前端:确认正确显示(大盘、类型分布、问题明细、提交记录)
1. code_analyses - 分析记录(含 fileChanges)
2. code_issues - AI代码审查问题
3. code_reviews - 提交记录详情
4. team_statistics - 团队统计
5. project_statistics - 项目统计
| 问题 | 后果 | 解决方案 |
|---|---|---|
| fileChanges 为空 | 类型分布空 | 分析脚本必须收集 fileChangesList |
| code_issues 无数据 | 问题与建议空 | 必须执行 AI 代码审查,写入 code_issues 表 |
| code_reviews 无数据 | 提交记录详情空 | 必须同步 commits 到 code_reviews 表 |
| team_statistics 无数据 | 大盘视图为0 | 必须同步统计数据 |
| AI 评分缺失 | 评分显示默认值 | 必须更新 ai_quality_score 和 ai_quality_report |
| 维度 | 周期值 | 分支匹配 | 变更统计 |
|---|---|---|---|
| 周维度 | YYYYMMDD(周四) | 找版本分支 | 从源分支切出后的所有提交 |
| 月维度 | YYYYMM | 不需要 | 该月所有提交(所有分支) |
| 配置项 | 值 |
|---|---|
| 周期值规则 | 周维度:周四日期(YYYYMMDD);月维度:月份(YYYYMM) |
| 项目目录 | /Users/zhangdi/work/codeCap/codebase/ |
| 数据库 API | http://localhost:3000/api/v1 |
| 团队 ID | team-42e79f51(运营前端组) |
| 分析脚本 | /Users/zhangdi/work/codeCap/代码质量分析系统/scripts/analyze-code-v2.js |
| 同步脚本 | /Users/zhangdi/work/codeCap/代码质量分析系统/scripts/sync-to-db.js |
| Teams 通知脚本 | /Users/zhangdi/work/codeCap/代码质量分析系统/scripts/notify-teams.js |
| 邮件通知脚本 | /Users/zhangdi/work/codeCap/代码质量分析系统/code-quality-backend/scripts/notify-email.js |
| 邮件配置文件 | ~/.openclaw/workspace/.email-config.json |
| 机器人名称 | 质检君 |
| Teams Webhook | 加签模式,secret 已配置 |
// 1. 精确匹配:分支名包含周期值
branches.find(b => b.includes(periodValue)) // 如 feature/xxx-20260326
// 2. 范围匹配:分支名包含本周日期(周一到周五)
// 如果精确匹配失败,遍历本周日期查找
// 3. 不用 --all:只统计匹配的分支,不是所有分支
周维度:统计版本分支从源分支切出后的所有提交
月维度:统计该月该项目的所有提交(时间范围)
# 1. 首先尝试分支差异(版本分支未合并)
git shortlog -sne origin/develop..origin/feature-20260326
# 2. 如果差异为空(版本分支已合并),找版本分支的开发提交
# 找版本分支上最近一次 merge commit(从上一个版本 merge 的)
git log origin/feature --merges --format="%H" --grep="release|Merge" | head -1
# 3. 统计从那个 merge 之后到版本分支最新提交
git shortlog -sne <merge-commit>..origin/feature
不需要找目标分支!直接统计该月的所有提交。
# 统计该项目在某月的所有提交(所有分支)
git log --all --since="2026-02-01" --until="2026-03-01" --oneline
# 获取用户列表
git shortlog -sne --all --since="2026-02-01" --until="2026-03-01"
# 获取某用户的提交详情
git log --author="user@email" --all --since="2026-02-01" --until="2026-03-01" --pretty=format:"%h|%ai|%s"
日期范围:
| 月份 | 开始日期 | 结束日期 |
|---|---|---|
| 202602 | 2026-02-01 | 2026-03-01 |
| 202603 | 2026-03-01 | 2026-04-01 |
关键命令:
# 月度统计(使用 --all 包含所有分支)
git shortlog -sne --all --since="${monthStart}" --until="${monthEnd}"
// 可能的源分支优先级
const possibleSources = ['develop', 'master', 'main', 'release', 'staging'];
// 从分支名推断源分支(如 feature/xxx 从 develop 切出)
// 遍历检查哪个源分支存在 merge-base
| 英文前缀 | 中文类型 |
|---|---|
| feat | 新功能 |
| fix | Bug修复 |
| refactor | 重构 |
| style | 样式 |
| test | 测试 |
| docs | 文档 |
| chore | 杂项 |
| perf | 性能优化 |
| other/其他 | 其他 |
同步脚本示例:
function getCommitType(message) {
if (!message) return '其他';
if (message.startsWith('feat')) return '新功能';
if (message.startsWith('fix')) return 'Bug修复';
if (message.startsWith('refactor')) return '重构';
if (message.startsWith('style')) return '样式';
if (message.startsWith('test')) return '测试';
if (message.startsWith('docs')) return '文档';
if (message.startsWith('chore')) return '杂项';
if (message.startsWith('perf')) return '性能优化';
return '其他';
}
## 代码质量报告
### 总体评价:{优秀|良好|一般|较差}
### 评分:{N.N}/10
### 主要问题
1. {问题描述}
2. {问题描述}
### 改进建议
1. {建议内容}
2. {建议内容}
### 亮点
1. {亮点内容}
2. {亮点内容}
解析规则:
1. xxx 格式暂无xxxfunction extractTaskIds(message) {
const match = message.match(/\(([A-Z]+-\d+)\)/);
return match ? [match[1]] : [];
}
// 示例
// "feat(API-9933): 用户参与活动奖品查询优化" → ["API-9933"]
// "fix(JKRISK-1734): 优惠券处理" → ["JKRISK-1734"]
统计代码变更时必须排除以下文件:
| 文件类型 | 示例 |
|---|---|
| Lock 文件 | package-lock.json, yarn.lock, pnpm-lock.yaml |
| 依赖目录 | node_modules/ |
| 构建产物 | dist/, build/ |
| 压缩文件 | .min.js, .min.css |
| 类型声明 | .d.ts |
const excludePatterns = [
/package-lock\.json$/,
/yarn\.lock$/,
/pnpm-lock\.yaml$/,
/node_modules\//,
/\.min\.(js|css)$/,
/dist\//,
/build\//,
/\.d\.ts$/
];
function shouldExclude(file) {
return excludePatterns.some(p => p.test(file));
}
┌─────────────────────────────────────────────────────────────┐
│ 正确流程 │
├─────────────────────────────────────────────────────────────┤
│ 1. 用户在「小组管理」中手动添加成员 │
│ → 用户写入 users 表,关联 teamId │
│ │
│ 2. 运行分析脚本时获取用户列表 │
│ → 只统计已添加用户的提交 │
│ │
│ 3. 未匹配的提交直接跳过,不创建新用户 │
└─────────────────────────────────────────────────────────────┘
// 1. 精确匹配
if (userMap.has(gitUsername)) return userMap.get(gitUsername);
// 2. 右侧匹配(处理前缀如 j-zhaojiannan-jk → zhaojiannan-jk)
for (const [dbUsername, dbId] of userMap.entries()) {
if (gitUsername === dbUsername || gitUsername.endsWith(dbUsername)) {
return dbId;
}
}
// 3. 邮箱匹配
const emailPrefix = email.split('@')[0];
if (userMap.has(emailPrefix)) return userMap.get(emailPrefix);
AI 评分由 AI 助手自己完成,不调用外部 API!
评分标准(满分 10 分):
| 维度 | 权重 | 评估标准 |
|---|---|---|
| 代码规范 | 20% | Commit message 规范性、任务关联 |
| 可维护性 | 25% | 重构意识、代码结构优化 |
| 代码质量 | 25% | 功能实现、错误处理 |
| 提交质量 | 15% | 提交粒度、类型分布 |
| 工作量 | 15% | 代码产出量、任务完成数 |
const scores = {
'zhangdi-jk@lego-nuxt': {
score: 8.8,
evaluation: '优秀',
issues: ['提交数量较少,可以适当增加提交频率'],
suggestions: ['继续保持高质量的提交习惯'],
advantages: ['代码净增长高,有效产出多', '有 style 和 refactor 类型提交', 'Commit message 规范且详细']
}
};
function generateReport(data) {
return `## 代码质量报告
### 总体评价:${data.evaluation}
### 评分:${data.score}/10
### 主要问题
${data.issues.map((i, idx) => `${idx + 1}. ${i}`).join('\n')}
### 改进建议
${data.suggestions.map((s, idx) => `${idx + 1}. ${s}`).join('\n')}
### 亮点
${data.advantages.map((a, idx) => `${idx + 1}. ${a}`).join('\n')}`;
}
❌ 禁止清空整个数据库
✅ 只覆盖同一周期的数据
□ 1. 确认只删除本周期的数据
DELETE FROM code_analyses WHERE period_type = 'week' AND period_value = '20260326'
□ 2. 检查是否需要保留已有数据(如 AI 评分)
如果会覆盖重要数据,先备份
□ 3. 同步完成后验证历史数据完好
-- 1. 先删除关联数据
DELETE FROM code_reviews WHERE analysis_id IN (
SELECT id FROM code_analyses WHERE period_type = ? AND period_value = ?
);
-- 2. 删除分析记录
DELETE FROM code_analyses WHERE period_type = ? AND period_value = ?;
-- 3. 删除统计
DELETE FROM project_statistics WHERE period_type = ? AND period_value = ?;
DELETE FROM team_statistics WHERE period_type = ? AND period_value = ?;
-- 4. 写入新数据
INSERT INTO code_analyses ...
INSERT INTO code_reviews ...
INSERT INTO project_statistics ...
INSERT INTO team_statistics ...
后果:分支列表过时,找不到正确的版本分支。
解决:分析脚本第一步必须是 git fetch。
后果:之前手动设置的评分被重置为默认值。
解决:同步前检查是否需要保留已有数据,必要时先备份。
后果:前端显示类型不一致(有的显示中文,有的显示英文)。
解决:同步脚本必须返回中文类型。
后果:找不到正确的版本分支。
解决:周期值永远是那周的周四日期。
后果:已合并分支的数据丢失。
解决:使用 develop^1 或时间范围作为备用。
后果:产生垃圾数据,用户名可能匹配错误。
解决:用户必须在小组管理中预先添加。
后果:API Key 未配置时返回默认值。
解决:AI 评分由 AI 助手自己完成,不调用外部 API。
后果:前端解析错误或显示异常。
解决:严格按照本文档的数据格式约定执行。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| user_id | UUID | 用户 ID |
| project_id | UUID | 项目 ID |
| period_type | string | week / month |
| period_value | string | 周期值(YYYYMMDD 或 YYYYMM) |
| commit_count | int | 提交数 |
| insertions | int | 新增行 |
| deletions | int | 删除行 |
| files_changed | int | 变更文件数 |
| task_count | int | 任务数 |
| branch | string | 分支名 |
| ai_quality_score | float | AI 评分 |
| ai_quality_report | text | AI 报告 |
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| analysis_id | UUID | 关联的分析 ID |
| commit_hash | string | 提交哈希 |
| commit_message | text | 提交信息 |
| commit_date | timestamp | 提交时间 |
| review_result | JSON | { insertions, deletions, type } |
| 字段 | 类型 | 说明 |
|---|---|---|
| team_id | UUID | 团队 ID |
| period_type | string | week / month |
| period_value | string | 周期值 |
| total_members | int | 总成员数 |
| active_members | int | 活跃成员数 |
| total_commits | int | 总提交数 |
| avg_quality_score | float | 平均质量分 |
| 字段 | 类型 | 说明 |
|---|---|---|
| project_id | UUID | 项目 ID |
| period_type | string | week / month |
| period_value | string | 周期值 |
| total_contributors | int | 贡献者数 |
| total_commits | int | 总提交数 |
| avg_quality_score | float | 平均质量分 |
1. 执行检查清单(逐项确认)
↓
2. git fetch 拉取所有项目最新变更
↓
3. 遍历项目,查找匹配的版本分支
↓
4. 找到源分支(develop/master)
↓
5. 获取分支差异(使用 develop^1 备用)
↓
6. 统计每条提交:
- 增删行数(排除 lock 文件)
- 提交类型(存中文)
- 任务号
↓
7. 按用户+项目聚合
↓
8. 保存分析结果到文件
↓
9. 同步数据到数据库:
- 检查是否需要保留已有数据
- 只删除本周期的旧数据
- 写入新数据
↓
10. AI 质量评分(自己完成)
↓
11. ⭐ 代码逐行审查(自动切换到 kimi-k2.5 模型)
↓
12. 存储审查结果到数据库
↓
13. 验证历史数据完好
↓
14. 验证前端显示正确
在代码质量分析的评审环节,自动切换到 kimi-k2.5 模型 进行详细的代码逐行审查。
| 模型 | 适合场景 |
|---|---|
| glm-5(默认) | 日常对话、数据同步、简单分析 |
| kimi-k2.5 | 长文本理解、代码深度分析、复杂推理 |
kimi-k2.5 拥有 262k 上下文窗口,适合处理大量代码变更的详细审查。
// 在代码审查环节,自动 spawn 子任务
sessions_spawn({
model: "bailian/kimi-k2.5",
task: `对 ${projectName} 项目的本周代码变更进行逐行审查。
审查范围:${branchName}
变更文件:${fileList}
变更行数:+${insertions}/-${deletions}
请按照以下维度进行审查:
1. Security - 安全性问题
2. Performance - 性能问题
3. Correctness - 正确性问题
4. Maintainability - 可维护性问题
5. Testing - 测试覆盖
输出格式:
| 文件 | 行号 | 问题类型 | 严重程度 | 描述 | 建议 |
`,
cwd: projectPath
})
审查完成后,将结果存入 code_issues 表:
INSERT INTO code_issues (
analysis_id, file_path, line_start, line_end,
issue_type, severity, description, suggestion
) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
主任务(glm-5):
└── 分析代码变更
└── 同步数据到数据库
└── 评分
└── 🔄 spawn 子任务(kimi-k2.5)
└── 获取 git diff
└── 逐行审查代码
└── 识别问题
└── 返回结构化结果
└── 接收审查结果
└── 存入数据库
| 文件 | 路径 |
|---|---|
| 分析脚本 | /Users/zhangdi/work/codeCap/代码质量分析系统/scripts/analyze-code-v2.js |
| 同步脚本 | /Users/zhangdi/work/codeCap/代码质量分析系统/scripts/sync-to-db.js |
| 通知脚本 | /Users/zhangdi/work/codeCap/代码质量分析系统/scripts/notify-teams.js |
| 分析输出 | /Users/zhangdi/work/codeCap/代码质量分析系统/analysis-output/analysis-YYYYMMDD.json |
| 后端服务 | /Users/zhangdi/work/codeCap/代码质量分析系统/code-quality-backend/ |
| 前端服务 | /Users/zhangdi/work/codeCap/代码质量分析系统/code-quality-frontend/ |
当 feature 分支已合并到 develop 时,git log develop..feature 可能返回空,但这并不意味着没有数据。
# 这条命令不管 feature 是否合并都能正确工作
git log origin/feature --not origin/develop --oneline
# 或者更完整的写法
git log origin/develop..origin/feature --oneline # feature 未合并时
git log origin/feature --not origin/develop --oneline # 任何情况都适用
--not origin/develop 排除所有在 develop 上的提交代码审查由 AI 助手直接完成,不需要调用外部 AI API。
1. 获取变更文件列表
git diff origin/develop..origin/feature --name-only
2. 获取具体代码变更
git diff origin/develop..origin/feature -- "path/to/file.tsx"
3. AI 审查代码变更,识别问题:
- 安全性问题
- 性能问题
- 代码正确性
- 可维护性
- 测试覆盖
4. 生成结构化问题明细
5. 存入数据库 / 展示给用户
-- 新增问题明细表
CREATE TABLE code_issues (
id UUID PRIMARY KEY,
analysis_id UUID REFERENCES code_analyses(id),
file_path VARCHAR(500),
line_number INT,
issue_type VARCHAR(50),
severity VARCHAR(20), -- P0/P1/P2
description TEXT,
suggestion TEXT,
code_snippet TEXT,
created_at TIMESTAMP
);
## 🔍 项目名 本周代码审查报告
### 概览
| 文件类型 | 文件数 | 变更行数 |
|---------|--------|---------|
| 新增文件 | N | +XXX |
| 修改文件 | N | +XXX/-XXX |
| **总计** | **N** | **+XXX/-XXX** |
### 主要变更模块
| 模块 | 变更量 | 描述 |
|------|--------|------|
| 模块名 | +XXX | 描述 |
---
### 问题明细
#### 文件 1: `path/to/file.tsx` (+XXX/-XXX)
| # | 行号 | 问题类型 | 严重程度 | 问题描述 | 修改建议 |
|---|------|---------|---------|---------|---------|
| 1 | 行号 | 问题类型 | P0/P1/P2 | 问题描述 | 修改建议 |
**具体代码示例**:
```typescript
// 当前代码
问题代码
// 建议修改
修改后代码
| 问题类型 | 出现次数 | 涉及文件 |
|---|---|---|
| 问题类型 | N | 文件列表 |
| 亮点 | 描述 |
|---|---|
| ✅ 亮点 | 描述 |
| 优先级 | 问题 | 理由 |
|---|---|---|
| P0 | 问题 | 理由 |
| P1 | 问题 | 理由 |
| P2 | 问题 | 理由 |
---
## 十四、结合 code-review 技能
### 审查维度
参考 `skills/code-review/SKILL.md`,按以下维度系统化审查:
| 维度 | 检查项 | 优先级 |
|------|--------|--------|
| Security | SQL注入、XSS、CSRF、认证授权 | Critical |
| Performance | N+1查询、内存泄漏、懒加载 | High |
| Correctness | 空值处理、边界情况、竞态条件 | High |
| Maintainability | 命名、单一职责、DRY | Medium |
| Testing | 测试覆盖、边界测试 | Medium |
### 严重程度标签
| 级别 | 标签 | 含义 | 阻塞合并 |
|------|------|------|---------|
| P0 | 阻塞 | 严重问题,必须修复 | 是 |
| P1 | 阻塞 | 重要问题,应该修复 | 是 |
| P2 | 不阻塞 | 改进建议 | 否 |
### 三遍审查法
| 遍数 | 重点 | 时间 |
|------|------|------|
| 第一遍 | 高层结构、架构设计 | 2-5 分钟 |
| 第二遍 | 逐行细节、问题识别 | 主要时间 |
| 第三遍 | 边界情况、遗漏检查 | 5 分钟 |
---
## 十五、审查示例
### lego-nuxt 本周审查报告示例
**审查范围**: `feature/lego-nuxt4.12.0_20260326`
| 文件 | 变更行数 | 问题数 |
|------|---------|--------|
| ComponentsManager.vue | +679/-1 | 4 |
| activity/index.vue | +466/-1 | 3 |
| mobile/Mixin/index.js | +54 | 2 |
**问题示例**:
| # | 行号 | 问题类型 | 严重程度 | 问题描述 | 修改建议 |
|---|------|---------|---------|---------|---------|
| 1 | 29-42 | 代码重复 | P2 | 双击编辑逻辑重复绑定 | 提取为独立组件 |
| 2 | 97-100 | 无障碍问题 | P1 | 统计图标缺少 aria-label | 添加无障碍属性 |
| 3 | 245-248 | 未使用导入 | P2 | 引入了 echarts 但未检查加载 | 添加错误处理 |
### dove_digital 本周审查报告示例
**审查范围**: `feature/iopsfe2.67.0-20260326` (40 files, +8,122/-1,551)
**问题示例**:
| # | 行号 | 问题类型 | 严重程度 | 问题描述 | 修改建议 |
|---|------|---------|---------|---------|---------|
| 1 | 全文 | 代码可维护性 | P0 | 单文件 2000+ 行 | 拆分为多个子组件 |
| 2 | 多处 | 硬编码常量 | P2 | AI 模型类型硬编码 | 提取为配置文件 |
| 3 | 39 | 错误处理不完整 | P1 | catch 只显示 message | 添加 error 状态 |
| 4 | 68 | JSON 解析错误 | P1 | JSON.parse 无错误处理 | 添加 try-catch |
---
## 十六、⚠️⚠️⚠️ 错误案例库(必读!防止重复犯错)
### 案例 1:fileChanges 字段名不一致(2026-03-28)
**错误现象**:个人代码评审详情页,文件变更明细列表文件名为空
**错误原因**:
- 分析脚本生成 `file` 字段
- 历史数据使用 `path` 字段
- 前端使用 `file.path` 读取
**正确做法**:
```javascript
// ❌ 错误:使用 file 字段
fileChangesList.push({ file, insertions, deletions });
// ✅ 正确:使用 path 字段,与历史数据保持一致
fileChangesList.push({ path: file, language: 'Vue', insertions, deletions });
教训:修改数据结构前,必须先检查历史数据的字段结构!
错误现象:提交记录详情为空
错误原因:同步数据时只写入了 code_analyses,忘记写入 code_reviews
正确做法:
// 同步流程必须包含:
// 1. code_analyses(分析记录)
// 2. code_issues(AI代码审查问题)
// 3. code_reviews(提交记录详情)⚠️ 容易遗漏!
// 4. team_statistics(团队统计)
// 5. project_statistics(项目统计)
教训:每次同步必须按照完整的数据同步顺序执行,不能跳过任何一张表!
错误现象:大盘视图数据全部为 0
错误原因:没有同步 team_statistics 和 project_statistics 表
正确做法:
-- 同步完成后必须执行:
INSERT INTO team_statistics (team_id, period_type, period_value, total_members, ...)
INSERT INTO project_statistics (project_id, period_type, period_value, total_contributors, ...)
教训:大盘视图依赖统计表,必须同步!
错误现象:AI评分显示默认值,问题与建议为空
错误原因:
正确做法:
完整流程:
1. git fetch
2. 运行分析脚本
3. ⭐ AI 评分(不能遗漏!)
4. ⭐ AI 代码审查(不能遗漏!)
5. 同步数据(一次性同步所有)
6. 验证前端显示
教训:AI 评分和 AI 代码审查是分析任务的一部分,必须在同步前完成!
错误现象:运营前端组的用户数据丢失(如 houfeng-jk)
错误原因:
正确做法:
// 只获取 team_id 匹配的用户
const teamUsers = await prisma.user.findMany({
where: { teamId: TEAM_ID }, // ⚠️ 必须过滤 team_id
select: { id: true, username: true }
});
const userMap = new Map();
for (const u of teamUsers) {
userMap.set(u.username, u.id);
}
教训:用户匹配时必须使用 team_id 过滤,避免匹配到重复的用户记录!
错误现象:同步了不该同步的用户数据(如 liuxin9-jk, gaohui-jwk 等不在运营前端组)
错误原因:
正确做法:
// 同步前必须检查用户是否在运营前端组
for (const analysis of analysisData.analyses) {
const username = analysis.user.username;
const userId = userMap.get(username); // userMap 只包含运营前端组用户
// 只保留运营前端组的用户
if (!userId) {
console.log('跳过(不在运营前端组):', username);
continue;
}
// ... 写入数据
}
教训:SKILL.md 明确规定"用户必须在小组管理中预先添加",同步时必须过滤!
错误现象:修改前端代码后,历史周期数据显示异常
错误原因:
file 字段file.file || file.path正确做法:
❌ 不要修改前端代码来适配新字段结构
✅ 应该让新数据的字段结构和历史数据保持一致
教训:修改数据结构时,优先修改数据源,而不是修改前端代码!
错误现象:同一个用户名在 users 表中有多条记录
根本原因:之前的同步脚本可能创建了重复用户
修复 SQL:
-- 删除没有 team_id 的重复用户
DELETE FROM users
WHERE team_id IS NULL
AND username IN (SELECT username FROM users WHERE team_id IS NOT NULL);
教训:定期检查数据质量,清理重复记录!
错误现象:大盘视图"AI分析平均代码质量"显示为 0
根本原因分析:
┌─────────────────────────────────────────────────────────────┐
│ 数据流向 │
├─────────────────────────────────────────────────────────────┤
│ 前端 Dashboard → GET /dashboard/overview │
│ ↓ │
│ 后端 DashboardService.getOverview() │
│ ↓ │
│ 查询 team_statistics 表 │
│ ↓ │
│ 返回 avg_quality_score 字段 │
│ │
│ ⚠️ 问题:team_statistics.avg_quality_score 是 null! │
└─────────────────────────────────────────────────────────────┘
深层原因:
team_statistics 表读取平均质量分,不是从 code_analyses 表聚合code_analyses.ai_quality_scoreteam_statistics.avg_quality_score错误代码示例:
// ❌ 只更新 code_analyses
await prisma.codeAnalysis.update({
where: { id },
data: { aiQualityScore: score }
});
// ⚠️ 遗漏:没有更新 team_statistics.avg_quality_score
正确做法:
// ✅ 同步完成后,计算团队平均质量分并更新 team_statistics
const analyses = await prisma.codeAnalysis.findMany({
where: {
periodType: 'week',
periodValue: '20260402',
user: { teamId: TEAM_ID }
},
select: { aiQualityScore: true }
});
const scores = analyses
.filter(a => a.aiQualityScore !== null)
.map(a => Number(a.aiQualityScore));
const avgScore = scores.length > 0
? Math.round((scores.reduce((a, b) => a + b, 0) / scores.length) * 100) / 100
: null;
await prisma.teamStatistic.updateMany({
where: {
teamId: TEAM_ID,
periodType: 'week',
periodValue: '20260402'
},
data: { avgQualityScore: avgScore }
});
教训:
code_analyses 是个体数据,team_statistics 是聚合数据错误现象:dove_digital 项目的代码审查问题,明明是 zhangdi-jk 提交的,却统计到 lizhuotian-jk 身上
根本原因分析:
┌─────────────────────────────────────────────────────────────┐
│ 数据结构 │
├─────────────────────────────────────────────────────────────┤
│ code_issues 表 │
│ ├── analysis_id → 关联到 code_analyses │
│ ├── committer_name → "zhangdi-jk"(实际提交者) │
│ └─────────────────────────────────────────────────────────┘│
│ │
│ code_analyses 表 │
│ ├── user_id → 关联到 users │
│ ├── 项目 + 周期 + 用户 │
│ └─────────────────────────────────────────────────────────┘│
│ │
│ ⚠️ 问题:analysis_id 关联到了错误的分析记录! │
│ - code_issues.committer_name = "zhangdi-jk" │
│ - code_analyses.user_id = "lizhuotian-jk" (错误!) │
└─────────────────────────────────────────────────────────────┘
后端统计逻辑:
-- 后端按 ca.user_id 分组统计问题
SELECT ca.user_id, COUNT(*) as issue_count
FROM code_issues ci
JOIN code_analyses ca ON ci.analysis_id = ca.id
GROUP BY ca.user_id -- ⚠️ 按 analysis 关联的用户统计
问题根源:
code_issues 时,analysis_id 应该关联到 committer_name 对应的分析记录错误代码示例:
// ❌ 错误:拿到 dove_digital 的某个分析记录(可能是第一个)
const analysis = await prisma.codeAnalysis.findFirst({
where: { project: { name: 'dove_digital' }, periodValue: '20260402' }
});
// ❌ 然后所有问题都关联到这个分析记录
await prisma.codeIssue.create({
data: {
analysis_id: analysis.id, // ⚠️ 可能是错误的分析记录!
committer_name: 'zhangdi-jk',
...
}
});
正确做法:
// ✅ 根据 committer_name 找到正确的分析记录
const analysis = await prisma.codeAnalysis.findFirst({
where: {
project: { name: 'dove_digital' },
periodValue: '20260402',
user: { username: committerName } // ⚠️ 按提交者查找!
}
});
if (!analysis) {
console.log(`⚠️ 未找到 ${committerName} 的分析记录,跳过`);
continue;
}
// ✅ 关联到正确的分析记录
await prisma.codeIssue.create({
data: {
analysis_id: analysis.id, // ✅ 正确的分析记录
committer_name: committerName,
...
}
});
教训:
project + period + user(username=committer_name)每次执行代码质量分析任务前,必须确认:
□ 1. 字段结构:检查历史数据的字段名,保持一致
□ 2. 同步顺序:code_analyses → code_issues → code_reviews → team_statistics → project_statistics
□ 3. 用户过滤:只同步 team_id 匹配的用户
□ 4. AI 评分:同步前必须完成
□ 5. AI 代码审查:同步前必须完成
□ 6. ⭐ team_statistics.avg_quality_score:必须计算并更新(从 code_analyses 聚合)
□ 7. ⭐ code_issues.analysis_id:必须匹配 committer_name(按 user.username 查找)
□ 8. 统计表:不要遗漏 team_statistics 和 project_statistics
□ 9. 验证前端:确认大盘、类型分布、问题明细、提交记录都正常显示
□ 10. ⭐ 验证关联关系:code_issues 的 analysis_id 对应的用户名 == committer_name
| 日期 | 更新内容 |
|---|---|
| 2026-03-28 | 新增错误案例库(8个典型案例) |
| 2026-03-28 | 新增错误防范清单 |
| 2026-03-24 | 新增月度分析逻辑(不需要找分支) |
| 2026-03-24 | 新增周维度分支差异正确命令 |
| 2026-03-24 | 新增代码审查功能 |
| 2026-03-24 | 新增审查报告格式模板 |
| 2026-03-24 | 新增结合 code-review 技能的审查方法 |
| 2026-03-24 | 新增 ProjectCodeReview 前端页面 |
| 2026-03-24 | 新增 CodeReviewModule 后端模块 |
-- 代码问题表
CREATE TABLE code_issues (
id UUID PRIMARY KEY,
analysis_id TEXT REFERENCES code_analyses(id),
file_path VARCHAR(500),
line_start INT,
line_end INT,
issue_type VARCHAR(100),
severity VARCHAR(20), -- P0/P1/P2
description TEXT,
suggestion TEXT,
code_snippet TEXT,
code_example TEXT, -- 修复后的代码示例
committer_name VARCHAR(100), -- 提交人
created_at TIMESTAMP,
updated_at TIMESTAMP
);
-- 创建索引
CREATE INDEX idx_code_issues_analysis ON code_issues(analysis_id);
CREATE INDEX idx_code_issues_severity ON code_issues(severity);
CREATE INDEX idx_code_issues_type ON code_issues(issue_type);
| 端点 | 方法 | 描述 |
|---|---|---|
/api/v1/code-review/:projectId | GET | 获取项目的代码审查问题 |
/api/v1/code-review/:projectId/summary | GET | 获取审查汇总统计 |
/api/v1/projects/:id/report | GET | 获取项目报告(含贡献者统计) |
/projects/:projectId/code-reviewProjectCodeReview.tsx┌─────────────────────────────────────────────────────────────┐
│ 页面头部 │
│ - 返回按钮、标题、描述、报告生成时间 │
├─────────────────────────────────────────────────────────────┤
│ 统计维度切换 │
│ - 周/月切换 + 日期选择器 │
├─────────────────────────────────────────────────────────────┤
│ 版本信息 │
│ ┌──────────────┬──────────────┬──────────────┐ │
│ │ 当前版本 │ 对比版本 │ 总任务数 │ │
│ └──────────────┴──────────────┴──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 整体统计 │
│ ┌────────┬────────┬────────┬────────┬────────┐ │
│ │提交次数│新增行数│删除行数│净增长 │贡献者 │ │
│ └────────┴────────┴────────┴────────┴────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 贡献者统计 │
│ ┌────────┬────────┬────────┬────────┬────────┬────────┐ │
│ │提交人 │提交次数│提交占比│新增行数│删除行数│净增行数│ │
│ ├────────┼────────┼────────┼────────┼────────┼────────┤ │
│ │必须修改│建议修改│问题总数│问题占比│质量评价│ │ │
│ └────────┴────────┴────────┴────────┴────────┴────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 问题明细 │
│ - Tab切换:全部/P0/P1/P2 │
│ - 或按文件分组查看 │
│ - 表格列:提交人、文件、问题类型、严重程度、描述、建议、操作 │
├─────────────────────────────────────────────────────────────┤
│ 共性问题汇总 │
│ - 问题类型、出现次数、涉及文件 │
├─────────────────────────────────────────────────────────────┤
│ 执行优先级 │
│ - P0/P1/P2 三行,背景色区分 │
│ - 优先级、说明、数量、示例问题 │
├─────────────────────────────────────────────────────────────┤
│ 总体评价 │
│ - 综合评分(大号数字) │
│ - 优点列表 │
│ - 改进建议列表 │
└─────────────────────────────────────────────────────────────┘
| 字段 | 说明 | 计算方式 |
|---|---|---|
| 提交人 | 用户名 | users.username |
| 提交次数 | 该周期提交数 | commit_count |
| 提交占比 | 占总提交百分比 | commit_count / total_commits * 100% |
| 新增行数 | 新增代码行 | insertions |
| 删除行数 | 删除代码行 | deletions |
| 净增行数 | 新增-删除 | insertions - deletions |
| 必须修改数 | P0+P1 问题数 | COUNT(severity IN ('P0','P1')) |
| 建议修改数 | P2 问题数 | COUNT(severity = 'P2') |
| 问题总数 | 该用户问题数 | COUNT(*) FROM code_issues |
| 问题占比 | 占总问题百分比 | issue_count / total_issues * 100% |
| 质量评价 | 文字评价 | 根据问题数判定 |
function getQualityRating(issueStats) {
const { mustFixCount, totalIssues } = issueStats;
if (totalIssues === 0) return '优';
if (mustFixCount >= 3 || totalIssues >= 5) return '需重点关注';
if (mustFixCount >= 1 || totalIssues >= 3) return '待改进';
return '良好';
}
-- 获取贡献者统计
SELECT
u.username,
ca.commit_count,
ROUND(ca.commit_count * 100.0 / total.commit_count, 1) as commit_ratio,
ca.insertions,
ca.deletions,
ca.insertions - ca.deletions as net_lines,
COALESCE(p0_p1.count, 0) as must_fix_count,
COALESCE(p2.count, 0) as suggest_count,
COALESCE(total_issues.count, 0) as issue_count
FROM code_analyses ca
JOIN users u ON ca.user_id = u.id
-- 子查询统计各严重程度问题数
LEFT JOIN (...) p0_p1 ON ...
LEFT JOIN (...) p2 ON ...
LEFT JOIN (...) total_issues ON ...
WHERE ca.project_id = ? AND ca.period_type = ? AND ca.period_value = ?
ORDER BY ca.commit_count DESC;
code-quality-frontend/
├── src/pages/ProjectCodeReview/
│ ├── ProjectCodeReview.tsx # 主组件
│ └── ProjectCodeReview.css # 样式
// 贡献者表格列定义
const memberColumns = [
{ title: '提交人', dataIndex: 'username', width: 130 },
{ title: '提交次数', dataIndex: 'commitCount', width: 90 },
{ title: '提交占比', dataIndex: 'commitRatio', width: 90 },
{ title: '新增行数', dataIndex: 'insertions', width: 100, render: v => <span style={{ color: '#52c41a' }}>+{v}</span> },
{ title: '删除行数', dataIndex: 'deletions', width: 100, render: v => <span style={{ color: '#f5222d' }}>-{v}</span> },
{ title: '净增行数', dataIndex: 'netLines', width: 100 },
{ title: '必须修改', dataIndex: 'mustFixCount', width: 90, render: v => v > 0 ? <Tag color="#ff4d4f">{v}</Tag> : <Tag>0</Tag> },
{ title: '建议修改', dataIndex: 'suggestCount', width: 90, render: v => v > 0 ? <Tag color="#52c41a">{v}</Tag> : <Tag>0</Tag> },
{ title: '问题总数', dataIndex: 'issueCount', width: 90 },
{ title: '问题占比', dataIndex: 'issueRatio', width: 90 },
{ title: '质量评价', dataIndex: 'qualityRating', width: 110, fixed: 'right',
render: v => <Tag color={colorMap[v]}>{v}</Tag> },
];
// 问题明细表格列定义
const issueColumns = [
{ title: '提交人', dataIndex: 'committer_name', width: 130 },
{ title: '文件', dataIndex: 'file_path', width: 180, ellipsis: true },
{ title: '问题类型', dataIndex: 'issue_type', width: 130 },
{ title: '严重程度', dataIndex: 'severity', width: 90 },
{ title: '问题描述', dataIndex: 'description', width: 280, ellipsis: true },
{ title: '修改建议', dataIndex: 'suggestion', width: 350, ellipsis: true },
{ title: '操作', key: 'action', width: 70, fixed: 'right' },
];
// 获取代码审查问题
const res = await request.get(`/code-review/${projectId}`, {
periodType, periodValue
});
// 获取项目报告(含贡献者统计)
const res = await request.get(`/projects/${projectId}/report`, {
periodType, periodValue
});
原因:code_issues 表的 committer_name 字段未填充。
解决:从 code_analyses 关联 users 表更新:
UPDATE code_issues ci
SET committer_name = u.username
FROM code_analyses ca
JOIN users u ON ca.user_id = u.id
WHERE ci.analysis_id = ca.id
AND ci.committer_name IS NULL;
原因:前端状态管理问题,URL 参数未同步。
解决:使用本地状态 + URL 同步:
const [periodType, setPeriodType] = useState(initialPeriodType);
const handlePeriodTypeChange = (newType) => {
setPeriodType(newType);
// 更新 URL 参数
const params = new URLSearchParams(searchParams);
params.set('periodType', newType);
navigate(`${pathname}?${params}`, { replace: true });
};
原因:后端接口不支持项目名称查询,只支持 UUID。
解决:在后端 service 添加名称解析:
async resolveProjectId(idOrName: string) {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (uuidRegex.test(idOrName)) return idOrName;
const project = await this.prisma.project.findFirst({
where: { name: idOrName }
});
return project?.id || null;
}
| 文件 | 路径 |
|---|---|
| 分析脚本 | /Users/zhangdi/work/codeCap/代码质量分析系统/scripts/analyze-code-v2.js |
| 同步脚本 | /Users/zhangdi/work/codeCap/代码质量分析系统/scripts/sync-to-db.js |
| 分析输出 | /Users/zhangdi/work/codeCap/代码质量分析系统/analysis-output/analysis-YYYYMMDD.json |
| 后端服务 | /Users/zhangdi/work/codeCap/代码质量分析系统/code-quality-backend/ |
| 前端服务 | /Users/zhangdi/work/codeCap/代码质量分析系统/code-quality-frontend/ |
| CodeReview 模块 | code-quality-backend/src/modules/code-review/ |
| ProjectCodeReview 页面 | code-quality-frontend/src/pages/ProjectCodeReview/ |
早期同步脚本手动指定项目 ID(如 project-lego-nuxt),后来改为自动生成 UUID,导致数据库中有两种格式。
所有项目 ID 必须使用 UUID 格式!
✅ 正确: 550e8400-e29b-41d4-a716-446655440000
❌ 错误: project-lego-nuxt
后端服务需要支持三种查询格式:
async resolveProjectId(idOrName: string) {
// 1. UUID 格式 - 直接返回
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (uuidRegex.test(idOrName)) return idOrName;
// 2. 项目 ID(非 UUID 格式)- 查找旧数据
const projectById = await this.prisma.project.findFirst({
where: { id: idOrName }
});
if (projectById) return projectById.id;
// 3. 项目名称 - 按名称查找
const projectByName = await this.prisma.project.findFirst({
where: { name: idOrName }
});
return projectByName?.id || null;
}
同步脚本应该按项目名查找,不存在时创建新项目(自动生成 UUID):
async function getOrCreateProject(projectName, prisma) {
let project = await prisma.project.findFirst({
where: { name: projectName }
});
if (!project) {
project = await prisma.project.create({
data: { name: projectName }
// id 自动生成 UUID
});
}
return project.id;
}
.archive/ 目录)以下文件已废弃,移动到 .archive/ 目录:
| 文件 | 原因 |
|---|---|
analyze-code.js | 旧版分析脚本,v2 已支持周/月 |
analyze-month.js | 月度专用,v2 已支持 |
sync-ai-scores-*.js | 旧的 AI 评分同步,已删除外部 API 调用 |
sync-month-to-db.js | 旧版月度同步 |
sync-to-db.js、sync-to-db-v2.js | 旧版同步 |
sync-week-to-db.js | 旧版周同步 |
sync-week-20260326.js | 特定周期的临时脚本 |
| 脚本 | 用途 |
|---|---|
analyze-code-v2.js | 分析代码变更(周/月) |
sync-to-db.js | 同步分析数据到数据库 |
code-review.js | 代码审查问题分析 |
notify-teams.js | 360Teams 消息通知 |
weekly-analysis.sh | 每周自动分析脚本 |
| 日期 | 更新内容 |
|---|---|
| 2026-03-25 | 新增项目 ID 格式约定(UUID 格式) |
| 2026-03-25 | 新增 resolveProjectId 方法支持三种格式 |
| 2026-03-25 | 新增废弃文件归档说明 |
| 2026-03-24 | 新增月度分析逻辑(不需要找分支) |
| 2026-03-24 | 新增周维度分支差异正确命令 |
| 2026-03-24 | 新增代码审查功能 |
| 2026-03-24 | 新增审查报告格式模板 |
| 2026-03-24 | 新增结合 code-review 技能的审查方法 |
| 2026-03-24 | 新增 ProjectCodeReview 前端页面 |
| 2026-03-24 | 新增 CodeReviewModule 后端模块 |
| 2026-03-24 | 新增贡献者统计表格(11 个字段) |
| 2026-03-24 | 新增问题明细表格优化 |
| 2026-03-24 | 新增总体评价卡片 |
| 2026-03-24 | 新增前端实现要点文档 |
| 2026-03-24 | 新增常见问题处理方案 |