从 CI 到 CD:一次 Spring Boot 项目自动化工作流实践
编辑
前言
在软件开发过程中,有很多重复性的工作,比如在提交代码前手动运行测试,或者在发布版本时手动打包、写更新记录、上传文件。这些操作不仅耗时,还容易出错。
GitHub Actions 是一个可以帮助我们自动完成这些任务的工具。这篇文章记录了我的一个 Spring Boot3 (w/ Kotlin) 项目的自动化流程配置实践。从持续集成 (CI) 开始,实现代码提交后自动进行测试;然后更进一步,实现持续交付 (CD),在项目源码有变动时每天自动发布新版本。
建立 CI 工作流
自动化的第一步是建立持续集成(CI)。它的主要作用是,当有新代码提交或合并时,系统能自动进行构建和测试。这可以很早地发现代码中的问题,保证项目代码的质量。
首先需要在项目里创建一个 CI 工作流。在项目根目录下,创建 .github/workflows/ci.yml
文件,并写入以下内容:
# .github/workflows/ci.yml
name: Spring Boot 3 Kotlin CI
# 触发条件:当有代码推送到 main 分支,或向 main 分支发起合并请求时
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 第一步:获取代码
- name: Checkout repository
uses: actions/checkout@v4
# 第二步:设置 JDK 环境
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
# 第三步:设置 Gradle (会自动处理依赖缓存)
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
# 第四步:执行构建和测试
- name: Build and test with Gradle
run: ./gradlew build
这个文件定义了一个基础的自动化流程。它会在指定的事件发生时,自动检出代码、准备好开发环境,并执行 build
命令来完成编译和测试。这样,所有代码变更都会经过一次自动检查。
实现自动发布
CI 保证了代码的质量,但软件的最终目的是交付给用户。在代码测试通过之后,发布版本是下一个关键步骤。手动发布版本涉及很多繁琐的操作,比如确定版本号、整理更新记录、打包、上传等,这些也很适合用程序来自动完成。
接下来,我们将 CI 的概念延伸到持续交付(CD),创建另一个工作流来专门负责版本的自动发布。
规划自动发布流程
在开始配置前,我们先明确一下这个自动发布流程需要做什么:
定时运行:每天在固定时间自动启动。
检查是否重复:如果当天的版本已经发布过,就停止运行。
检查是否有更新:如果上次发布后没有新的代码提交,也停止运行。
自动生成版本号:根据日期生成版本号,例如
2025.07.27
。自动生成更新记录:从 Git 提交历史中提取两次发布之间的所有变更,形成更新记录。
自动打包和发布:将上面所有信息和打包好的文件整合,在 GitHub 上创建一个新的 Release。
解构自动发布工作流
根据上面的规划,我们创建第二个工作流文件 .github/workflows/release.yml
。这个工作流由两个任务组成。
1. 检查任务
第一个任务的作用是进行前置检查,判断是否需要执行发布。它会先根据当前日期生成版本号,然后使用 GitHub 的命令行工具 gh
查询这个版本的 Release 是否已经存在。如果已存在,整个工作流就会停止,以避免重复发布。
2. 执行任务
只有在检查任务通过后,第二个任务才会开始执行。它首先会再次进行检查,通过 git log
命令查看是否有新的代码提交。如果没有,任务同样会停止,以避免发布一个内容没有任何变化的新版本。
如果确认有新的代码提交,任务就会继续执行以下步骤:运行 ./gradlew bootJar
命令打包项目,并把动态生成的版本号传递给构建过程。最后,它会使用一个社区提供的 Action,将版本号、自动生成的更新记录,以及打包好的 Jar 文件整合起来,在 GitHub 上创建一个标准的 Release。
整合与应用
现在,项目里有了两个工作流文件,它们有各自明确的分工。
文件一:.github/workflows/ci.yml
(用于代码检查) 它的作用是在代码合并到主分支之前,对每次的提交和合并请求进行自动测试,确保代码质量。
文件二:.github/workflows/release.yml
(用于版本发布) 它的作用是在代码合并到主分支之后,按照预定的时间(每日),自动执行版本发布流程。
# .github/workflows/release.yml
name: Daily Build and Release
on:
schedule:
- cron: '0 16 * * *' # 每天 UTC 16:00 (北京时间 00:00) 运行
workflow_dispatch:
jobs:
check:
name: Check if release is needed
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check_release.outputs.should_run }}
version: ${{ steps.generate_version.outputs.VERSION }}
steps:
- name: Generate Version
id: generate_version
run: |
VERSION=$(date +'%Y.%m.%d')
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
- name: Check if Release for today already exists
id: check_release
run: |
if gh release view ${{ steps.generate_version.outputs.VERSION }} > /dev/null 2>&1; then
echo "should_run=false" >> $GITHUB_OUTPUT
else
echo "should_run=true" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-and-release:
name: Build and Create Release
needs: check
if: needs.check.outputs.should_run == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Generate Release Notes and Check for Changes
id: generate_info
shell: bash
run: |
if ! git describe --tags --abbrev=0 > /dev/null 2>&1; then
CHANGELOG=$(git log --pretty=format:"* %s (%h) - %an")
else
LATEST_TAG=$(git describe --tags --abbrev=0)
CHANGELOG=$(git log ${LATEST_TAG}..HEAD --pretty=format:"* %s (%h) - %an")
fi
if [[ -z "$CHANGELOG" ]]; then
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
{
echo 'CHANGELOG<<EOF'
echo "$CHANGELOG"
echo 'EOF'
} >> "$GITHUB_ENV"
fi
- name: Build with Gradle
if: steps.generate_info.outputs.has_changes == 'true'
run: ./gradlew bootJar -PnewVersion=${{ needs.check.outputs.version }} --build-cache
- name: Create GitHub Release
if: steps.generate_info.outputs.has_changes == 'true'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.check.outputs.version }}
name: Release ${{ needs.check.outputs.version }}
body: ${{ env.CHANGELOG }}
files: build/libs/*.jar
为了让 release.yml
能正确地设置 Jar 包的版本,还需要修改项目中的 Gradle 配置文件 build.gradle.kts
,使其能接收命令行传来的版本号:
// build.gradle.kts
version = project.findProperty("newVersion")?.toString() ?: "0.0.1-SNAPSHOT"
下面是通过 Github Action 生成的 Release:
总结
通过以上配置,我们为项目建立了一套完整的自动化流程。CI 工作流保证了日常开发的质量,CD 工作流则处理了版本发布的繁琐工作。将这些重复性任务交给机器,可以让我们更专注于功能开发,同时也让整个开发和发布过程更加规范和高效。
果然,懒惰是第一生产力(。
- 0
- 0
-
分享