Java 8老系统AI Code Review实战:规则扫描+AI分析双层防线,Git合并前先过审 📅 2026/6/20 14:22:14 为什么要写这个系列做Java后端十年我接触过不少企业的核心系统。金融、电商、政务——行业不同但底层的现状惊人地相似生产系统还在Java 8框架停在Spring Boot 2.x甚至更早代码跑了很多年没人敢轻易动。去年开始几乎每个项目都在谈接AI。但真正动手的时候团队就卡住了。不是因为不懂大模型而是老系统本身接不住。JDK版本不够Spring AI引不进来依赖树牵一发动全身升级一个包怕带崩一片生产流量压着不敢拿主流程赌一个AI试点。更危险的是硬塞。我见过团队在老系统的Service层直接new一个HttpClient调模型APIPrompt拼在业务代码里超时没配、降级没有。模型响应慢的时候老系统的订单查询线程池被占满主流程跟着卡住。还有团队把用户手机号、身份证原样送进Prompt过了两个月才被安全审计发现。这种事见多了我就开始想一个问题老系统不具备直接接入AI的条件这是不是大多数企业的常态答案是肯定的。而且这不应该成为接不了AI的理由。核心思路其实就三条老系统少改 —— 不升级 JDK不引入 Spring AI 依赖 AI 能力旁路 —— 独立部署老系统通过 HTTP 或 MQ 调用 企业边界先行 —— 脱敏、审计、降级、幂等比模型调用更重要这个系列就是把这三条线展开成10讲。从AI Gateway到MCP工具中心从SQL Agent到RAG知识库从工单Agent到多Agent研发团队——每一讲都围绕同一个前提你的老系统还在跑Java 8你不能为了AI去赌它的稳定性。每讲配套一个可运行的Maven Lab不讲空架构不写Hello World。你跑得通Demo看得到边界拿得到代码。这就是我做这个系列的原因。Java 8老系统AI Code Review实战规则扫描AI分析双层防线第4讲我们建立了一张结构化的代码地图扫描源码 → 识别分层 → 生成调用链 → 输出阅读路线有了这张地图团队至少知道一个业务动作经过了哪些地方。但代码每天都在变。新人会提交代码老项目会修Bug业务会加需求AI也会生成一堆看着像那么回事的实现。这时候第4讲的代码阅读地图帮不上了——因为它分析的是项目现状不是代码变更。你需要的是在代码合并之前有人先帮你扫一眼这次改动有没有低级风险 有没有可能影响老系统稳定性 有没有漏掉该加的校验这就是本讲要解决的问题。但在开始之前先说清楚一件事AI Code Review 不是替团队做决策更不是让 AI 自动改代码。它适合做的是第一道过滤器把那些容易漏、容易犯、容易在加班时被忽略的低级问题在合并前提出来。最终效果代码目录code/spring-ai-enterprise-lab/labs/chapter05-ai-code-review运行.\compile-and-run.ps1跑完后能看到解析sample.diff识别空指针风险、参数校验缺失、库存校验TODO输出结构化Markdown Review预留AI分析接口当前由Stub透传输入Git Diff本讲用样例 sample.diff 模拟输出结构化 Markdown Review - 风险等级 - 文件 - 行号 - 风险类型 - 原因 - 建议企业边界- 只输出建议不自动提交代码 - 高风险问题标记需人工确认 - 规则扫描和 AI 分析双层检查 - 输出结构化方便接入 GitHub / Gitee 评论为什么不能只靠模型做Review在讨论怎么实现之前先想清楚一个问题为什么Code Review不能直接把Diff丢给模型不是因为模型不够好而是因为Review里有两类完全不同的任务。第一类模式匹配型空指针风险 → 新增代码里有没有 request.getXxx() 没判空 SQL 注入风险 → 有没有字符串拼接 SQL 敏感字段 → 有没有打印手机号、身份证 事务边界 → 有没有写库操作没包事务这些问题有规律不需要理解业务。它们需要一个确定性的规则引擎而不是一个概率模型。第二类语义理解型这段代码的设计意图是什么 这个 catch 掉异常会不会掩盖上游问题 这个 clientVersion 兼容分支是不是该删了这类问题需要上下文、需要解释、需要基于项目事实给出自然建议。这才是模型擅长的事。所以本讲的核心观点是AI Code Review 应该是规则扫描和 AI 分析的双层防线。确定性规则兜底安全问题AI 负责理解意图和补充上下文。这一讲先跑通第一层——规则扫描管道。第二层AI分析留好接口后续接入Spring AI时替换Stub即可。样例Diff本讲使用一个经过设计的Diff -1,6 1,10 public String createOrder(OrderRequest request) { String userId request.getUserId(); String productId request.getProductId(); if (request.getQuantity() 0) { throw new IllegalArgumentException(quantity must be positive); } // TODO check product stock later return orderService.createOrder(userId, productId, request.getQuantity()); }这段代码看起来没什么问题但至少有三个风险点第一个问题直接调用request.getUserId()和request.getProductId()但没有判断request是否为空。如果上游传了一个null这里直接NPE。第二个问题创建订单前留下了库存校验TODO。// TODO check product stock later说明团队知道这里要加校验但还没加。第三个问题新增的两个userId、productId参数没有做非空和格式校验。如果它们是必填字段说明校验逻辑还在别的地方——或者还没写。这三个问题都是规则扫描能识别、也应该识别的。它们不依赖模型也不依赖对业务的深度理解。架构设计本讲链路Git Webhook / Push ↓ GitDiffParser ↓ ┌──────────────────────────┐ │ 第一层规则扫描 │ │ 空指针 / SQL 注入 / 事务 │ │ 敏感字段 / 参数校验 / 异常 │ └──────────────────────────┘ ↓ ┌──────────────────────────┐ │ 第二层AI 分析预留 │ │ 语义理解 / 上下文补充 │ │ 自然建议 / 风险解释 │ └──────────────────────────┘ ↓ ReviewCommentFormatter ↓ Markdown Review和第1-3讲一样AI不直接接入老系统流程。老系统的代码变更通过Git Webhook进来进入独立的Review管道规则引擎先做确定性扫描AI再做语义分析最终输出结构化评论。这一讲当前只实现了第一层和第二层的接口。Stub模式让管道先跑通后续替换Stub为真实模型调用时不需要改管道代码。代码结构src/main/java/com/ynzz/lab/chapter05 ├── Chapter05Demo.java ├── diff │ ├── GitDiffParser.java │ ├── DiffModel.java │ └── ChangedLine.java ├── review │ ├── RuleBasedRiskScanner.java │ ├── AiReviewService.java ← 接口 │ ├── StubAiReviewService.java ← Stub 实现 │ ├── CodeReviewService.java │ ├── ReviewFinding.java │ ├── ReviewResult.java │ └── ReviewCommentFormatter.java └── webhook ← 本讲新增 ├── GitWebhookRequest.java └── GitWebhookController.java子模块后续扩展git-webhook-adapter/ ← Webhook 签名校验、平台适配 diff-analysis-service/ ← 规则引擎 AI Review 服务化 review-comment-publisher/ ← 评论发布到 GitHub / Gitee第一层规则扫描本讲Demo实现了三个规则。规则一空指针风险扫描逻辑如果新增代码调用了 request.getXxx() 并且没有出现 request null 或 request ! null → 标记空指针风险规则二库存校验缺失扫描逻辑如果新增代码中出现 TODO 且包含 stock 关键词 → 标记库存校验缺失这个规则很朴素但它表达了一个重要原则Review不仅要关注代码写了什么还要关注代码应该写但没写什么。规则三必填参数无校验扫描逻辑如果新增代码提取了 userId、productId 等业务参数 但没有出现对应的 null 判断或 StringUtils.isEmpty → 标记参数校验缺失完整的规则维度维度检测内容风险等级空指针风险request.getXxx() 无判空MEDIUM参数校验缺失业务参数无 null/empty 检查MEDIUMSQL 注入风险字符串拼接 SQL 语句HIGH事务边界写库操作无 TransactionalHIGH敏感字段暴露日志打印手机号/身份证HIGH异常吞没catch 块只有 log 无 rethrowLOW当前Demo实现了前三个。后续规则可以通过扩展RuleBasedRiskScanner#scan逐个加入不影响现有逻辑。第二层AI分析预留AiReviewService接口定义publicinterfaceAiReviewService{/** * 基于规则扫描结果生成 AI 补充分析。 * 当前 Stub 实现透传原有 finding不修改。 */ListReviewFindingenrich(ListReviewFindingfindings,DiffModeldiff);}这个设计有几个好处1. 管道代码CodeReviewService不感知是 Stub 还是真实模型 2. 规则扫描的结果是 AI 分析的输入两层不互相替代 3. 后续接入 Spring AI 只需替换一个实现类 4. 即使 AI 不可用规则扫描仍然独立工作这和第4讲事实层vs表达层的分工一致规则负责确定性的事实检测AI负责非确定性的语义表达。运行效果运行Demo后输出### AI Review 风险等级MEDIUM #### 1. 可能的空指针风险 - 文件OrderController.java - 位置第 2 行 - 原因新增代码直接读取 request 字段但没有判断 request 是否为空。 - 建议在进入业务逻辑前增加请求对象校验。 #### 2. 参数校验缺失 - 文件OrderController.java - 位置第 3 行 - 原因新增代码提取了 userId、productId 业务参数但没有做非空或格式校验。 - 建议在 Service 入口或 Controller 层增加必填参数校验。 #### 3. 库存校验缺失 - 文件OrderController.java - 位置第 7 行 - 原因新增代码留下库存校验 TODO创建订单前可能没有真实校验库存。 - 建议在 Service 层增加库存检查Controller 不承载业务规则。企业避坑第一AI Review不要自动改代码。本讲只输出Markdown评论不生成Patch不自动提交。AI在Review流程里的角色是建议者不是执行者。第二高风险问题必须进入人工确认。涉及支付、退款、库存、权限的改动不能让AI自动判断通过。规则扫描标记HIGH等级后必须有人在合并前确认。第三规则扫描不能丢。空指针、SQL注入、敏感字段、事务边界这些问题应该有确定性规则兜底。不能因为有了AI就把规则引擎撤掉。两者是互补关系不是替代关系。第四Review结果必须结构化。至少要包含文件、行号、风险等级、风险类型、原因、建议。结构化输出方便接入GitHub/Gitee评论API也方便后续统计分析。第五不要只扫描新增行。真实Review需要关注上下文行和删除行的语义。比如删掉了一个null判断只靠行看不出来。第六Review要和CI流水线集成。单独跑一个Review脚本价值有限。更好的嵌入点是PR创建 → Webhook触发 → Review管道 → 评论回写PR。从Demo到落地还差什么Webhook真实接入GitHub/Gitee当前Demo用本地sample.diff模拟输入真实项目需要GitWebhookController接收平台Webhook校验签名拉取Diff再触发Review管道。规则配置化当前规则硬编码在RuleBasedRiskScanner里。企业场景需要把规则抽成配置文件YAML或JSON让不同项目、不同团队自定义规则集合和风险等级。AI分析接入Spring AI当前StubAiReviewService只做透传。接入真实模型后AI需要基于Diff上下文解释风险原因、补充业务场景——但这些都是在规则扫描已有结果的前提下做增强。上下文行分析当前只扫描新增行。真实diff的上下文行和删除行也需要纳入分析否则会漏掉删掉了一个null判断这类问题。Review结果回写Git平台当前只输出到控制台。落地时需要把结构化Review结果以行级评论的形式发布到PR对应位置。高危变更自动升级涉及支付、退款、权限模块的变更应该自动将Review等级提升为HIGH并通知指定Reviewer。小结规则扫描负责确定性检测 AI 分析负责语义理解 两层互补不互相替代从第4讲的读懂老项目到第5讲的审查代码变更这个系列正在从理解系统走向保护系统。下一步第6讲会把企业文档接入RAG知识库——让AI不只是理解代码还能回答业务问题。AI Code Review 的目标不是替代 Reviewer而是让 Reviewer 把精力从找低级错误中解放出来去关注更重要的设计决策。