首页 / 资讯中心 / 文章详情

从diff分析到规则引擎:自托管代码审查工具open-code-review实战

从diff分析到规则引擎:自托管代码审查工具open-code-review实战 ★ FEATURED ARTICLE
1. 项目来源与初始思路1.1 我为什么还要再做一款代码审查工具先说清楚一个背景我所在团队大概二十来人维护着四五个微服务仓库日常开发流程是标准的 GitLab MR 人工审查。听起来没什么问题但真正跑起来之后痛点一个接一个冒出来。最让我受不了的是两件事。第一MR 一多reviewer 根本看不过来很多 MR 从提起到合并要卡两三天而真正等代码审核的时间可能只有十几分钟其余时间全在等人。第二代码风格、基础规范、明显的低级错误比如把密码硬编码进配置文件、日志里打印了敏感字段、空指针风险、数据库查询忘加 limit这些问题其实完全可以靠工具自动拦下来却非要消耗人肉去 review。我统计过一个月的数据团队大概有 37% 的 review 评论是在重复说同一类低级问题。当时我也试过市面上的方案。商业 SaaS 产品成本高、数据要出网我们这边合规上过不去开源的通用 lint 工具比如 ESLint、Checkstyle确实能抓一部分问题但它们是“规则引擎”不是“审查流程”只能告诉你代码哪里不符合规范却无法回答“这个 MR 改了哪些高风险文件”“谁应该被拉进来审查”“这次改动跟最近的故障有没有关联”。所以我想做一件事搭一个自托管的、能和现有 Git 平台深度集成的代码审查辅助层把机械性的检查交给机器把真正需要人判断的问题留给人类。这个项目就是open-code-review一个开源的、面向团队协作的代码审查辅助平台。1.2 它到底解决什么问题open-code-review 不是什么颠覆性的东西它做的事情说白了就是三件在 MR/PR 创建时自动拉取 diff跑一轮静态分析和自定义规则把低风险但高频的问题直接标出来reviewer 打开页面就能先看到机器的结论。根据改动文件的范围和内容自动推荐合适的 reviewer避免“每个 MR 都艾特所有人”这种低效模式。提供审查数据看板让技术负责人能看清每个仓库、每个人的 review 负载和响应时间及时发现流程里的瓶颈。从定位上说它更像一个“审查流程的辅助驾驶”而不是“自动驾驶”。它不替代人工审查也不强制任何规范而是把机械劳动接走让人的注意力集中在架构设计、业务逻辑、潜在风险这些真正需要判断力的地方。适用对象我总结下来有三类一是受困于 review 阻塞的开发团队二是同时维护多个仓库、需要统一审查口径的平台组三是想给开源项目加上自动化审查能力的维护者。如果你只是一个人写代码这个工具帮助有限它天生是为协作场景设计的。2. 核心功能拆解与技术选型2.1 三大核心能力的实现逻辑先讲功能再讲技术这样好理解我为什么做这些选型。diff 分析与规则引擎这是整个项目的基石。每次 MR 触发后服务会向 Git 平台 API 拉取该 MR 的 diff 数据解析成标准化的文件修改列表。然后规则引擎逐文件、逐 diff 块执行检查。规则分几层基础层文件路径匹配比如禁止.env文件提交、禁止在src/main/resources下出现config类敏感文件。代码层针对不同语言做 token 级检查比如 Python 的assert不能用于生产校验、Java 里避免printStackTrace、JS 里禁止eval。语义层需要结合上下文判断比如新增了数据库查询但没有加 limit、API 接口新增字段但没有写文档注释。规则引擎我采用了“管道 插件”的架构。管道负责串联不同检查阶段插件提供具体能力。每种语言一个插件插件内部可以调用对应语言的 linter 暴露的结果也可以自己写精确匹配。这样新增语言支持不需要改核心代码只要写一个插件。reviewer 自动推荐这部分当时考虑了很久。最朴素的方案是看历史代码归属用 git blame 统计数据谁最近改动过这个文件谁就是潜在 reviewer。但这样做有个问题新人刚来的头几周没有任何 blame 记录他们改的代码会被系统判为“无人认领”。所以我用了一个混合策略。先设一个基准分文件历史修改者权重最高目录 owner 文件次之最近合并过相邻模块的同事再降一档最后用随机加权补足新人。实际效果还算满意推荐的命中率在七成左右剩下三成由人工调整。审查数据看板看板不是核心功能但它是推动工具落地的关键。人都是看数据说话的如果工具上线后拿不出一组“节省了多少时间、拦截了多少问题”的数字很难说服团队持续使用。我就用定时任务每天把 MR 状态、review 耗时、评论数、工具拦截数汇总到一张表里再用一个轻量图表页展示。2.2 技术栈选择的几个关键考量技术选型方面我遵循一个原则尽量选团队已经熟悉、生态成熟、部署轻量的东西不追求新潮。后端Go。主要原因是想做成单二进制部署方便在各种 CI 环境里跑。而且 Go 写并发任务很顺手diff 分析师有时要同时处理几十个仓库的扫描请求goroutine 天然合适。数据库PostgreSQL。数据量不大但关系复杂MR、文件、规则命中、reviewer 推荐记录之间有很多关联查询PG 的 JSONB 类型也让规则配置变灵活。任务队列Redis 简单的轮询。没有引入重量级消息队列是因为单机部署场景下 Redis 足够而且部署时少一个依赖就少一个出问题的环节。前端Vue 3 Element Plus。管理后台性质的应用不需要太重的框架Vue 的上手成本对后端开发者友好。反向代理Caddy。自动 HTTPS配置简洁比 Nginx 省心很多。这里有个容易被忽视的点和 Git 平台的集成方式决定了整个系统的复杂度。open-code-review 没有选择为每一种平台写适配器而是定义了一套统一抽象GitLab、GitHub、Gitea 各自实现对应的客户端。第一次接入用 GitLab因为团队内部最熟后续再补其他平台只需要按接口规范实现。注意如果是企业内部使用GitLab 的版本差异会带来一些兼容问题。比如老版本 MR webhook 的事件格式和新版本不完全一样解析时一定要做字段存在性判断不能假设所有版本行为一致。2.3 数据模型设计里的几个取舍MR 审查系统的实体不算复杂但关系要理清楚。核心表我设计了六张repositories仓库元信息包括平台类型、项目 ID、Webhook Secret。merge_requestsMR 主表存储标题、源分支、目标分支、状态、发起人。review_comments工具生成的审查评论关联 MR 和具体文件位置。rule_hits规则命中记录比评论更细粒度一条 MR 可能命中多条规则但可以汇总成一条评论。reviewer_suggestions推荐 review 记录包含推荐理由和用户是否采纳。scan_tasks扫描任务表记录每次 diff 分析的状态失败时保存错误信息方便排查。这里我踩过一个坑一开始把rule_hits和review_comments合并成一张表结果发现规则命中和最终评论往往不是一一对应比如 5 条同类型命中可能聚合为 1 条评论。拆开后数据更清晰后续做统计和运维也方便。关于性能我的经验是不要提前优化。刚开始只部署一个实例、一个 PostgreSQL撑住一个几十人团队的扫描量完全没问题。单个仓库的 diff 扫描时间控制在 3 秒以内几千行的大 MR 也就十几秒这个量级根本不需要上分布式。3. 部署配置与接入实操3.1 用 Docker Compose 快速拉起整个服务我尽量让部署过程保持“一条命令跑起来”。项目根目录提供了docker-compose.yml包含三个服务postgres、redis、ocr-server。克隆代码后修改.env文件里的数据库密码、Git 平台地址和回调密钥执行docker compose up -d就能启动。version: 3.8 services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: ocr POSTGRES_PASSWORD: change-me POSTGRES_DB: open_code_review volumes: - pgdata:/var/lib/postgresql/data restart: unless-stopped redis: image: redis:7-alpine command: redis-server --appendonly yes volumes: - redisdata:/data restart: unless-stopped ocr-server: image: open-code-review/server:latest ports: - 8080:8080 environment: DB_DSN: hostpostgres userocr passwordchange-me dbnameopen_code_review sslmodedisable REDIS_ADDR: redis:6379 GIT_PLATFORM: gitlab GIT_BASE_URL: https://gitlab.example.com GIT_ADMIN_TOKEN: token WEBHOOK_BASE_URL: https://review.example.com depends_on: - postgres - redis restart: unless-stopped volumes: pgdata: redisdata:实际部署时有几个细节可以注意一下GIT_ADMIN_TOKEN建议使用只读权限的 tokenopen-code-review 只需要读取仓库信息和 MR 数据不需要写仓库的权限。用最小权限 token即使泄露影响也可控。WEBHOOK_BASE_URL是回调地址需要能从 GitLab 服务器访问到。如果你把 open-code-review 部署在内网要确保 GitLab 所在网络能通到这个地址。首次启动后访问http://server-ip:8080会进入初始化页面要求配置管理员账号。这里的密码策略我建议至少 12 位因为部署在公网的实例很容易被扫描器盯上。3.2 配置文件逐项说明除了环境变量系统还支持一个 YAML 配置文件用来定义审查规则和策略。默认配置在config/default.yml核心内容如下server: port: 8080 database: dsn: hostpostgres userocr passwordchange-me dbnameopen_code_review sslmodedisable redis: addr: redis:6379 git_platform: type: gitlab base_url: https://gitlab.example.com admin_token: xxxx # Webhook 回调相对路径会拼上 WEBHOOK_BASE_URL webhook_route: /webhook/gitlab review_rules: # 扫描哪些类型的文件 include_paths: - **/*.go - **/*.java - **/*.py - **/*.js - **/*.ts - **/*.vue exclude_paths: - **/vendor/** - **/node_modules/** - **/dist/** - **/target/** # 规则列表 rules: # 硬编码密钥检测 - id: hardcoded_secret name: 检测硬编码的敏感信息 description: 识别代码中的 AK/SK、密码等敏感信息的硬编码 level: error # error / warning / info engine: regex pattern: (?i)(password|passwd|secret|token)\\s*[:]\\s*[\][^\]{6,}[\] # 禁止提交的敏感文件 - id: sensitive_file name: 敏感文件禁止入库 description: .env、.pem、.key 等文件不允许提交到仓库 level: error engine: path pattern: (\\.env$)|(\\.pem$)|(\\.key$)|(id_rsa) # 空指针风险提示 - id: null_dereference name: 空指针风险提示 description: 检测未判空就调用的高风险写法目前只做基础启发式规则 level: warning engine: regex pattern: (\\w)\\.(get|find|query|fetch)\\(\\)\\.(get|value|execute)\\( # 调试代码残留 - id: debug_printf name: 调试输出残留 description: 检测常见的调试打印语句 level: warning engine: regex pattern: (console\\.log|System\\.out\\.print|print\\() # TODO 统计只标记 INFO - id: todo_left name: 遗留 TODO description: 提示代码中仍有 TODO 未清理 level: info engine: regex pattern: TODO|FIXME|HACK规则引擎设计的几个层次我在开发过程中反复调过优先级。error级别的命中会直接阻止 MR 合并如果开启了门禁必须人工确认warning只做标记不阻断流程info纯粹是信息提示让开发者心里有数。这里容易犯的错是一开始把太多规则设成error结果团队怨声载道后来我调整了策略第一版只把“硬编码密钥”和“敏感文件入库”设为 error其余全部默认 warning等人接受这个工具了再逐步收紧。这个策略救了我一命。工具上线第一周如果每天弹几十条阻断性错误开发同学直接就把 webhook 关了。3.3 接入 GitLab 并下发第一条审查任务配置好服务之后接入仓库大概是四步。第一步在 GitLab 上创建一个项目访问令牌权限只需要read_api和read_repository。因为 open-code-review 需要拉取 MR diff需要这两个权限才能正常工作。第二步在 open-code-review 后台添加仓库。填写仓库地址和项目 ID系统会自动从 GitLab 读取仓库信息并注册 webhook。第三步去 GitLab 对应项目里确认 webhook 已经创建成功。GitLab 会在 webhook 列表里显示一条指向 open-code-review 服务的记录。可以点测试按钮发送一个测试事件观察 open-code-review 日志里是否收到请求。第四步创建一条测试 MR。修改一个文件、加一个明显的调试输出然后提交 MR。正常情况下系统会在十几秒内自动在 MR 下追加一条评论告诉你有几条 error、几条 warning以及具体在哪一行。这是第一次跑通时最有成就感的瞬间。整个流程从环境部署到看见第一条自动评论熟练的话大概半小时内能完成。# 创建一条测试 MR在测试仓库里操作 git checkout -b test-review echo console.log(test debug); test.js git add test.js git commit -m test: add debug output for review git push origin test-review # 在 GitLab 网页上创建 MR等十几秒刷新页面MR 下方会出现类似这样的自动评论open-code-review 扫描完成发现 2 个 warning、0 个 error。test.js:1debug_printf调试输出残留 (console.log)test.js:1todo_left遗留 TODO看到这条评论出来整个链路就通了。4. 常见问题与排查技巧实录4.1 权限类问题webhook 收到事件但扫描无结果上线前测试时我遇到过最诡异的情况GitLab 后台的 webhook 测试是绿的发送事件也显示成功但 open-code-review 这边一条日志都没有。排查思路我最终确定是按这个顺序走的。先看服务日志里有没有收到 webhook 请求如果没收到就说明是网络层问题检查反向代理配置和防火墙规则。如果收到了那大概率出在消息解析上——GitLab 不同的 webhook 事件Push Hook、Merge Request Hook、Note Hook数据格式差异很大解析时如果按一个固定的 JSON 结构去取字段很容易取到空值。我当时的问题是 GitLab 配置的 Secret Token 和 open-code-review 环境变量里的不一致导致 webhook 请求被默认拒绝。这个错误级别只打了一行 debug 日志不仔细看根本发现不了。建议部署初期把日志级别调成 debug跑通主流程后再改回 info。4.2 大仓库扫描超时diff 分析性能优化另一个高频问题是仓库太大几百个文件的 MR 提交过来扫描任务经常超时。我的解决方案分了三步走。第一步给扫描任务加了一个 Google 风格的并发控制每个仓库同时只跑一个任务其余排队避免多个大 MR 同时进来把 CPU 打满。第二步将超大 diff 按文件类型做裁剪只扫描配置里include_paths命中的文件vendor、node_modules、target这类目录直接跳过。第三步设置单次扫描的 diff 行数上限超过一万行的 MR 自动降级为“抽样扫描”只检查高风险路径上的文件。这三步做完线上最慢的扫描从 60 秒降到了 8 秒左右而单次扫描的命中率并没有明显下降。顺便说一句exclude_paths里的目录一定要在项目配置初期就维护好否则后期加规则时会把一堆第三方代码误报成问题。4.3 规则误报太多如何调出“准而不吵”的规则规则引擎最头疼的问题就是误报。我试过把硬编码密钥的正则写宽结果大量password字段的关键字被误伤也试过把某些语言的内置 API 当成业务代码扫产生了一堆没价值的噪音。后来我总结出几条调规则的实战经验宁可漏报不可误报。误报率一高开发者就会对工具的评论免疫最后变成“点掉不看”。想找回信任非常难。每一条规则都要有“真实案例支撑”。我是这么操作的看到一条真实事故或一次线上故障才把对应的检测规则模型抽象出来而不是凭想象写规则。规则分阶段上线。先作为info观察一周统计触发频率和人工确认率如果确认率高再升到warning确认率低于三成的规则直接回炉重练。用“白名单”对冲不确定规则。比如敏感文件检测允许仓库根目录放一个.ocr-whitelist文件把确实需要提交的特殊文件列进去。4.4 团队落地的三个关键动作技术问题解决之后真正的难题是让人愿意用。我见过太多内部工具死在“没人看、没人反馈、最后悄悄下线”这个循环里。我的经验是做好三件事。第一件事是上线第一天就拉出“第一批战果”。我们上线第一周工具拦截了两个生产事故级别的风险——一个是有同事把数据库连接串明文提交到了公开仓库另一个是某次批量删除操作忘加WHERE条件。我把这两个案例截图发到团队群里效果比任何动员都有说服力。第二件事是建立一个“规则反馈群”。任何人觉得规则误报、漏报或者需要新增规则都可以在群里提。我每周五汇总一次能改的当场改不能改的说明原因。这个习惯保持了三个月规则库从最初的 18 条增长到 46 条误报率从 12% 降到 3%。第三件事是别把工具做成“卡点”。很多人会用它来做强制门禁比如 MR 必须 0 error 才能合并。但我的建议是第一版千万别这么干。先让工具以“辅助”的角色存在等人习惯了再看要不要开强制。一旦强制你会瞬间站到所有人的对立面。我们是在上线两个月后、团队反馈普遍正向的前提下才正式启用了 error 级阻断门禁。5. 项目后续可以怎么扩展5.1 接入 CI 管道和更多平台目前 open-code-review 的核心流程是“webhook 触发 → 扫描 → 评论”这决定了它只能处理“已经创建出来的 MR”。我下一步计划是让它在 CI 管道里也能跑比如在 GitLab CI 里提供一个ocr-check命令由各项目在.gitlab-ci.yml中显式调用这样扫描结果就能直接反映在 pipeline 状态上而不是只出现在评论里。# 示例在 GitLab CI 中集成 open-code-review review: stage: test image: open-code-review/cli:latest variables: OCR_SERVER_URL: https://review.example.com OCR_REPO_ID: 123 script: - ocr-cli scan --mr $CI_MERGE_REQUEST_IID --output json only: - merge_requests平台侧GitHub 的 App 版本也在规划中。GitHub 的审查生态和 GitLab 差别很大API 调用方式、权限模型、webhook 签名方式都不一样需要重新写一层适配但核心的 diff 分析引擎和规则引擎可以直接复用。5.2 把规则引擎做成可共享的生态代码审查规则这种东西通用场景其实很多但每个团队又有自己的个性化需求。我想做的是一个类似“规则市场”的目录让不同团队可以把自己积累的规则包导出、分享、导入。规则格式目前是 YAML内部做了一版简单的 schema 校验后续再加一层版本管理规则包就能像 npm 包一样被安装和升级了。这个功能的技术难度不高难的是规则本身的质量。我计划后续把规则仓库独立出来维护一套贡献指南和审核流程至少保证共享出去的规则是经过真实项目验证过的而不是拍脑袋写的正则。6. 最后分享几点开发中的体会这个项目从第一行代码到现在前后用了大约三个月的业余时间。技术上的收获固然有比如 diff 分析的处理、并发任务编排、与 Git 平台 API 对接时那些文档里没写清楚的细节但我更想说的是另外几件事。一个是做内部工具最忌讳“把自己当成平台的替代品”。open-code-review 从头到尾都没有打算替代 GitLab、GitHub 自带的 code review 功能它做的是“辅助”。这个定位想清楚之后很多设计上的问题都迎刃而解了——我不用去重写评论 UI不用做 MR 列表页只需要把数据送给现有的交互界面。另一个是规则类工具的生命力在于规则本身而不在于框架。框架再漂亮没有高质量的规则堆上去还是空壳子。所以哪怕功能再忙我都坚持每周花几小时看看真实项目里新出现的 bug 和事故把它们沉淀成新规则。这个过程有点像“把团队的教训代码化”。最后一点如果你是个人开发者想做个类似的工具我建议一定要找到一个真实的使用场景和一两个愿意陪你把第一版跑通的人。没有真实场景驱动很容易做出一个技术完美但没人用的东西而有人愿意用哪怕第一天只有两三个人整个开发的动力和方向感都会完全不一样。这个项目目前还在持续演进中欢迎对代码审查、团队工具链感兴趣的朋友一起交流。说不定你实际用起来之后踩到的第一个坑就是我还没遇到过的那种。
阅读完成 · 觉得有帮助?
咨询建站