构建 Spring Boot
这一课你将能做到
- 写出完整的 backend job:
checkout → setup-java(Temurin 21 + Maven 缓存)→ ./mvnw -B verify
- 用
actions/upload-artifact 保存 backend/target/*.jar,并在 Actions 页下载验证
- 为依赖 MySQL 的集成测试配置
services: 服务容器,并解释它与 Testcontainers 的取舍
为什么要学
本站的 backend 是 Maven + Spring Boot。若 CI 只跑前端,Java 侧的编译错误、测试失败会在合并后才爆——那时排查成本已经翻倍。
本地 IDE 一点绿不代表干净 Linux runner 上也能绿:缺 Wrapper 执行权限、JDK 版本漂移、测试依赖的外部 MySQL 未就绪,都是高频坑。CI 是一位只用命令行的同事——没有 IDE,只认 mvnw 与 JDK;你让他跑的,应和本地终端同一条命令。
本课把「Java 侧最低门禁」写成可运行的 job,与 spring-boot 轨道的 Wrapper 习惯对齐。
先建立图像
job: backend(ubuntu-latest)
1. checkout 仓库
2. setup-java Temurin 21 + cache maven
3. working-directory: backend → ./mvnw -B verify
├─ compile(编译)
├─ test(跑测试)
└─ package/verify(打 jar 及扩展校验)
4. upload-artifact → target/*.jar(给人或其他 job 用)
| 本地 | CI 对应 |
|---|
| IDE 里点 Run | ./mvnw -B verify |
| 本机早已装好 JDK | setup-java |
~/.m2 积攒了半年依赖 | cache: maven |
手动翻 target/ 目录 | upload-artifact |
跟着做
步骤 1:确认本地 Wrapper 能跑
在仓库根:
cd backend
chmod +x mvnw # Linux/mac;Windows 用 mvnw.cmd
./mvnw -v
./mvnw -B test # 先保证本地测试可过
本地不过,CI 几乎必红——先修项目再修 YAML。
步骤 2:写入 backend job
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
backend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- name: 拉取代码
uses: actions/checkout@v4
- name: 安装 Temurin 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
cache: maven
- name: 测试并打包
run: |
chmod +x mvnw
# -B 批处理模式:关掉下载进度条,日志更适合 CI
./mvnw -B verify
- name: 上传 JAR
uses: actions/upload-artifact@v4
with:
name: backend-jar
# path 相对仓库根
path: backend/target/*.jar
if-no-files-found: error
推 PR 后:Actions 中 backend 应变绿;run 页 Artifacts 区可下载 jar。
步骤 3:故意制造失败看门禁
临时改坏一个测试断言或引入编译错误 → push → 看 job 红叉与日志。再在 PR 页确认检查失败。改回后复绿。
排错口诀:红日志里的第一个 ERROR 往往比最后几行更重要——Maven 失败时堆栈从第一个 [ERROR] 开始看。
步骤 4:理解为什么选 -B verify
test:编译并跑测试,不一定执行后续校验
package:打 jar
verify:在 package 之后执行绑定的校验(如集成测试、静态检查插件)
本站入门用 verify 作「严一点的默认」。若太慢,先保证正确再拆分级:PR 只跑 test,main 上再 verify。
步骤 5:服务容器跑 MySQL(集成测试可选)
当测试需要真 MySQL 而不是 H2 时,给 job 加 services::
jobs:
backend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
services:
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: codeforge
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1 -uroot -prootpass"
--health-interval=10s
--health-timeout=5s
--health-retries=10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
cache: maven
- name: 集成测试
env:
# 与 application-test 的数据源对齐(示例)
SPRING_DATASOURCE_URL: jdbc:mysql://127.0.0.1:3306/codeforge
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: rootpass
run: |
chmod +x mvnw
./mvnw -B verify
--health-cmd 让 job 等 MySQL 真正就绪再开跑,避免「连不上就闪崩」。密码仅用于短暂 CI 环境,勿与生产 secrets 混用。更现代的路线是 Testcontainers(测试代码自己起容器),本课知道两者取舍即可。
步骤 6:与 platform job 并列
同一 workflow 再加 platform job(下一课),二者不写 needs,并行。分支保护将来同时勾选 backend 与 platform 两个检查(第 11 课)。
讲清楚要点
- 因为 CI 环境无 IDE,所以用 Maven Wrapper 锁定构建方式——
./mvnw 自带指定 Maven 版本,减少「我这边 Maven 3.9」式差异。
- 因为 JDK 版本必须与项目一致,所以
setup-java 显式写 21——与 pom 的 maven.compiler.release 对齐;不确定就查 pom。
- 因为依赖下载贵,所以开
cache: maven——第二次起明显加速,但测试照跑。
- 因为 monorepo 根不是 Java 根,所以
working-directory: backend——报 pom.xml not found 时先查目录。
- 因为产物要给人或其他 job 用,所以 jar 用 artifact 上传——不要依赖 runner 磁盘跨 job 残留;
if-no-files-found: error 防止「静默没传」。
- 因为集成测试可能依赖外部进程,所以用 services(或 Testcontainers)并配健康检查——避免测在 MySQL 未就绪时闪崩。
你可能会懵的地方
-
「mvnw Permission denied?」
Linux runner 上先 chmod +x mvnw;更彻底的是把可执行位提交进 Git(git update-index --chmod=+x backend/mvnw),一劳永逸。
-
「artifact 里混进了 sources/javadoc jar?」
用更精确的 path(如 backend/target/backend-*.jar)或排除模式;配合 if-no-files-found: error 保证产物非空。
-
「本地 21,CI 要不要测 17?」
与项目约定为准,YAML 与文档写死同一版本;要验证兼容性可用 matrix java: [17, 21] 一次看清。
-
「verify 太慢怎么办?」
先保证正确,再分级:PR 快速 job 只 test;verify 留给 main;夜间任务跑全量。paths 过滤也能减少无谓触发。
-
「services 里的密码算不算 secret?」
它只在这次 run 的临时容器里存在,跑完销毁,不进 Git——这是可接受的。不安全的是把它写进提交的配置文件里。
错法对照
| 错法 | 问题 | 正解 |
|---|
| CI 脚本里写本机 JDK 绝对路径 | runner 上不存在 | setup-java |
只 compile 不跑测 | 假绿 | 至少 test,推荐 verify |
| 假设 runner 上已有 MySQL | 连接失败 | services / Testcontainers / H2 profile |
artifact path 写 target/*.jar | 忘了 backend 前缀,找不到文件 | backend/target/*.jar |
忘记 chmod +x mvnw | Permission denied | chmod 或提交可执行位 |
卡点急救
./mvnw: Permission denied:step 里加 chmod +x mvnw;根治是把可执行位提交进 Git。报错出现在 verify 之前说明还没跑到构建,先解决权限。
- 集成测试连不上 MySQL(Connection refused):查 services 的
ports 映射与测试配置的主机地址(容器内服务经 127.0.0.1:3306 访问);确认 health check 通过后才开始测试;密码、库名与测试 profile 的环境变量逐项核对。
- upload-artifact 报
No files were found:path 是相对仓库根的——monorepo 必须写 backend/target/*.jar;确认 verify 真的跑到了 package 阶段(先前的 step 失败会中断后续)。
面试常问
-
Maven 的生命周期中 test、package、verify 有什么区别?CI 里该用哪个?
test 编译并执行单元测试;package 在此基础上打 jar;verify 再执行绑定的集成校验(如 failsafe 集成测试、质量插件门槛)。CI 门禁推荐 verify(严)或按分级策略 PR 用 test、main 用 verify,兼顾速度与严格性。
-
CI 中 Java 项目最容易踩的坑有哪些?
高频四件套:① Maven Wrapper 无执行权限——提交可执行位;② JDK 版本与 pom 不一致——setup-java 显式钉版本;③ 依赖外部服务未就绪——services/Testcontainers + 健康检查;④ 缓存误用导致旧依赖——key 绑定 pom 哈希。能按「环境差异、时序、依赖」三类归纳会更加分。
-
services 和 Testcontainers 怎么选?
services 是 workflow 级声明,配置简单、与语言无关,但生命周期粗(整个 job 一份);Testcontainers 在测试代码里起容器,随用随起、版本参数化,表达力强,但要求 runner 有 Docker 且测试代码引入额外依赖。小项目或固定依赖用 services 够了;测试矩阵需要多版本数据库时 Testcontainers 更灵活。
自测三问
- 写出 backend job 的标准四步(含可选上传)。
- 为什么推荐
./mvnw 而不是假设全局 mvn?
- job 之间如何拿到打好的 jar?路径为什么必须写
backend/target/?
小结一句话
./mvnw -B verify(加 JDK 21 与 Maven 缓存)是本站 Java 后端 CI 的最低门槛;jar 用 artifact 交接。
延伸