返回博客

上线前的最后一道闸:Spring Boot 六阶段验证流水线

很长一段时间里,我的「发布前检查」就是 mvn clean package 跑一遍,绿了就推。这个做法不是没用,但它只保证了「能编译」,并不保证「能上线」。

直到一次生产故障——一个本地跑得好好的接口,上线后因为依赖版本里的 CVE 漏洞被扫出来紧急回滚,才让我认真思考:发布前究竟应该做哪几件事,才算「真正确认过了」?

六个阶段,缺一不可

第一阶段:构建(Build)

最基础的门。用并行模式加速:

mvn -T 4 clean verify -DskipTests

-DskipTests 是因为这一步只验「能编译」,测试单独在下一阶段跑。构建失败立刻停下来,不往后走。

第二阶段:静态分析(Static Analysis)

SpotBugs 找空指针解引用和线程安全问题,PMD 找过于复杂的方法,Checkstyle 保持代码风格一致。三者指向不同维度的问题,联合起来才完整。

mvn -T 4 spotbugs:check pmd:check checkstyle:check

这一步很多团队觉得「没什么大用」跳过。我的经验是:SpotBugs 找出的问题,有相当一部分是真实 bug,不是误报。

第三阶段:测试 + 覆盖率

三类测试各有分工:

  • 单元测试:用 Mockito 隔离依赖,验 Service 层的业务逻辑。速度快,反馈快。
  • 集成测试:用 Testcontainers 拉起真实数据库容器,验 Repository 层的 SQL 行为。H2 内存库和生产库行为有差异,Testcontainers 能规避这个坑。
  • Controller 测试:用 @WebMvcTest + MockMvc 验接口的状态码、响应体格式、参数校验行为。

覆盖率用 JaCoCo 输出报告,设 80% 行覆盖的门禁。这个数字不是绝对标准,但它能暴露「哪些路径从来没被走过」。

Testcontainers 比 H2 更诚实

H2 的 SQL 方言和 MySQL/PostgreSQL 存在差异——某些在生产数据库才触发的 SQL 错误,在 H2 里根本不会出现。Testcontainers 会在测试时启动一个真实的数据库容器,测试结束后自动销毁,几乎零配置成本,但覆盖度远高于内存数据库。

第四阶段:安全扫描

两条线并行:

一是依赖 CVE 扫描,用 OWASP Dependency Check:

mvn org.owasp:dependency-check-maven:check

二是代码层面的敏感信息扫描,硬编码密码、API Key、通配符 CORS 这类问题不会被编译器抓到,要用 grep 主动搜:

grep -rn "allowedOrigins.*\*" src/main/ --include="*.java"
grep -rn "e\.getMessage()" src/main/ --include="*.java"

e.getMessage() 直接塞进响应体是个常见坏习惯——生产环境的异常信息可能包含 SQL 语句、文件路径、内部服务名,全都不该暴露给调用方。

第五阶段:代码格式

用 Spotless 统一格式,消除 PR Diff 里的噪音:

mvn spotless:apply

格式问题不是 bug,但格式混乱的 Diff 会掩盖真正的逻辑变更。

第六阶段:Diff 复查

这一步是人工关卡:git diff --stat 看变更范围,再读一遍 diff,确认没有:

  • 忘删的调试日志(System.out.println、无条件的 log.debug
  • 遗漏 @Transactional 的写操作
  • 配置变更没有文档说明

把它变成习惯,而不是仪式

六个阶段听起来重,但实际上前五个阶段可以全部自动化挂在 CI 里。真正需要人工判断的只有第六阶段的 Diff 复查——而这件事本来就应该由人来做。

一个实用建议:在长时间开发过程中,每 30-60 分钟跑一次轻量子集(只跑测试 + SpotBugs),保持快速反馈节奏。留到最后再一起跑完整六个阶段,往往会发现积累了一堆小问题。

一句话心法

「快速反馈胜过后期惊喜」——每个阶段失败都应该立刻停下来修,而不是带着问题往后走,等到最后一起爆。