☰
自研轻量代码评审系统:基于Git的open-code-review实现解析
2026/10/13 13:45:09 网站建设 项目流程

1. 为什么我放弃了现成的代码评审工具,自己动手写了一套

先说一下背景。我们团队大概从去年开始,代码评审这事儿就变得特别别扭。不是说评审本身不好,而是手头可用的工具总差那么点意思。用现成的商业平台,流程倒是完整了,但每次拉分支、建请求、等流水线、找审批人,一套流程走下来,半天时间就没了。尤其我们有些内部项目,代码量不算大,也没法直接往公开平台上传,数据安全上过不去。还有一类小项目,就是临时脚本、内部实验代码、甚至文档仓库,引入完整评审工具体系显得特别重。

后来我干脆做了个决定:自己搭建一套轻量的代码审查系统,起名叫open-code-review。它不是替代那些重型平台,而是一套贴合小团队、内部仓库、数据自持场景的轻量解决方案。用下来的效果,比我想象中好得多,所以今天把整个思路、架构、踩坑过程都写出来分享给大家。

如果你也是下面这几类人,这篇文章应该能对上你的胃口:

  • 团队规模不大,比如 5 到 20 人,仓库都在内网或私有 Git 服务器上。
  • 对代码评审有需求,但觉得现有商业化工具的流程太重、权责不清、上手成本高。
  • 想自己掌控评审流程,希望整个系统可控、可扩展、数据不落别人手里。

这套系统前后我断断续续改了三轮,从最开始的简单 Git Hook 加邮件通知,到后来加了命令行工具、Web 面板、统计报表,再到打通了 CI 状态,算是完整地走了一遍从需求到落地的全过程。这篇文章会把每一轮的思路、取舍、坑都沉淀下来,你可以当参考,也可以直接抄作业。

2. 先拆需求:代码评审工具到底要解决什么问题

很多人在选型或者自研工具时有个误区,上来就想着"我要做一个 XX 平台""要有 Web 界面""要支持多人协作"。但实际上,工具的核心价值是解决业务痛点,而不是为了炫技。我自己的经验是,先花一个下午把所有干系人拉在一起,把"不满"写下来,再反推需求。

我们当时收集到的痛点,大致可以归成四类:

  • 流程低效:评审信息分散在邮件、IM 和本地记录里,经常要费劲找上下文。
  • 入口不统一:开发人员要看属于自己模块的评审请求,得靠"所有人都在群里刷屏"才能推动。
  • 质量无法度量:评审完成没有统计数据,说不清哪个模块问题多、什么类型错误占比高,没法持续改进。
  • 权限和数据受限:公司要求代码不能出内网,商业化 SaaS 产品就用不了,自建是我们能想到的唯一出路。

基于这些,我理出了open-code-review的核心需求清单:

需求项优先级说明
基于 Git 仓库的轻量评审流程高不引入额外数据库表结构,直接基于 Git 元数据
命令行提交评审请求高开发者不用打开网页,终端里就完成提交
通知挂钩,主动推送中评审请求状态变更时能通知相关人员
Web 面板查看与管理高给不熟悉命令行的同事一个可视化入口
评审统计与报表中便于跟进团队质量趋势
权限可控高项目和评审记录隔离,支持私有化使用

排完优先级你就明白了,这不是要把系统做得多庞大,而是一开始就要想清楚:什么场景下值得设计一个功能,什么场景下不值得。举个例子,当时我想加自动分配给评审人的功能,但仔细想想团队规模小,互相都认识,谁有空谁评就行;这个"自动分配"看似智能,实际用起来会很尴尬。后来我干脆砍掉,改用手动指派加通知。

这个环节我特别建议做一件事:把"我们要解决什么问题"贴在白板上。每次想加新功能时先问一句,它和这四类痛点有没有直接关系?没有就不加。这样能帮你避免自研工具最忌讳的过度设计。

3. 总体设计思路:Git 本身就是最好的数据库

整个open-code-review设计上最核心的一个决定,就是不单独搞复杂的关系型数据库来存储评审数据,而是让 Git 仓库本身承担数据库的角色。

你可能觉得有点反直觉,我解释一下。每个项目仓库里有主干分支、功能分支、标签、提交记录,这些天然就带着"代码版本信息"。而评审请求的本质,是"某一次代码变更,经过了一段时间,经过了几轮讨论,最终被接受或拒绝"。顺着这个思路走,每一次评审请求就可以通过一个特殊的分支名或标签来标记,再配合提交记录和备注,整个评审的生命周期都被 Git 原生地记录下来。

这样的好处非常明显:

  • 数据一致性强。Git 有版本、有历史、可回溯,评审记录跟着仓库走,不会因为数据库误删而丢失。
  • 基础设施复用。团队本来就有 Git 服务器,不需要额外搭数据库、Redis、消息队列。对于内网环境,少一个组件就少一个可挂点。
  • 权限模型天然。仓库本身有权限控制,能访问仓库的人才能看到评审请求,和公司的权限体系天然对齐。

当然,这不是说完全没有需要存储的地方。实际实现里,我们也会用一个小型存储来存 Web 面板的配置项、用户偏好、统计缓存等。但核心流程数据全部依托 Git,这一点从第一版就定了下来,后续的迭代也一直在遵守。

4. 核心实现与流程解析

4.1 评审请求的数据模型

在设计数据模型时,我参考了主流评审工具的字段,但做了一层精简。一条评审请求(Review Request)在系统里就是一个结构化文本,通常包含以下几个核心字段:

  • 唯一标识:按照日期和流水号生成,比如RR-20250415-001。
  • 标题:描述这次变更的目的,例如"修复订单导出超时问题"。
  • 关联分支和目标分支:从哪来的变更、要合到哪去。
  • 提交范围:两个 commit 之间的 SHA 范围。
  • 评审人:被指派的 Reviewer 列表。
  • 状态:待评审、评审中、已通过、已拒绝、已关闭。
  • 附加说明:比如测试结论、影响范围说明、相关 issue 链接。

实际存储上,我把一条评审请求放在仓库下的一个特定目录中,比如.code-reviews/2025/04/RR-20250415-001.md。内容用 Markdown 编写,自带格式,也方便 Web 界面直接渲染。有人会问,为什么不用 JSON?因为评审请求终究是给人看的,Markdown 的可读性更好,而且 Git 客户端直接打开文件也完全可读。你要是真想让机器解析,前面 YAML front matter 放结构化字段,后面正文写描述就行。

这套模式跑起来后非常舒服,你甚至可以不用任何额外工具,直接用git log来查看所有评审请求的提交历史,谁创建的、谁修改了什么、最后什么时候关闭的,一目了然。

4.2 命令行工具:让提交评审变成 3 秒的事

命令行工具是整套系统里用得最频繁的部分,我给它取名review,通过包装 Git 命令来实现和仓库的交互。

最常用的几个命令是这样设计的:

# 创建一条评审请求 review create --title "修复订单导出超时问题" --reviewer alice --reviewer bob # 查看当前仓库的评审请求列表 review list --state open --mine # 更新一条评审请求状态 review update RR-20250415-001 --state approved # 为评审请求添加评论 review comment RR-20250415-001 --message "LGTM,但建议补充一下异常日志"

有人可能会问,这些命令背后做了什么?其实核心就三步:

  1. 在.code-reviews/下生成对应的 Markdown 文件。
  2. 用 Git 提交这条变更,commit message 格式比如review: RR-20250415-001 创建评审请求。
  3. 推送这个提交到远端仓库的指定评审分支。

如果是更新状态或添加评论,同样是在新提交里追加内容,再推上去。这个设计保证了评审历史在 Git 日志里是有迹可循的,谁在什么时候做了什么操作,通过git log --follow都能查出来。这种"所有状态流转都是 Git 提交"的思路,后续排查问题时会发现好得惊人。曾经有一次同事问我某条评审请求是什么时候被批准的,我在终端里敲了一句git log --oneline .code-reviews/2025/04/RR-20250415-001.md,三秒钟答案就出来了。

4.3 Git Hook 联动:让系统自动感知状态变化

如果只用命令行工具,那这套系统还只是个"手动挡"。为了让评审请求的状态变更及时触达相关人,我在服务端仓库加了一些 Git Hook,主要用post-receive这个钩子。

当开发者在本地推送代码时,远端仓库的post-receive钩子会探测推送的分支名和提交信息。如果提交信息符合review: RR-xxx 更新状态的格式,就会触发一个脚本去解析本次变动涉及的状态字段,然后调用通知渠道。

实际的通知方式我们做了两个通道:

  • 企业微信/钉钉机器人的 Webhook:适合快速触达,消息卡片里直接附上评审状态、变更摘要、链接。
  • 邮件通知:适合老旧但正式的场景,某条评审请求被批准或拒绝时必须邮件留档。

这里要提醒一下:Hook 脚本里千万注意幂等性。因为 Git 的post-receive可能一次推送多个提交,你要保证同一个评审请求的多次状态更新不会造成通知轰炸。我当时的解决方案是,先比较当前 Markdown 文件里最新的状态字段和数据库里的记录,只有真正变化时才发通知。

4.4 Web 面板:给不想敲命令的同事一条活路

虽说命令行工具效率高,但团队里总有同事用不惯终端,尤其是产品、测试甚至一些偏后端的开发,他们更习惯点按钮。为了这部分用户,我开发了一个轻量的 Web 面板。

Web 面板不需要实时、高并发,它只需要做几件非常简单的事:

  • 以列表形式展示当前所有评审请求,支持按状态、按指派人过滤。
  • 点击一个评审请求,展示完整的描述、变更范围、评论历史。
  • 支持在页面上更新状态、添加评论(本质上还是调 CLI 的脚本)。
  • 展示一个非常简单的统计图,看每周创建的评审数和平均处理时长。

技术选型上,我没有选重型前端框架,后端就是一个 Flask 应用,前端直接用 Jinja2 模板加一点原生 JavaScript。说句实在话,这种内部工具,功能明确、用户量小,复杂的前端反而拖慢迭代速度。整个 Web 面板代码量不到两千行,后续维护成本极低。

部署方式也简单,在服务器上跑一个 systemd 服务,绑内网端口,前面用 Nginx 做个反向代理和 Basic Auth。团队成员通过内网域名访问,数据和代码全部留在自己手里,安全合规这块完全不用操心。

5. 实操过程:从零搭建一套可用版本

这个环节我尽量把步骤还原得很细,你可以照着操作下来,基本上半天就能搭起一套能跑起来的版本。

5.1 环境准备与目录规划

先列一下需要的依赖组件:

  • 一台 Linux 服务器(我用的是 CentOS 7,其实 Debian/Ubuntu 也可以)。
  • Git 版本 2.20 以上(低版本在post-receive钩子的某些特性上会有差异)。
  • Python 3.6+(用于运行 Web 面板脚本)。
  • 一个已有的 Git 服务端环境,可以是裸仓库、也可以是 Gitea 之类的轻量 Git 服务,但核心钩子逻辑要能挂上。

项目和脚本的目录规划如下:

/opt/open-code-review/ ├── cli/ # 命令行工具入口 │ ├── review.py │ └── commands/ ├── hooks/ # Git 钩子脚本 │ └── post-receive ├── server/ # Web 面板 │ ├── app.py │ ├── templates/ │ └── static/ ├── storage/ # 存储配置、日志 └── docs/ # 文档

提示:目录规划里最重要的是把 CLI、钩子脚本、Web 面板拆开,方便后续独立迭代。我第一版全塞在一个文件里,后来改起来特别痛苦,拆开之后清爽很多。

5.2 命令行工具的实现要点

CLI 我用的 Python 标准库argparse,没有引第三方依赖,这样在任何内网机器上都能直接跑,不需要额外处理环境问题。

核心实现可以划分为几个函数,先看创建评审请求的伪代码逻辑:

def create_review_request(repo_path, title, reviewers, description): # 1. 检查当前仓库状态,确保没有未提交的变更 ensure_clean_working_tree(repo_path) # 2. 生成唯一标识 rr_id = generate_rr_id() # 3. 生成 Markdown 文件 content = build_markdown(rr_id, title, reviewers, description) # 4. 写入 .code-reviews/ 目录 file_path = get_file_path(rr_id) write_file(file_path, content) # 5. 提交并推送 git_commit_add(file_path, f"review: {rr_id} 创建评审请求") git_push_current_branch() return rr_id

这里的generate_rr_id是日期加序号,我通过读取当前年度已有的最大流水号实现,避免并发时重复。build_markdown会生成类似下面的文件内容:

--- id: RR-20250415-001 title: 修复订单导出超时问题 status: open reviewers: [alice, bob] created_by: chris created_at: 2025-04-15 10:30:00 --- ## 变更描述 修复了导出接口在数据量大时因事务超时导致失败的问题。调整了分批查询逻辑,并将导出任务改为异步执行。 ## 测试情况 - 本地构造 10 万条订单数据验证通过 - 导出耗时从 120 秒降至 18 秒

这种 YAML front matter 加正文的结构,机器解析方便,人看着也舒服。后续 Web 面板渲染时,也是直接解析这个文件。

5.3 服务端钩子脚本的编写细节

post-receive钩子脚本逻辑是这样的,我贴一下关键部分:

#!/bin/bash # 读取标准输入,每一行是 "旧SHA 新SHA 分支名" while read oldrev newrev refname; do # 只处理推到主干分支的更新 if [[ $refname =~ refs/heads/main$ ]]; then # 在一段提交范围内检查是否包含 review 文件变更 for commit in $(git rev-list $oldrev..$newrev); do files=$(git diff-tree --no-commit-id --name-only -r $commit) if echo "$files" | grep -q "^\.code-reviews/"; then # 调用 Python 脚本解析变更并发送通知 python3 /opt/open-code-review/hooks/parse_and_notify.py $commit fi done fi done

有几个细节需要特别注意:

  • git rev-list $oldrev..$newrev一定要用正式 SHA 而不是分支名,避免在分支强制推送时出问题。
  • 解析 Markdown 文件的脚本必须做异常捕获,不能因为一条脏数据导致整个 Hook 崩溃。Hook 崩了不会影响正常推送,但会静默失败,这是最麻烦的。
  • 通知发送要有"去重"机制,同一状态重复推送不触发通知。

5.4 Web 面板的快速实现

Web 面板我用 Flask,先把监听地址绑在内网。下面这段代码是一个简化版的展示逻辑:

from flask import Flask, render_template, request import os, re app = Flask(__name__) REVIEW_DIR = "/opt/open-code-review/repo/.code-reviews" def parse_review_file(filepath): # 解析 Markdown 文件,返回字段字典 content = open(filepath, encoding="utf-8").read() meta = {} for line in content.splitlines(): if line.startswith("---"): continue if ":" in line: key, value = line.split(":", 1) meta[key.strip()] = value.strip() return meta @app.route("/") def index(): requests = [] for root, dirs, files in os.walk(REVIEW_DIR): for name in files: if name.endswith(".md"): requests.append(parse_review_file(os.path.join(root, name))) # 按创建时间倒序 requests.sort(key=lambda x: x.get("created_at", ""), reverse=True) return render_template("index.html", requests=requests)

实际操作里,还会加上过滤、分页、评论展示等功能。但核心框架就是这么简单,没有复杂抽象。

Web 面板的认证我用最简单的方式:Nginx 的auth_basic。团队本身就很小,不需要做复杂 SSO,每个人用内网账号登录一下就行。前端页面显示核心字段、状态标签、创建人、评审人,支持通过下拉框快速改变状态。这个过程中我最大的体会是,内部工具界面不用好看,顺手最重要。

6. 踩坑记录与排查经验

自研工具没有现成的售后支持,一切问题都得自己扛。这一路我踩的坑加在一起,够写十篇排查手册了。挑几个最典型的说说:

6.1 并发提交时评审 ID 重复

第一版generate_rr_id函数是直接读当天已存在的数量再加一。结果某次两个同事同时创建评审请求,生成的回车 ID 完全一样,后面推送时相互覆盖,数据直接丢失。后来我改成"读取当前年度已有 ID 列表,取最大流水号 + 1",同时在文件创建上加一个简单的锁文件,才把问题压下去。

注意:并发场景在自研工具里非常容易被忽略。团队哪怕只有两三个人,也可能在同一秒操作,一定要提前考虑幂等和互斥。

6.2 Git Hook 静默失败

早期我用post-receive脚本时,没把 Python 解析器路径写对,结果脚本报错后直接被 Git 吞了,日志里没有任何记录。直到某天同事来问我"为什么创建评审后迟迟没通知"才发现。从那以后,我在所有钩子脚本开头强制加日志:

exec >> /var/log/open-code-review/hooks.log 2>&1 echo "Hook invoked at $(date)"

这样每次钩子执行都有记录,排查问题方便太多了。这个习惯一直保留到现在,任何挂到 Git 生命周期里的脚本都必须有日志。

6.3 评审分支与主干分支混用

最初设计时,评审请求的变更直接推送到main分支,导致main分支上混了很多.code-reviews/的提交,看起来特别乱。想拉个干净的发布分支时,总是带出一堆不必要的提交。后来我给评审请求单独设计了"评审分支":

  • 开发分支始终指向功能开发分支。
  • 评审分支 (refs/heads/review/RR-xxx) 专门用来收集评审文件的提交。
  • 通过git merge --no-ff合并回主分支时,保持主干历史整洁。

这个修改合入后,主分支日志一下子清爽了,每次发布生成 changelog 时也不会再混入评审记录。

6.4 状态流转的权限校验

一开始做 Web 面板时,任何人打开页面都能改评审状态,有次某位同事误操作把一条已通过的评审改成"待评审",引起连环通知。后来我加了一个简单的权限配置,在storage/config.json里指定哪些角色可以更改状态:

{ "roles": { "admin": ["chris", "alice"], "reviewer": ["bob"] }, "permissions": { "update_status": ["admin", "reviewer"], "close_request": ["admin"] } }

Web 面板和 CLI 都读取这份配置做校验。虽然权限模型简单,但真的拦截住了误操作。

6.5 通知轰炸的处理

评审请求被反复修改时,每次提交都会触发通知,造成"刷屏"。我后来在parse_and_notify.py里加了状态变更去重逻辑,只有当状态字段与已记录状态不一致时才发通知。但评论之类的追加内容,还是允许每次提交都触发通知,方便评审人跟进讨论。

7. 数据统计与质量分析

这套系统用了两个月之后,我开始琢磨怎么让数据产生价值。由于所有评审请求都是 Markdown 文件,做统计其实非常顺手。我写了一个简单的分析脚本,按周、按月汇总以下指标:

  • 创建的评审请求数量。
  • 平均评审处理时长(从创建到通过/拒绝的小时数)。
  • 评审请求规模分布(每个请求涉及的提交数量、代码行数)。
  • 各状态占比(通过率、拒绝率、待处理率)。
  • 热门评审人榜(谁被指派的次数最多,谁平均处理最快)。

这些指标不需要多高大上的可视化,用 Excel 或者简单的折线图就能讲清楚问题。举个例子,有一周我们发现某个模块的平均处理时长突然从 8 小时涨到 40 小时,后来排查发现是那个模块的负责人临时休假,评审请求没人处理。于是我们补了一条规则提醒:评审请求创建超过 24 小时未有人响应,自动发给默认兜底评审人。这个改进虽然简单,但非常实用。

统计脚本我从一开始就规划成独立模块,每天由 crontab 跑一次,生成 Markdown 格式的日报发到群里。这样团队人人都能看到数据,质量问题不会被蒙在鼓里。

8. 后续可扩展的方向与维护建议

open-code-review从设计之初就有意识地留了扩展位,如果你也打算做一套类似的系统,我建议先看看这几个扩展点:

  • 与 CI 集成。评审请求创建后可以触发一次流水线,自动编译运行单元测试,把结果贴到评审页面。我现在通过一个回调脚本实现:CLI 创建评审请求后调用 CI 的 API 触发流水线,流水线完成后把测试结论写进 Markdown 文件的test_report字段。
  • 模板化评审检查单。不同项目类型有不同的检查侧重,比如前端项目要关注构建体积、数据请求合理性;后端项目要关注接口参数校验和数据权限。我预留了模板目录,每周可以调整检查单模板,新请求会自动附带模板内容。
  • 多仓库支持。当前版本一次只处理一个仓库,如果要支持一个仓库组的评审,需要把.code-reviews/路径换成动态配置。这个我目前还在规划,但核心数据结构设计已经支持。
  • 评论的富文本支持。Markdown 本身支持图片和链接,如果要在评论里贴截图,直接引用内网图片链接即可。

维护建议方面,我最大的心得有两条:

  • 定好备份策略。因为评审数据都在 Git 仓库里,所以备份整个仓库目录就等同于备份了评审数据。我用每天晚上打一个 bundle 到另一台机器的方式,简单可靠。
  • 写好接口文档。CLI 和 Web 面板之间的交互既有"文件格式约定"又有"命令调用约定",不写清楚的话,三个月后的你会骂现在的你。

写在最后的一点体会

自己动手写代码评审工具,这件事在很多人看来可能有点傻,毕竟现成方案一大把。但整套系统做完、真正跑了一年后,我反而更坚定这个方向是对的。小团队的需求往往是市面上任何工具都无法完全满足的,与其花时间研究复杂配置、想办法绕开限制,不如做一个完全贴合自己使用习惯的东西。它可能不够完美,却处处顺手。

最后分享一个小技巧:如果你也打算做类似的内部工具,第一个版本千万不要追求大而全。把我上面写的 CLI 和 Git Hook 这两块先跑起来,哪怕只是把评审请求变成仓库里的 Markdown 文件,这个过程你都会对代码评审本身产生新的理解。之后再慢慢补 Web 面板、通知、统计,一步一步来,成就感是完整拿满的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询