GithubActions

GithubActions
可爱可倾GitHub Actions
GitHub Actions 是 GitHub 提供的 CI/CD 服务,支持自动化构建、测试、部署等工作流。
下面只介绍基础,常用的 Action 及其用法请参考官方文档。
| Action | 说明 |
|---|---|
| actions/checkout | 签出代码 |
| actions/setup-node | 设置 Node.js 环境 |
| actions/setup-python | 设置 Python 环境 |
| actions/setup-java | 设置 Java 环境 |
| actions/cache | 缓存依赖,加速构建 |
| actions/upload-artifact | 上传构建产物 |
| actions/download-artifact | 下载构建产物 |
| softprops/action-gh-release | 自动创建和发布 GitHub Release |
| stefanzweifel/git-auto-commit-action | 自动提交并推送代码 |
| ad-m/github-push-action | 半自动推送代码 |
| Mattraks/delete-workflow-runs | 清理历史运行记录 |
1. 语法基础
1.1 Action 引用语法
(uses)
语法:uses: {owner}/{repo}@{ref}
@{ref} 决定了使用哪个版本的 Action,优先级如下:
- Tag (推荐): 比如
@v3,@v3.1.0。
- 特点:版本固定,稳定性高,推荐生产环境使用。
- Commit SHA (最安全): 比如
@f2a1d8...。
- 特点:不可篡改,供应链安全级别最高,但可读性差,无法自动获取更新。
- Branch (慎用): 比如
@main,@master,@latest。
- 特点:只是开发者维护的一个分支(Branch),代码随时变动。
1.2 npm vs npx
- npm (Node Package Manager):
管理工具。侧重于安装、卸载、更新包,管理
package.json。 - npx (Node Package Execute): 执行工具。
- 特性:可以直接运行
node_modules/.bin下的命令,或者运行未安装的包(临时下载,运行完即删)。 - 场景:
npx prettier --write .(无需全局安装 prettier).
1.3 npm install vs
npm ci
在 CI 环境中,必须使用 npm ci。
| 特性 | npm install |
npm ci (Clean Install) |
|---|---|---|
| 依据文件 | package.json |
package-lock.json |
| 锁文件行为 | 如果不一致,会修改 lock 文件 | 严格遵守,不一致直接报错 |
| 前置动作 | 尝试增量安装 | 直接删除 node_modules |
| 速度 | 较慢(需解析依赖树) | 极快(跳过解析,直接下载) |
| 适用场景 | 本地开发 | CI/CD 环境 |
2. 多语言环境配置与缓存
2.1 Node.js
(actions/setup-node)
不要手动使用 actions/cache,直接利用
setup-node 的内置缓存。
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm' # 根据 package-lock.json 生成缓存 Key
2.2 Python
(actions/setup-python)
支持 pip, pipenv, poetry
的缓存,支持 Version Range(如 3.x 会自动选最新的 3.x
版本)。
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
architecture: 'x64' # (可选) x64 或 x86
cache: 'pip' # 根据 requirements.txt 生成缓存 Key
- run: pip install -r requirements.txt
2.3 Java
(actions/setup-java)
Java 有很多发行版,使用时 必须指定
distribution** 参数,推荐使用 Temurin
- name: Setup Java JDK
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin' # 必填 (temurin, zulu, corretto)
cache: 'gradle' # 支持 maven 或 gradle
3. 存储策略:Cache vs Artifacts
这两者经常被混淆,但用途完全不同。
| 特性 | Cache (缓存) | Artifacts (构建产物) |
|---|---|---|
| 对应 Action | actions/cache (或 setup-xx 内置) |
actions/upload-artifact &
download-artifact |
| 主要用途 | 存储依赖
(node_modules),加速下一次构建。 |
存储结果 (.exe, .jar,
dist/),用于部署或下载。 |
| 持久性 | 临时。7天未命中或总量超10GB自动删除 (LRU)。 | 持久。默认保留 90 天 (可配置)。 |
| 跨 Job 访问 | 同一分支及子分支可读。 | 任何 Job 均可下载,支持 UI 界面手动下载。 |
| 计费 | 通常不计费 (GitHub 托管)。 | 占用存储配额 (私有仓库超额收费)。 |
构建产物传递示例: Job A 编译 -> Upload -> Job B Download -> 发布。
在 GitHub Actions 中,每个 Job
都是在一个全新的、隔离的虚拟机中运行的。 必须使用
upload 和 download
来“传递”这些文件。详见附录为什么要上传和下载构建产物(Artifact)?
3.1
actions/upload-artifact (上传)
作用:将当前 Job 生成的文件(编译结果、测试报告、日志)打包上传到 GitHub 服务器。
常用参数:
name: 产物名称(后续下载要用)。path: 要上传的文件或目录路径(支持通配符)。retention-days: 保留几天(默认 90 天,建议设短一点省空间)。
示例:
- name: Upload Build Artifact
uses: actions/upload-artifact@v6
with:
name: my-build-files # 给这个包起个名
path: dist/ # 上传 dist 目录下的所有内容
retention-days: 1 # 只保留1天,传给下一个Job用完就行
3.2
actions/download-artifact (下载)
作用:在另一个 Job 中,下载之前上传的产物,以便进行部署或分析。
常用参数:
name: 对应上传时的名称。如果不填,会下载所有产物。path: 下载到哪里(默认解压到当前目录)。
示例(配合上面的 Upload):
- name: Download Build Artifact
uses: actions/download-artifact@v7
with:
name: my-build-files # 必须和 upload 的名字一致
path: build-files/ # 下载并解压到这个目录
4. Git 自动化操作 (Auto Push)
4.1 核心前提:权限
默认 Token 通常只有读权限。若要 Push,必须在 Workflow 顶部或 Job 级添加:
permissions:
contents: write
或者去 Settings -> Actions -> General 修改为
Read and write permissions。
4.2 方案对比:全自动 vs 半自动 vs 手动
| 需求 | 推荐 Action | 说明 |
|---|---|---|
| 懒人/简单场景 | stefanzweifel/git-auto-commit-action |
全自动。自动检测变更、add、commit、push。 |
| 精细控制/动态消息 | ad-m/github-push-action |
半自动。只负责 Push,需要写 git commit
命令。 |
| 极简主义/复杂钩子 | 原生命令 (Shell) | 手动。完全自定义 git 命令。 |
全自动配置示例:
- name: Apply changes & Push
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: "chore: update stats [skip ci]"
branch: ${{ github.head_ref }}
半自动配置示例:
- name: Commit changes
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add .
# 可以使用 shell 变量拼接 commit message
git commit -m "Update data: $(date) [skip ci]"
- name: Push changes
uses: ad-m/github-push-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
手动配置示例:
- name: Commit and Push
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "Auto update [skip ci]"
git push
4.3 防止 CI 无限循环 (Infinite Loop)
当 Action 推送代码后,新的 Commit 可能会再次触发
on: push,导致死循环。 解决方法:在 Commit
Message 中包含以下关键词之一:
[skip ci][ci skip][no ci]
5. 自动创建与发布 Release (Release Automation)
Release 通常包含构建产物(如 .exe,
.zip,
.jar)以及更新日志(Changelog)。
5.1 核心逻辑
- 触发时机:通常由 打标签 (Git Tag)
触发。例如,当推送一个
v1.0.0的标签时,Action 自动运行。 - 执行动作:编译代码 -> 打包文件 -> 在 GitHub 创建 Release 页面 -> 上传文件。
5.2 推荐工具
- Action:
softprops/action-gh-release - 理由: 社区事实标准,配置简单,支持通配符文件上传,支持自动生成更新日志。
5.3 完整配置示例
这是一个生产环境可用的配置,包含了从“触发”到“构建”再到“发布”的全过程。
文件: .github/workflows/release.yml
name: Build and Release
on:
push:
tags:
- "v*" # 仅当推送以 v 开头的标签时触发 (如 v1.0.0)
permissions:
contents: write # 必须!否则无法创建 Release
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
# 模拟构建步骤 (需要替换为真实的构建命令)
- name: Build Project
run: |
mkdir -p build
echo "This is the binary content" > build/app-v1.0.0.exe
zip -r build/source-code.zip . -x ".git/*"
- name: Create Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
# 1. 自动根据 PR 标题生成更新日志 (强烈推荐)
generate_release_notes: true
# 2. 上传的文件 (支持通配符)
files: |
build/*.exe
build/*.zip
# 3. 如果标签包含 'beta',标记为预发布版本
prerelease: ${{ contains(github.ref, 'beta') }}
# 4. 自定义 Release 标题 (默认使用 Tag 名)
# name: Release ${{ github.ref_name }}
5.4 关键参数详解
| 参数 | 说明 | 推荐配置 |
|---|---|---|
files |
指定要上传到 Release页面的文件路径。支持多行和 glob 通配符。 | 务必确认构建产物的路径正确。 |
generate_release_notes |
神器。让 GitHub 自动根据两次 Tag 之间的 Pull Request 标题生成 Changelog。 | true (省去手动写日志的麻烦) |
body |
手动指定 Release 的描述文本。 | 如果开启了 generate_release_notes,通常不需要。 |
draft |
是否存为草稿(不直接对外发布)。 | 生产环境建议 false,调试时 true。 |
prerelease |
是否标记为 “Pre-release”(黄色标签)。 | 配合 contains 函数动态判断。 |
6. 自动化维护与清理
6.1 Dependabot 配置
防止 PR 轰炸,合并更新。
文件:.github/dependabot.yml
version: 2
updates:
# 🤖 更新 GitHub Actions 版本
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly" # 指定更新频率
time: "10:00" # 指定更新时间
timezone: "Asia/Shanghai" # 指定时区
open-pull-requests-limit: 10 # 限制同时打开的PR数量
labels: # 为Dependabot创建的PR添加标签
- "dependencies" # 添加一个名为"dependencies"的标签
- "automerge" # 添加一个名为"automerge"的标签,需要配合工作流
# ignore: # 忽略的依赖
# allow: # 允许的依赖
groups:
actions:
patterns:
- "*"
6.2 清理历史运行记录
方式 A:GitHub 原生设置 (基于时间)
- 位置: Settings -> Actions -> General -> Workflow retention settings.
- 功能: 设置保留天数(如 90 天)。
- 局限: 无法按“数量”保留。
方式 B:使用 Action (基于数量/状态)
- 推荐:
Mattraks/delete-workflow-runs - 场景: 即使时间没到,也想删除那些 Failed 的记录,或者只保留最近 3 条记录以保持界面整洁。
- 示例:
- uses: Mattraks/delete-workflow-runs@v2
with:
token: ${{ github.token }}
repository: ${{ github.repository }}
retain_days: 1 # 超过1天的都删
keep_minimum_runs: 3 # 但至少保留最近3个
7. 进阶控制与技巧
7.1 并发控制 (Concurrency)
场景:连续 Push 了 3 次代码。
- 默认行为:GitHub 会同时启动 3 个 Job 并行跑。
- 后果:
- 浪费构建分钟数。
- 如果涉及部署,可能会导致旧代码覆盖新代码(Race Condition)。
- 如果是 Auto-Push,可能会产生 Git Lock 报错。
解决方案:配置
concurrency,让新任务自动取消旧任务。
# 在 workflow 顶层配置
concurrency:
# 组名:通常以 workflow 名 + 分支名 组合
group: ${{ github.workflow }}-${{ github.ref }}
# 核心:一旦有新提交,立即取消正在运行的旧任务
cancel-in-progress: true
7.2 定时任务 (Cron) 的陷阱
on:
schedule:
- cron: '0 0 * * *'
- 时区:GitHub Actions 永远使用 UTC 时间。
- 换算:北京时间 (UTC+8) 需要减去 8
小时。例如想在北京时间 早上 8 点 运行,Cron 应写为
0 0 * * *(00:00 UTC)。在 GitHub 资源紧张时,定时任务可能会延迟 10-30 分钟启动,不要用于秒级精度的任务。
7.3 步骤间传参 (Outputs)
场景:步骤 A 生成了一个版本号(如
v1.2.3),步骤 B 需要用这个版本号来命名文件。不能直接用
Shell 变量,因为步骤间环境是隔离的。
steps:
- name: Generate Version
id: meta # ⚠️ 必须给步骤起个 id
run: |
VERSION="v$(date +%Y%m%d)"
# 写入环境变量文件
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Use Version
# 使用 ${{ steps.ID.outputs.KEY }} 获取
run: echo "The generated version is ${{ steps.meta.outputs.version }}"
7.4 数据库服务 (Service Containers)
场景:测试代码(如 Node.js + MariaDB)需要连接真实的数据库,而不是 Mock。 方法:直接在 Job 里启动一个临时的 Docker 容器。
jobs:
test:
runs-on: ubuntu-latest
# 定义服务
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test_db
ports:
- 3306:3306
# 健康检查 (确保数据库启动后再跑步骤)
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- uses: actions/checkout@v6
- name: Run Tests
# 代码里直接连 localhost:3306 即可
run: npm test
env:
DB_HOST: 127.0.0.1
DB_USER: root
DB_PASS: root
7.5 条件控制 (if 的妙用)
不仅仅是
if: github.ref == 'refs/heads/main',还有很多状态判断。
- 即使前面报错也要运行 (比如发送报警通知):
if: failure()
- 无论成功失败都要运行 (比如清理环境):
if: always()
- 仅在主仓库运行:
if: github.repository == 'my-name/my-repo'
7.6 万能脚本
(actions/github-script)
通常如果想调用 GitHub API(比如给 Issue 评论、合并
PR、打标签),需要写 curl 命令(很难读)或者专门写一个
Node.js 脚本文件并安装 octokit 依赖(很麻烦)。
Github Script 把这些都封装好了,只需要写核心逻辑。
三大内置对象:
github: 预认证的 API 客户端 (Octokit)。context: 当前运行的上下文(包含仓库信息、触发者、Issue 内容等)。core: 用于设置 Action 的输出变量或标记失败。
使用场景:
- 简单的 API 交互(如自动欢迎新用户)。
- 根据复杂的条件触发逻辑。
简单示例 (给当前的 Issue 自动评论 “Hello”):
- name: Comment on Issue
uses: actions/github-script@v7
with:
script: |
// 直接调用 GitHub API,无需配置 token
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '👋 Thanks for reporting!'
})
8. 安全与调试
8.1 Secrets vs Variables
- Secrets (
${{ secrets.XXX }}): 加密存储。日志中显示为***。用于存 Token、密码。 - Variables (
${{ vars.XXX }}): 明文存储。用于存非敏感配置(如 API URL、构建标志)。
8.2 开启调试模式
当 Action 莫名失败时,不要瞎猜。
- 在仓库 Settings -> Secrets 中添加一个
Secret:
ACTIONS_RUNNER_DEBUG值为true。 - 重新运行 Job,日志会输出极详细的 Debug 信息(包括文件路径、具体命令参数等)。
8.3 矩阵构建 (Matrix)
如果需要测试代码在不同 Node 版本下的兼容性:
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
这会并行启动 3 个 Job 运行
9. 总结:这些 Action 怎么串起来?
一个典型的多语言、多步骤工作流通常是这样组合的:
- checkout: 拿出代码。
- setup-node/python/java: 准备环境(顺便自动开启 cache 加速)。
- run build: 编译代码。
- upload-artifact: 把编译好的文件(如
dist/或.jar)传上去。 - (另一个 Job) download-artifact: 把文件下载下来。
- action-gh-release: 把下载下来的文件发布到 Release 页面。
- delete-workflow-runs: 最后清理一下运行记录。
附录
为什么要上传和下载构建产物(Artifact)?
1. 核心原因:Job 之间的“失忆”机制
在 GitHub Actions 中,每一个 Job(作业)都是在不同的、全新的虚拟机(Runner)上运行的。
- Job 1 (Build):系统启动一台虚拟机
A。下载代码,编译生成了
dist/app.exe。当这个 Job 结束时,虚拟机 A 会被直接销毁,里面的所有文件(包括刚编译好的代码)都会消失。 - Job 2 (Release):系统启动一台全新的虚拟机
B。这是个空房子,里面连代码都没有,更别提虚拟机 A 里生成的
app.exe了。
Artifact(工件)的作用就像是一个“云端中转站”或“网盘”:
- Upload: 虚拟机 A 在销毁前,把
dist/上传到云端存起来。 - Download: 虚拟机 B 启动后,先从云端把这个文件下载下来,然后才能发布。
2. 为什么不把所有步骤写在一个 Job 里?
可能会问:“那不分 Job 不就行了?从头跑到尾,不就不需要上传下载了吗?”
确实可以(对于简单项目),但对于典型的、复杂的项目,拆分 Job 有巨大的优势:
- A. 并行构建(Matrix Builds)——最常见的原因
假设需要同时发布 Windows、Linux 和 Mac 版软件。
单 Job 模式:得先在 Windows 上跑,跑完换 Linux 跑……这太慢了。
多 Job 模式:可以启动 3 个 Job 同时跑(并行)。
Job Win: 编译 -> Upload
win.exeJob Lin: 编译 -> Upload
linux.binaryJob Mac: 编译 -> Upload
mac.appRelease Job: 等上面 3 个都跑完 -> Download All -> 一次性发布所有文件。
B. 权限与安全
构建 Job:通常只需要读取代码的权限,即使脚本被黑客篡改,也无法修改的 Release 页面。
发布 Job:需要写入权限(发布 Release)。
将它们分开,可以限制高权限 Token 仅在最后的发布环节使用,降低风险。
C. 容错与重试
如果“构建”花了 30 分钟成功了,但在“上传 Release”这一步网络抖动失败了:
- 单 Job:必须重头开始,再花 30 分钟重新编译一遍。
- 多 Job:只需要点击“Re-run”那个失败的 Release Job,它会直接去下载之前编译好的文件(Artifact),几秒钟就能重试完成。





