痛点
我们团队 3 个项目、6 个仓库、前后端各一人。按理说每周应该做代码审查,但现实是:
- 没人做。活都干不完,谁有空看别人的代码?
- 做了也流于形式。随便扫两眼,"看起来没问题",完事。
- 问题照出。低级命名、重复代码、潜在 Bug,事后才发现。
说白了,代码审查重要性都知道,但执行力几乎为零。
那能不能让 AI 来做?每天自动采集代码提交,AI 审查质量,报告推送到钉钉群。团队不用花时间,但问题有人盯着。
这就是 code-review-agent。
架构:四步走
CNB API → Git Show → DeepSeek AI → 钉钉推送
采集提交 获取Diff AI审查代码 周报直达群
- Step 1:从代码仓库拉取本周所有 commit 记录
- Step 2:用 Git 获取每个 commit 的代码差异
- Step 3:把代码差异丢给 AI,让它审查代码质量
- Step 4:汇总成周报,推送到钉钉群
关键差异:周报只需要 commit message,代码审查需要看真正的代码变更。
Step 1:数据采集——项目映射
CNB 的仓库路径是 team/project/task-api,但报告里应该写"任务管理系统·后端"。所以需要映射:
type ProjectConfig struct {
Name string
Backend RepoInfo // 仓库名 + 负责人
Frontend RepoInfo
}
配置在 .env 里:
CNB_REPOS=org/repo1,org/repo2
PROJECTS_JSON={"name":"任务管理系统","backend":{"repo":"task-api","dev":"后端A"},"frontend":{"repo":"task-web","dev":"前端A"}}
PROJECTS_JSON={"name":"文档协作平台","backend":{"repo":"doc-api","dev":"后端B"},"frontend":{"repo":"doc-web","dev":"前端A"}}
采集时,把仓库名映射到项目和负责人,后面的报告才能按项目分组、按负责人 @提醒。
Step 2:获取代码差异——踩坑最多的环节
最初方案:CNB Diff API(❌ 404)
url := fmt.Sprintf("https://api.cnb.cool/%s/commit/%s/diff", repo, sha)
// 全部 404,这个 API 对第三方未开放
最终方案:Git Clone + Git Show(✅)
func FetchDiff(cfg *entities.Config, repo, sha string) (string, error) {
repoDir := filepath.Join(os.TempDir(), "code-review-"+repo)
// 首次:克隆仓库
if _, err := os.Stat(repoDir); os.IsNotExist(err) {
repoURL := fmt.Sprintf("https://cnb.cool/%s.git", repo)
if cfg.GitUsername != "" {
repoURL = fmt.Sprintf("https://%s:%s@cnb.cool/%s.git",
cfg.GitUsername, cfg.GitPassword, repo)
}
exec.Command("git", "clone", "--depth=200", repoURL, repoDir).Run()
} else {
// 后续:只 fetch 更新
exec.Command("git", "-C", repoDir, "fetch", "--all").Run()
}
// 获取指定 commit 的 diff
output, _ := exec.Command("git", "-C", repoDir, "show", "--no-color", sha).CombinedOutput()
return string(output), nil
}
关键点:
--depth=200:浅克隆,只拉最近 200 个 commit--no-color:去掉 ANSI 颜色码,纯文本更利于 AI 解析- 缓存机制:仓库只克隆一次,后续只 fetch 更新
源码过滤:只审查有意义的代码
一个 commit 的 diff 里可能有 package-lock.json、.env、二进制文件。这些丢给 AI 是浪费 Token:
sourceExts := map[string]bool{
".go": true, ".py": true, ".js": true, ".ts": true,
".vue": true, ".java": true, ".css": true, ".scss": true,
}
func FilterSourceCode(diff string) string {
// 只保留源码文件的 diff 行
// 2000 行 diff 可能只剩 500 行,Token 消耗降 75%
}
Step 3:AI 代码审查——Prompt 是灵魂
逐个审查:每个 commit 过一遍 AI
func ReviewDiff(cfg *entities.Config, diffBlock string, commitInfo string) (string, error) {
systemPrompt := `你是代码审查助手。只输出有问题的代码,格式必须适合手机阅读。
【输出格式 - 严格遵循】
# 📋 代码审查报告
## 📊 概览
审查 X 个提交 | 质量评分 X/5
---
## ⚠️ 发现的问题
### 问题1:问题标题
**责任人:后端A**
**❌ 修改前:**
` + "```" + `语言
// 有问题的代码
` + "```" + `
**✅ 修改后:**
` + "```" + `语言
// 改进后的代码
` + "```" + `
**原因:** 简要说明问题
---
【强制规则】
- ❌ 禁止使用 Markdown 表格
- ✅ 每个问题必须有代码对比
- ✅ 每个问题必须标注责任人
- ✅ 只输出有问题的地方
- ✅ 移动端友好`
reqBody := ChatRequest{
Model: cfg.DeepSeekModel,
Messages: []ChatMessage{
{Role: "system", Content: systemPrompt},
{Role: "user", Content: "Commit: " + commitInfo + "\n\nDiff:\n```diff\n" + diffBlock + "\n```"},
},
}
// 调用 DeepSeek API ...
}
移动端友好的输出格式
这是迭代后的版本,解决了原版的问题:
- ❌ 去掉 Markdown 表格(移动端显示乱)
- ✅ 每个问题包含「修改前 vs 修改后」代码对比
- ✅ 每个问题标注责任人
- ✅ 只输出有问题的部分,正确的不写
- ✅ 用
---分隔问题,适合聊天记录滚动查看
Prompt 设计的 5 条铁律
1. 格式必须写死
不加格式约束,AI 输出千奇百怪。在 Prompt 里把完整模板写出来,输出立刻可控。
2. 每个问题必须"修改前 vs 修改后"
如果 AI 只说"变量命名不规范",读者还得猜怎么改。要求给出代码对比,可操作性翻倍。
3. @负责人
审查报告的价值在于可执行。"有个变量命名不好"不如"@后端A,第 42 行变量名建议改为 userList"。
4. 只关注代码质量,不关注业务逻辑
AI 看不懂你的业务。让它审查命名、结构、可读性、重复代码、潜在 Bug。这些是 AI 真正擅长的。
5. 合并共性问题
34 个 commit 逐个审查,可能有 10 个都犯了"变量名缩写过度"。汇总时让 AI 合并同类项,报告才不会又臭又长。
汇总审查:把多份审查合并成一份周报
func GenerateSummary(cfg *entities.Config, reviews []string, commits []entities.Commit) (string, error) {
// 构建项目统计信息
projectInfo := buildProjectStats(commits)
// 把所有审查结果拼接
combinedReview := strings.Join(reviews, "\n\n---\n\n")
// 丢给 AI 生成最终周报(格式同上)
// ...
}
最终输出一份按项目分组的审查周报,钉钉群里直接渲染 Markdown。
Step 4:钉钉推送
上次用企业微信,这次团队用钉钉。逻辑完全一样,换了个 Webhook 格式:
func SendMarkdown(webhook, title, markdown string) error {
msg := DingTalkMarkdownMsg{MsgType: "markdown"}
msg.Markdown.Title = title
msg.Markdown.Text = markdown
jsonBody, _ := json.Marshal(msg)
resp, _ := http.Post(webhook, "application/json", bytes.NewReader(jsonBody))
return nil
}
💡 想用飞书、企业微信、Slack?只需要改这一个函数,其他代码不用动。
项目结构
code-review-agent/
├── main.go # 入口 + 调度
├── entities/ # 数据结构
├── config/ # 配置加载
├── cnb/ # CNB API 采集 + 源码过滤
├── git/ # Git clone/show 操作
├── review/ # DeepSeek AI 审查 + 汇总
├── dingtalk/ # 钉钉推送
├── go.mod
├── .env.example
└── README.md
拆分的理由很实际:main.go 超过 600 行后,改一个地方要上下翻好久。拆成 6 个包后,改 Git 逻辑只看 git/git.go,改审查 Prompt 只看 review/review.go。
定时执行
func main() {
config.LoadDotEnv()
cfg := config.Load()
// 启动时立即执行一次
runCodeReview(cfg)
// 注册定时任务:每周三 23:11
if cfg.CronSpec != "" {
c := cron.New()
c.AddFunc(cfg.CronSpec, func() {
cfg.DateFrom = time.Now().AddDate(0, 0, -7).Format("2006-01-02")
cfg.DateTo = time.Now().Format("2006-01-02")
runCodeReview(cfg)
})
c.Start()
select {}
}
}
⚠️ Cron 表达式的坑
robfig/cron/v3 的格式是:分 时 日 月 周
我想设"每周三 23:00",一开始写了 0 3 23 * * 3,以为是从左到右递增。结果被解析成"每月 23 号周三凌晨 3 点"。
正确写法:0 23 * * 3
实际效果
34 个 commit 逐个审查,大约 15 分钟。其中 AI 调用是主要耗时,每个 commit 约 8-15 秒。
钉钉群里收到的报告格式:
# 📋 代码质量周报
## 📊 概览
审查 8 个提交 | 质量评分 3/5
---
## ⚠️ 问题清单
### 问题1:死代码未删除
**责任人:后端A**
**❌ 修改前:**
```html
<!-- <button class="submit">报送</button> -->
✅ 修改后:
(直接删除整行)
原因: 应删除而非注释,Git 已有历史记录
📝 改进建议
- 清理注释残留代码 @后端A
- 提取硬编码数值为常量 @前端A
可以看到:
- ✅ 适合手机滚动查看
- ✅ 每个问题有代码对比
- ✅ @了具体负责人
- ✅ 共性问题已合并
---
## 和周报 Agent 的对比
两个 Agent 架构相似,但重点不同:
- **周报 Agent**:输入 commit message,AI 做文案整理,输出给人看"做了什么"
- **代码审查 Agent**:输入 commit message + 代码 Diff,AI 做代码审查,指出代码问题
代码审查 Agent 新增了 3 个关键能力:
1. **项目映射**:仓库名 → 项目名 + 负责人,报告才有归属
2. **源码过滤**:只审查 `.go` `.vue` `.ts` 等源码文件,跳过配置和锁文件
3. **汇总报告**:多份逐个审查 → 1 份按项目分组的周报
---
## 部署指南
### 前置准备
1. DeepSeek API Key(注册就送额度,审查一次约 0.1 元)
2. CNB Token(或其他代码平台 Token)
3. 钉钉群机器人 Webhook
4. 服务器上安装 Git
### 部署步骤
```bash
# 克隆项目
git clone https://github.com/lobster-bujiaban/code-review-agent.git
cd code-review-agent
# 配置
cp .env.example .env
# 编辑 .env,填入 API Key、仓库列表、项目映射、Webhook
# 编译
go build -o code-review-agent .
# 测试运行
./code-review-agent
.env 配置示例
DEEPSEEK_API_KEY=sk-xxxxx
CNB_TOKEN=your-token
CNB_REPOS=org/project-a/backend,org/project-a/frontend
# 项目配置(每个项目一行)
PROJECTS_JSON={"name":"项目A","backend":{"repo":"backend","dev":"后端A"},"frontend":{"repo":"frontend","dev":"前端A"}}
DINGTALK_WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=xxx
# 留空=自动最近7天,或指定日期
DATE_FROM=auto
DATE_TO=auto
# 每周三 23:00
CRON_SPEC=0 23 * * 3
systemd 部署
[Unit]
Description=Code Review Agent
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/code-review-agent
ExecStart=/opt/code-review-agent/code-review-agent
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
# 部署
sudo cp code-review-agent /opt/code-review-agent/
sudo cp .env /opt/code-review-agent/
# 启动
sudo systemctl daemon-reload
sudo systemctl enable code-review-agent
sudo systemctl start code-review-agent
# 查看日志
sudo journalctl -u code-review-agent -f
交叉编译(推荐,无需在服务器装 Go)
# Mac/Linux
GOOS=linux GOARCH=amd64 go build -o code-review-agent-linux
# Windows PowerShell
$env:GOOS="linux"; $env:GOARCH="amd64"; go build -o code-review-agent-linux
改造成你自己的
- 换代码平台 → 改
cnb/cnb.go - 换 AI 模型 → 改
review/review.go的 API 地址 - 换推送渠道 → 改
dingtalk/dingtalk.go - 加审查维度 → 改
review/review.go的 Prompt - 调审查频率 → 改
.env的CRON_SPEC
踩过的坑
坑 1:CNB Diff API 404
API 文档里有 diff 端点,但第三方调用全部 404。浪费了 2 小时尝试各种参数。
解法:降级到 git clone + git show。多一步 Git 操作,但 100% 可靠。
教训:别信文档,先 curl 验证 API 可用性,再写代码。
坑 2:Cron 表达式字段顺序搞反
0 3 23 * * 3 → 我以为是"秒 分 时 日 月 周",实际是"分 时 日 月 周"。
解法:改成 0 23 * * 3。
坑 3:AI 输出格式不可控
第一版 Prompt 只有"你是代码审查员",AI 输出五花八门。
解法:在 Prompt 里写死完整的 Markdown 模板。
教训:Prompt 不是聊天,是"编程"。你不说清楚格式,AI 就自由发挥。
坑 4:源码不过滤,Token 爆炸
一个 commit 可能改了 package-lock.json、.env,这些文件 diff 动辄几千行。
解法:加了 FilterSourceCode() 函数,只保留源码文件的 diff。
总结
这个项目从想法到跑通,花了大约 两个晚上。比周报 Agent 多花了一倍时间,主要是因为 Diff 获取方案从 API 降级到 Git,以及 Prompt 迭代。
但核心架构是同一条线:
数据采集 → AI 处理 → 自动推送
下周我会写 W3 踩坑实录,把搭这两个 Agent 过程中所有踩过的坑详细拆解。
📦 源码地址
GitHub - lobster-bujiaban/code-review-agent
欢迎 Star、提 Issue、Fork 改造。
🦞 一只用 AI Agent 搭副业产线的程序员
公众号:虾哥不加班 | B站:龙虾不加班 | 掘金:龙虾不加班