ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Git学习笔记:GitHub Actions 从零到实战 - PC2005

2026/8/13 23:15:41 拓冰建站 浏览量
Git学习笔记:GitHub Actions 从零到实战 - PC2005

概述

GitHub Actions 是 GitHub 原生提供的 CI/CD(持续集成/持续部署)平台,它让你在仓库中定义自动化流程,在代码推送、PR 合并、发布版本等事件发生时自动执行构建、测试、部署等任务。

核心概念

GitHub Actions 围绕几个核心概念构建,理解它们是掌握后续所有操作的基础。

概念 类比 说明
Workflow(工作流) 流水线图纸 一个完整的自动化流程,定义在 .github/workflows/*.yml 文件中
Job(任务) 流水线上的工位 Workflow 中的一个任务单元,多个 Job 可并行或串行执行
Step(步骤) 工位上的每个操作 Job 中的每一步,可以是运行一条 shell 命令,也可以引用一个 Action
Action(动作) 预制的工具/机器 可复用的独立功能块,像积木一样在 Step 中组装使用
Runner(运行器) 流水线工人 执行 Workflow 的服务器,由 GitHub 托管或自行管理
Event(触发器) 启动按钮 触发 Workflow 执行的事件——push 代码、创建 PR、发布版本、定时任务等

为什么要学习 GitHub Actions?

  • 免运维:无需搭建 Jenkins 服务器,GitHub 原生托管,开箱即用
  • 生态丰富:GitHub Marketplace 上有上万现成的 Action,无需重复造轮子
  • 与仓库深度集成:PR 状态自动关联、Checks 界面原生展示,协作体验无缝
  • 灵活的触发模型:不限于 push/PR,定时、手动、仓库事件、外部 webhook 皆可触发
  • 矩阵构建:一行配置即可在多个 OS 和语言版本上并行测试
  • 免费额度慷慨:公开仓库完全免费,私有仓库每月 2000 分钟免费

GitHub Actions 与同类工具对比

工具 托管方式 配置格式 学习曲线 生态规模
GitHub Actions 云托管(GitHub) YAML ⭐⭐⭐⭐⭐
Jenkins 自托管为主 Groovy / UI ⭐⭐⭐⭐
GitLab CI 云托管 / 自托管 YAML ⭐⭐⭐
CircleCI 云托管 YAML ⭐⭐⭐

前置准备

环境要求

环境 说明
Git 本地已安装,用于代码推送
GitHub 账号 注册于 github.com
一个 Git 仓库 本次实验使用 D:\Projects\test,需推送至 GitHub 远端

项目结构约定

D:\Projects\test              ← 实验仓库根目录
├── .github/
│   └── workflows/            ← 所有 Workflow 文件存放在此(YAML 格式)
├── src/                      ← 实验用测试代码
└── ...

注意.github/workflows/ 是 GitHub Actions 的固定目录,只要将 .yml 文件放入此目录并推送到 GitHub,Actions 就会自动识别并执行。

Workflow 文件的基本骨架

无论多复杂的流水线,它的起点都是这样一个 YAML 文件:

# .github/workflows/文件名.yml
name: Workflow 名称          # 显示在 GitHub Actions 界面上的名称
on: push                     # 触发事件jobs:job-id:                    # Job 的唯一标识runs-on: ubuntu-latest   # 运行环境steps:- run: echo "Hello World"   # 一条 shell 命令就是一个 Step

后面所有章节的实战,都是在这个骨架基础上不断添加功能。


第一部分:Hello World —— 第一个 Workflow

万事开头难,但 GitHub Actions 的 Hello World 出奇地简单。你只需要一个 YAML 文件,推送到仓库,剩下的交给 GitHub。

1. 先看最小的 Workflow 长什么样

创建一个文件 .github/workflows/hello.yml,写入:

name: Hello World
on: [push]jobs:say-hello:runs-on: ubuntu-lateststeps:- run: echo "Hello World!"

只有 9 行。推送这个文件到 GitHub,Actions 就会自动执行。来,逐行拆解每个关键词的意思:

关键词 什么意思
1 name 给你的流水线起个名字,显示在 GitHub 的 Actions 页面上
2 on 触发器——什么时候开始跑?[push] = 一推送代码就跑
4 jobs 一个 Workflow 可以包含多个 任务,这里只定义一个
5 say-hello 任务 ID,自己取的名字,后面可以用它做依赖
6 runs-on 运行环境——用什么机器跑?ubuntu-latest = Ubuntu 最新版
7 steps 这个任务的所有步骤列表
8 run 执行一条 shell 命令,这里就是 echo "Hello World!"

关键规律:缩进 + 冒号 + 短横线。YAML 语法就这三个结构。上面 9 行,你已经看懂了 80% 的 Workflow 语法。

2. 理解每个部分在干什么

把上面那个 9 行的 Workflow 翻译成大白话:

name: "这条流水线叫 Hello World"
on: "什么时候跑?——代码推送到仓库时"jobs:say-hello: "我要定义一个叫 say-hello 的任务"runs-on: "用 Ubuntu 的最新版系统来跑"steps: "这个任务分几步走"- run: "第一步:在终端里执行 echo 'Hello World!'"

3. 给它加一个关键动作:检出代码

上面那个 Workflow 有个问题——它没有代码。Runner 上只是一个空目录。

加一行 uses 来引入 GitHub 上现成的工具:

steps:- uses: actions/checkout@v4    # ← 新加的一行- run: ls -la                   # 现在能列出仓库的文件了

actions/checkout@v4最常用的 Action,作用就是把代码下载到 Runner 上。没有它,后面所有命令都找不到你的文件。

参数 含义
uses 引用一个现成的 Action,相当于安装了一个工具
actions/checkout@v4 官方提供的"代码检出"工具,@v4 是版本号

4. 再了解一个概念:${{ }} 上下文变量

GitHub Actions 会自动给你提供一些信息,比如谁触发的、什么分支。用 ${{ }} 这个语法来读取它们:

steps:- run: echo "触发者是 ${{ github.actor }}"- run: echo "当前分支是 ${{ github.ref_name }}"

常见的上下文变量

写法 输出什么 例子
${{ github.actor }} 谁推送的代码 PC2005-cloud
${{ github.repository }} 仓库全名 PC2005-cloud/test
${{ github.ref_name }} 分支名 master
${{ github.sha }} 这次提交的 ID b8b2ac7...

5. 组合起来:我们最终推送的版本

把上面的知识点拼在一起,就是完整的 Hello World Workflow:

name: Hello World
on: [push]jobs:say-hello:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4    # 1. 拉代码- run: echo "🎉 Hello World!"  # 2. 打招呼- run: echo "触发者: ${{ github.actor }}"   # 3. 谁触发的- run: echo "分支: ${{ github.ref_name }}"  # 4. 哪个分支- run: ls -la                                # 5. 看看有啥文件

6. 看看实际跑出来的效果

把这个文件推送到 GitHub,你会在仓库的 Actions tab 看到这样的输出:

🎉 Hello World! 第一个 GitHub Actions Workflow 触发了!
触发者: PC2005-cloud
分支: master
提交 SHA: b8b2ac79fee7f71de5b8e05ce5b7862b30d99b0d仓库文件列表:
total 24
drwxr-xr-x  .git
drwxr-xr-x  .github
-rw-r--r--  .gitignore
-rw-r--r--  README.md

7. 小结:这一部分你学会了

概念 一句话记住
name Workflow 的名字
on: [push] 推送代码就跑
runs-on 用什么系统跑
uses 引用现成的 Action 工具
run 执行 shell 命令
${{ }} 读取 GitHub 提供的变量
🎉 Hello World! 第一个 GitHub Actions Workflow 触发了!
触发者: PC2005-cloud
仓库: PC2005-cloud/test
分支: master
提交 SHA: b8b2ac79fee7f71de5b8e05ce5b7862b30d99b0d仓库文件列表:
total 24
drwxr-xr-x 4 runner runner 4096 .git
drwxr-xr-x 3 runner runner 4096 .github
-rw-r--r-- 1 runner runner   99 .gitignore
-rw-r--r-- 1 runner runner  387 README.md

运行总览

指标
Runner 镜像 ubuntu-24.04
Runner 版本 2.336.0
总耗时 14 秒
Job 状态 ✅ success

注意:Runner 的工作目录是 /home/runner/work/<仓库名>/<仓库名>/,每次运行都是全新的环境,运行结束后自动销毁。

6. 小结

第一个 Workflow 虽然简单,但已经包含了 GitHub Actions 的全部核心要素

  • ✅ 一个 YAML 文件定义整个流程
  • ✅ 一个 Event(push)触发执行
  • ✅ 一个 Job 在 ubuntu-latest 上运行
  • ✅ 多个 Step,包含 uses(引用 Action)和 run(执行命令)
  • ✅ GitHub Context 变量注入运行时的动态信息

这就是后续所有复杂 Workflow 的起点。掌握了这个骨架,后面的每一章都是在它之上做加法。


第二部分:触发器实战

触发器就是 Workflow 的"启动按钮"——告诉 GitHub 什么时候开始跑。GitHub 支持几十种触发方式,但最常用的只有三个。

1. 最简单的触发器:on: [push]

你应该还记得第一部分的这个写法:

on: [push]    # 只要有代码推送,就跑

方括号 [push] 表示"事件列表",你也可以写成一行多个事件:

on: [push, pull_request]    # 推送 或 PR 都触发

2. 让它只在你想要的分支触发

不加限制的话,任何分支的推送都会触发。加个 branches 过滤一下:

on:push:branches: [master]     # 只有 master 分支推送才触发

翻译成大白话:

on:                          "什么时候跑?"push:                      "有人推送代码时"branches: [master]       "但只有推的是 master 分支才跑"

常用的过滤参数

参数 意思 例子
branches 只在这些分支触发 [master, develop]
branches-ignore 这些分支不触发 [gh-pages]
paths 只改这些文件才触发 [src/**]
paths-ignore 改这些文件不触发 [**.md]

为什么要过滤?如果只改了 README 也要跑一遍 30 秒的 CI,就是浪费资源和时间。

3. 第二个触发器:Pull Request

代码审查是团队协作的核心,每次有人开 PR 或更新 PR,自动跑一次检查:

on:pull_request:branches: [master]    # 目标分支是 master 的 PR 才触发

Push 和 PR 有什么区别?

触发方式 什么时候触发 典型用途
push 你推送代码到仓库 提交后验证代码能不能编译通过
pull_request 你开 PR 或给 PR 加新提交 合并前检查新代码有没有问题

安全提示:PR 触发器在 fork 仓库场景下 Token 只有只读权限,无法访问 Secrets,这是为了防止恶意 PR 窃取你的敏感信息。

4. 第三个触发器:手动触发

适合"按需执行"的场景——比如部署、数据迁移、清理任务。不需要推送代码,在浏览器里点个按钮就能跑:

on:workflow_dispatch:           # 手动触发inputs:environment:             # 定义一个输入项description: '部署到哪个环境?'required: truedefault: 'staging'type: choiceoptions:- staging- production

手动触发后,在 Actions 页面点击 Run workflow → 选择参数 → 执行。

┌──────────────────────────────────┐
│  Run workflow                    │
│                                  │
│  部署到哪个环境?    [staging  ▼] │  ← choice 选项
│                                  │
│  [✓] 启用调试模式                │  ← boolean 复选框
│                                  │
│        [Run workflow]            │
└──────────────────────────────────┘

5. 三个触发器放在一起

一个 Workflow 可以同时监听多个事件:

on:push:branches: [master]              # 推送 master 触发pull_request:branches: [master]              # PR 发向 master 触发workflow_dispatch:                # 手动触发inputs:environment:type: choiceoptions: [staging, production]

小提示:多种触发方式组合时,用 ${{ github.event_name }} 来区分是哪个事件触发的。下面会讲。

6. 区分不同的事件:if 条件

Workflow 里的每一步可以通过 if 控制"什么时候执行":

steps:- name: 只看 push 事件的信息if: github.event_name == 'push'           # 只有 push 触发时才执行run: |echo "提交者: ${{ github.event.head_commit.author.name }}"echo "提交信息: ${{ github.event.head_commit.message }}"- name: 只看 PR 事件的信息if: github.event_name == 'pull_request'   # 只有 PR 触发时才执行run: |echo "PR 标题: ${{ github.event.pull_request.title }}"- name: 只看手动触发的参数if: github.event_name == 'workflow_dispatch'run: echo "环境: ${{ github.event.inputs.environment }}"

常用的条件写法

写法 含义
if: github.event_name == 'push' 事件类型是 push
if: github.ref == 'refs/heads/master' 分支是 master
if: github.actor != 'dependabot[bot]' 排除机器人提交
if: success() 上一步成功才执行(默认行为)
if: failure() 上一步失败了才执行
if: always() 不管成功失败都执行

7. 完整 Workflow

把所有触发器整合到一个文件里,通过 if 区分不同事件:

name: 触发器实战
on:push:branches: [master, develop]paths-ignore: ['**.md']pull_request:branches: [master]workflow_dispatch:inputs:environment:description: '目标环境'required: truedefault: 'staging'type: choiceoptions: [staging, production]jobs:show-trigger:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- run: echo "📡 触发事件: ${{ github.event_name }}"- if: github.event_name == 'push'run: |echo "分支: ${{ github.ref_name }}"echo "提交者: ${{ github.event.head_commit.author.name }}"- if: github.event_name == 'pull_request'run: |echo "PR 标题: ${{ github.event.pull_request.title }}"- if: github.event_name == 'workflow_dispatch'run: |echo "环境: ${{ github.event.inputs.environment }}"

8. 来看看实际输出

Push 触发(推送代码后自动跑)

📡 触发事件: push
分支: master
提交者: pc2005
提交信息: feat: 添加触发器实战 Workflow

手动触发(在 Actions 页面点按钮)

📡 触发事件: workflow_dispatch
环境: production
调试模式: true

9. 小结

你学到的 一句话记住
on: [push] 推送就跑
branches: [master] 指定分支才跑
paths-ignore 某些文件改了不跑
pull_request PR 开了就跑
workflow_dispatch 点按钮就跑
if: 控制哪一步在什么条件下跑

第三部分:Job 依赖与矩阵构建

一个真实的 CI 流程通常是:先做代码检查 → 跑测试 → 构建产物 → 发通知。这些任务不可能同时跑,但也不能一个一个等——这就需要理解 Job 之间的依赖并行关系。

1. 先看两个 Job 怎么串起来

默认情况下,Workflow 里的多个 Job 是同时跑的。但如果你想让 Job B 等 Job A 跑完再跑,就用 needs

jobs:job-a:                # Job A 先跑steps:- run: echo "我是 A"job-b:needs: job-a        # 等 job-a 完成后才跑steps:- run: echo "我是 B,A 跑完了才轮到我"

翻译成大白话:

jobs:lint:                "一个叫 lint 的任务,先跑"test:needs: lint        "test 任务依赖 lint,等它跑完再跑"build:needs: test        "build 依赖 test,等 test 跑完再跑"

2. 写一个实际的链式流水线

jobs:lint:                # 1. 代码检查steps:- run: echo "🔍 检查代码风格..."- run: echo "检查通过 ✅"build:               # 2. 构建(等 lint 完成后)needs: lintsteps:- run: echo "📦 构建项目..."- run: echo "构建完成 ✅"notify:              # 3. 通知(等 build 完成后)needs: buildsteps:- run: echo "📬 全部完成!"

执行顺序:

lint ──→ build ──→ notify
(1)      (2)       (3)

3. 依赖写法小结

写法 意思
不加 needs 和其他无依赖的 Job 同时跑
needs: lint 等 lint 这一个 Job
needs: [lint, test] 等 lint 和 test 完成

注意:如果依赖的 Job 失败了,当前 Job 默认不会执行。可以用 if: always() 让它在失败时也执行(比如发通知)。

4. 矩阵:一份配置,跑 N 遍

现在的软件经常需要在多个操作系统、多个语言版本上测试。如果为每个组合都写一个 Job,你会复制出大量重复代码。

矩阵就是用来解决这个问题的:

jobs:test:strategy:matrix:os: [ubuntu-latest, windows-latest]    # 2 个 OSnode: [18, 20]                         # 2 个 Node 版本runs-on: ${{ matrix.os }}                  # 运行时动态指定steps:- run: echo "跑 ${{ matrix.os }} + Node ${{ matrix.node }}"

拆开看里面发生了什么:

matrix:os: [ubuntu, windows]       ← 这一列有 2 个值node: [18, 20]              ← 这一列也有 2 个值组合结果 = os × node = 2 × 2 = 4 个 Job 并行跑:Job 1: ubuntu + Node 18Job 2: ubuntu + Node 20Job 3: windows + Node 18Job 4: windows + Node 20

没有矩阵,你得写 4 个几乎一样的 Job;有了矩阵,写 1 个 Job 就够了。

5. 矩阵的几个参数

strategy:matrix:os: [ubuntu-latest, windows-latest]node: [18, 20]fail-fast: false        # 一个组合失败了,其他的继续跑max-parallel: 2         # 最多同时跑 2 个(防止把额度跑光)
参数 默认值 说明
fail-fast true true:一个失败就取消所有。false:继续跑完其余组合
max-parallel 无上限 限制同时跑的 Job 数量

6. 三个操作系统 Runner 怎么选

runs-on: ubuntu-latest      # Linux(免费额度 1 倍消耗)
runs-on: windows-latest     # Windows(2 倍额度消耗)
runs-on: macos-latest       # macOS(10 倍额度消耗)
Runner 用在哪 额度消耗
ubuntu-latest 通用构建、Docker
windows-latest .NET、Windows 桌面应用
macos-latest iOS 构建 10×

7. 完整 Workflow

把依赖链和矩阵组合在一起:

name: Job 依赖与矩阵构建
on: [push]jobs:lint:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- run: echo "🔍 代码检查通过 ✅"test:needs: lintstrategy:matrix:os: [ubuntu-latest, windows-latest]node: [18, 20]runs-on: ${{ matrix.os }}steps:- uses: actions/checkout@v4- run: echo "🧪 ${{ matrix.os }} + Node ${{ matrix.node }} 通过 ✅"build:needs: testruns-on: ubuntu-lateststeps:- run: echo "📦 构建完成 ✅"notify:needs: buildruns-on: ubuntu-lateststeps:- run: echo "📬 全部 Job 执行成功 ✅"

8. 实际运行结果

lint:     🔍 代码检查通过 ✅test:     (4 个并行)ubuntu + Node 18    ✅ubuntu + Node 20    ✅windows + Node 18   ✅windows + Node 20   ✅build:    📦 构建完成 ✅notify:   📬 全部 Job 执行成功 ✅

运行总览

指标
Job 总数 7(1 lint + 4 test + 1 build + 1 notify)
最大并行数 5
总耗时 47 秒
状态 ✅ 全部成功

9. 小结

你学到的 一句话记住
needs Job 之间的"等一等"——A 跑完 B 再跑
strategy.matrix 一次性在所有版本/系统上跑测试
${{ matrix.os }} 运行时取当前组合的值
fail-fast 一个失败要不要停掉其他
ubuntu/windows/macos 三种 Runner,额度消耗不同

第四部分:Marketplace Action 实战

前面几部分用的都是 run(执行 shell 命令)。但大多数时候你不需要自己写命令——GitHub Marketplace 上有上万现成的 Action,拿来就能用。

本部分介绍四个最常用、最实用的 Marketplace Action。

1. 第一个 Action:setup-node

要给仓库配 Node.js 环境,完全不需要自己写安装脚本。用 setup-node 一行搞定:

steps:- uses: actions/setup-node@v4with:node-version: 20

翻译成大白话:

uses: actions/setup-node@v4    "我要用 setup-node 这个工具,版本是 v4"
with:                          "传参数给它"node-version: 20             "Node.js 版本用 20"

这个 Action 自动帮你装好指定版本的 Node.js,还设置了环境变量。之后的 run: npm installrun: node xxx 就直接能用。

2. 第二个 Action:cache

每次跑 CI 都重新 npm install 一遍很慢。用 cache~/.npm 目录缓存起来,下次就能直接从缓存恢复:

steps:- uses: actions/cache@v4id: npm-cache                  # 给这个步骤起个 ID,方便调试with:path: ~/.npm                 # 缓存哪个目录?key: npm-${{ hashFiles('package-lock.json') }}   # 缓存唯一标识

运行机制

第 1 次跑: 缓存没找到 → npm install → 把 ~/.npm 存起来
第 2 次跑: 缓存命中了 → 直接恢复 ~/.npm,跳过 npm install

hashFiles('package-lock.json') 的意思是:根据 package-lock.json 的内容生成一个指纹。依赖变了,指纹就变了,自动用新缓存。

3. 第三个 Action 组合:upload-artifact + download-artifact

前面学了 needs 让 Job 串起来跑,但有个问题——每个 Job 是独立的环境。Job A 构建好的文件,Job B 里看不到。

ci Job (Runner A)               deploy Job (Runner B)
├── 构建完成                     ├── 找不到 dist/ 目录!
└── dist/ 在 A 跑完就销毁了       └── 因为这不是同一台机器

Artifact 就是用来解决这个问题的:跨 Job 传文件

# Job A:上传
- uses: actions/upload-artifact@v4with:name: build-output         # 给产物取个名字path: ./dist               # 上传 dist/ 目录# Job B:下载
- uses: actions/download-artifact@v4with:name: build-output         # 用同一个名字取回来path: ./dist               # 放到 dist/ 目录

传递过程:

Job A: 构建                  Job B: 部署└─ dist/                      ┌─ dist/  ← 拿到同样的文件↓ upload                  ↑ download[blob 存储] ──────────────►

4. 完整 Workflow 组装

把上面四个 Action 和 Node.js 项目组合成一条流水线:

name: Marketplace Action 实战
on: [push]jobs:ci:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4# 装 Node- uses: actions/setup-node@v4with:node-version: 20# 缓存 npm- uses: actions/cache@v4id: npm-cachewith:path: ~/.npmkey: npm-${{ hashFiles('package-lock.json') }}# 安装、测试、构建- run: npm install- run: npm test- run: npm run build# 上传产物- uses: actions/upload-artifact@v4with:name: build-outputpath: ./distdeploy:needs: ciruns-on: ubuntu-lateststeps:# 下载产物- uses: actions/download-artifact@v4with:name: build-outputpath: ./dist- run: cat ./dist/build-info.json- run: echo "🚀 部署完成!(模拟)"

5. 实际运行结果

📦 缓存 npm: 未命中(第一次跑,会缓存起来)
📦 安装依赖...
🧪 运行测试✅ 测试 1: 基础数学通过✅ 测试 2: lodash 正常加载🎉 全部测试通过!
🔨 构建完成
📤 上传产物 build-output.zip (245 bytes)📥 下载产物 build-output...
📦 已下载构建产物:
{"name": "gh-actions-test","builtAt": "2026-07-28T01:03:59.053Z","builtBy": "GitHub Actions"
}
🚀 部署完成!(模拟)

6. 小结

Action 一句话记住
actions/setup-node@v4 帮你装好 Node.js
actions/cache@v4 缓存依赖,避免每次都重新下载
actions/upload-artifact@v4 把文件传给另一个 Job
actions/download-artifact@v4 从另一个 Job 接收文件

第五部分:自定义 Action

Marketplace 上有上万 Action,但总有你找不到的。当你想把一组常用的步骤打包在不同 Workflow 里复用,就需要写自己的 Action。

1. 什么时候该写自定义 Action?

先看一个例子:你发现多个 Workflow 都在做同样的操作:

# Workflow A
- run: |echo "检查代码..."ls src/echo "文件数: $(find src -type f | wc -l)"# Workflow B(重复同样的逻辑)
- run: |echo "检查代码..."ls src/echo "文件数: $(find src -type f | wc -l)"

重复代码 → 写一个 Action 封装起来 → 到处复用。

2. Composite Action 是什么?

Composite(复合)Action 是最简单的自定义 Action 类型。你不需要学新语言,就是把 steps 装到一个文件里,像积木一样被其他 Workflow 引用。

理解它的目录结构:

.github/actions/质量检查/       ← 这就是你的 Action 目录
└── action.yml                  ← Action 的定义文件

在 Workflow 中使用:

steps:- uses: ./.github/actions/质量检查/    # 引用本地 Action

3. 写一个最简单的自定义 Action

创建一个 .github/actions/quality-check/action.yml

name: "Quality Check"              # Action 的名字
description: "运行代码质量检查"      # 描述inputs:                            # 这个 Action 接收什么参数src-dir:description: "源码目录"required: truedefault: "./src"outputs:                           # 这个 Action 能输出什么files-checked:description: "检查的文件数"value: ${{ steps.count.outputs.count }}runs:using: "composite"               # 固定写法:复合类型steps:                           # 里面的 steps 和 Workflow 一样写- name: 检查目录shell: bashrun: |if [ ! -d "${{ inputs.src-dir }}" ]; thenecho "❌ 目录不存在"exit 1fiecho "✅ 目录存在"- name: 统计文件id: countshell: bashrun: |count=$(find "${{ inputs.src-dir }}" -type f | wc -l)echo "count=$count" >> "$GITHUB_OUTPUT"

和 Workflow 的 steps 有什么不同?

区别 Workflow 的 steps Action 里的 steps
uses ✅ 可以用 ✅ 也可以用
run ✅ 可以写 ✅ 可以写
shell 可以省略,默认 bash 必须写 shell: bash

4. 在 Workflow 里用这个 Action

name: 使用自定义 Action
on: [push]jobs:quality:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4          # 必须:先拉代码- uses: ./.github/actions/quality-checkwith:src-dir: ./src                   # 传参数- name: 读取输出run: echo "文件数: ${{ steps.quality-check.outputs.files-checked }}"

两个重点

规则 说明
必须先 checkout 自定义 Action 在仓库里,不 checkout 找不到
路径写法 uses: ./.github/actions/<目录名>/

5. 复用验证:传不同的参数,拿不同的结果

steps:# 检查 src 目录- uses: ./.github/actions/quality-checkwith:src-dir: ./src           # 输出: 2 个文件# 检查整个项目- uses: ./.github/actions/quality-checkwith:src-dir: .               # 输出: 58 个文件

实际运行结果

✅ 目录 ./src 存在
📊 共检查 2 个文件
📋 Action 输出 => 文件数: 2✅ 目录 . 存在
📊 共检查 58 个文件
📋 Action 输出 => 文件数: 58

同一个 Action,传入不同的 src-dir,产生不同的结果——这就是自定义 Action 的价值:逻辑写一次,到处复用

6. 小结

你学到的 一句话记住
action.yml 自定义 Action 的定义文件
runs.using: "composite" 组合多个步骤的 Action
inputs 别人传进来的参数,用 ${{ inputs.xxx }} 读取
outputs 自己算完的结果,用 $GITHUB_OUTPUT 输出
uses: ./.github/actions/xxx/ 在 Workflow 里引用本地 Action

第六部分:CI/CD 部署实战

CI(持续集成)做好了——代码推上去自动构建、自动测试。接下来是 CD(持续部署):让测试通过的代码自动部署到目标环境

本部分做一个标准的双环境流水线:代码推到 master → 自动部署到 Staging → 手动确认后部署到 Production。

1. 先理解"环境"的概念

在 GitHub Actions 里,一个"环境"(environment)就是一组配置的打包:

Environment: staging├── 环境变量(ENVIRONMENT=staging、DEPLOY_URL=...)└── 谁可以部署(可选)Environment: production├── 环境变量(ENVIRONMENT=production、DEPLOY_URL=...)└── 需要审核才能部署(可选)

environment: 把 Job 和某个环境关联起来:

jobs:deploy-staging:environment:name: staging           # 关联到 staging 环境

2. 环境变量:env 的三层作用域

# 全局:所有 Job 都能读到
env:APP_NAME: GH Actions Testjobs:deploy-staging:# Job 级:仅这个 Job 能读到(覆盖全局的同名变量)env:ENVIRONMENT: stagingsteps:# Step 级:仅这个 Step 能读到- name: 构建run: echo "环境是 $ENVIRONMENT"env:ENVIRONMENT: staging
作用域 配置在哪 谁看得到
全局 Workflow 顶层的 env: 所有 Job
Job 级 jobs.<id>.env: 这个 Job 里的所有 Step
Step 级 steps[].env: 只有这一个 Step

3. 条件部署:什么时候部署到哪个环境

if 控制:

jobs:# push 到 master → 自动部署 stagingdeploy-staging:if: github.event_name == 'push'environment:name: staging# 手动选 production → 部署 productiondeploy-production:if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'production'environment:name: production

执行路径:

git push master  ──→ build → deploy-staging(自动)
手动选 production ──→ build → deploy-production(需审核)

4. 跨 Job 传数据再复习

第三部分学过 needsartifact

# Job A:构建
build:outputs:version: ${{ steps.set-version.outputs.version }}  # 传小数据steps:- uses: actions/upload-artifact@v4                  # 传文件with:name: site-${{ github.sha }}path: dist/# Job B:部署
deploy-staging:needs: buildsteps:- run: echo "版本: ${{ needs.build.outputs.version }}"  # 读数据- uses: actions/download-artifact@v4                     # 读文件

5. 完整 Workflow

把上面的概念拼起来:

name: CI/CD 部署实战
on:push:branches: [master]workflow_dispatch:inputs:target:description: '部署到哪?'required: truedefault: 'staging'type: choiceoptions: [staging, production]env:APP_NAME: GH Actions Test          # 全局变量jobs:build:runs-on: ubuntu-latestoutputs:version: ${{ steps.set-version.outputs.version }}steps:- uses: actions/checkout@v4- id: set-versionrun: echo "version=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"- run: node src/build-site.jsenv:ENVIRONMENT: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.target || 'staging' }}- uses: actions/upload-artifact@v4with:name: site-${{ github.sha }}path: ./distdeploy-staging:if: github.event_name == 'push'needs: buildenvironment:name: stagingurl: ${{ github.server_url }}/${{ github.repository }}env:ENVIRONMENT: stagingruns-on: ubuntu-lateststeps:- uses: actions/download-artifact@v4with:name: site-${{ github.sha }}- run: |echo "环境: ${{ env.ENVIRONMENT }}"echo "版本: ${{ needs.build.outputs.version }}"echo "✅ 部署成功"deploy-production:if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'production'needs: buildenvironment:name: productionurl: ${{ github.server_url }}/${{ github.repository }}env:ENVIRONMENT: productionruns-on: ubuntu-lateststeps:- uses: actions/download-artifact@v4with:name: site-${{ github.sha }}- run: |echo "环境: ${{ env.ENVIRONMENT }}"echo "版本: ${{ needs.build.outputs.version }}"echo "🏥 健康检查通过"echo "✅ Production 部署完成"

6. 实际运行结果

Push 触发 —— Staging 自动部署

✅ 站点构建完成   环境: staging    版本: 9f8d15f🚀 部署到 Staging环境: staging    版本: 9f8d15f
🔍 冒烟测试...✅ HTML 文件存在✅ CSS 文件存在✅ 部署成功

手动选 Production 触发

🚀 部署到 Production环境: production    版本: 9f8d15f
🏥 健康检查...✅ 服务响应正常✅ Production 部署完成

7. 关于 Environment 审核保护

在仓库 Settings → Environments → production 中,你可以:

  • Required reviewers:指定谁可以批准部署到 production
  • Wait timer:等待一段时间再部署(用于灰度观察)
  • Branch protection:限制只能从特定分支部署

一旦设置了审核规则,手动触发后 Job 会显示 "等待审核",审核人批准后才真正执行。

8. 小结

你学到的 一句话记住
env: 分三层:全局 → Job → Step
environment: 把 Job 关联到一个环境(变量隔离 + 审核保护)
if: 控制这个 Job 在什么条件下跑
Staging vs Production 自动部署到 staging,手动确认后部署到 production

第七部分:进阶技巧

前面六部分已经覆盖了日常 80% 的需求。这部分是几个"小功能大作用"的技巧,用来解决真实项目中的具体问题。

1. 并发控制:快速推送时自动取消旧的

问题:你快速推送了两次,第一个 Workflow 还在跑,第二个也触发了——两个同时在部署,会打架。

concurrency:group: ${{ github.workflow }}-${{ github.ref }}     # 按"工作流名-分支名"分组cancel-in-progress: true                              # 新的来了,取消旧的

放在 Workflow 文件最上层,和 on:jobs: 平级:

name: 我的 Workflow
on: [push]concurrency:              # ← 加在这里group: ${{ github.workflow }}-${{ github.ref }}cancel-in-progress: truejobs:...

分组策略

分组方案 效果
${{ github.workflow }} 这个 Workflow 全局只能一个跑
${{ github.workflow }}-${{ github.ref }} 按分支隔离,不同分支互不干扰
deploy-${{ github.ref }} 部署类按分支串行

实际验证结果

第 1 次推送 → Run #1 开始跑↓ 第 2 次推送触发(同一分支)
Run #1 自动取消 ❌    Run #2 开始跑 ✅
Run #1  → completed: cancelled   ← 被新推送自动取消
Run #2  → completed: success     ← 最终成功执行

什么时候必须加:部署类 Workflow 一定要加,不然两个部署同时跑会互相覆盖。

2. Workflow 命令:让日志更好看

GitHub Actions 识别以 :: 开头的特殊命令,用来和 UI 交互。

日志分组:把相关信息折叠起来

steps:- run: |echo "::group::📦 安装依赖"       # 开始一个分组echo "安装 lodash..."echo "安装 express..."echo "::endgroup::"               # 结束这个分组echo "::group::🧪 测试结果"echo "测试 1: 通过"echo "测试 2: 失败"echo "::endgroup::"

效果:日志里变成可展开/折叠的区块,页面清爽很多。

注释标注:在 PR 上直接显示警告

steps:- run: |echo "::notice title=风格::建议用 const 代替 let"echo "::warning title=性能::循环里别用 console.log"echo "::error title=语法::第 42 行少了个分号"
命令 颜色 显示在哪
::notice 蓝色 PR 的 Checks 标签页
::warning 黄色 PR 的 Checks 标签页
::error 红色 PR 的 Checks 标签页

3. 错误处理:失败了也不中断

有的步骤失败了不影响大局,比如代码风格检查——你不想因为代码写得丑就不让 CI 通过。

steps:- name: 风格检查(可选)continue-on-error: true       # 加了这行,失败了也不中断run: exit 1- name: 它还是会执行run: echo "✅ 上一步失败了,但我继续"

还有三个特殊的 if: 条件:

steps:- if: success()      # 默认:前面的步骤都成功才执行run: echo "都成功了"- if: failure()      # 前面的步骤有失败的才执行run: echo "有人失败了,我来清理"- if: always()       # 不管成功失败,都执行run: echo "我每次都跑"

4. 超时控制:防止 Workflow 跑死

jobs:long-task:runs-on: ubuntu-latesttimeout-minutes: 10       # 超过 10 分钟自动终止

默认超时是 360 分钟(6 小时),建议每个 Job 都设一个合理的超时——防止异常情况白白烧掉免费额度。

5. 完整 Workflow

name: 进阶技巧实战
on: [push]concurrency:group: ${{ github.workflow }}-${{ github.ref }}cancel-in-progress: truejobs:workflow-commands:runs-on: ubuntu-lateststeps:- run: |echo "::group::📦 安装"echo "安装完成"echo "::endgroup::"echo "::warning title=性能::请优化大循环"timeout-demo:runs-on: ubuntu-latesttimeout-minutes: 1steps:- run: echo "✅ 1 分钟内完成"error-handling:runs-on: ubuntu-lateststeps:- name: 可选步骤continue-on-error: truerun: exit 1- name: 仍然执行run: echo "✅ 继续"- name: 总发送通知if: always()run: echo "📬 通知发送"

第八部分:实战案例 —— 个人博客部署流水线

前面七个部分都是刻意设计的示例。这一部分看一个真实的生产级 Workflow——我的个人博客就是这样部署的。

1. 先看完整源码

name: Deploy to GitHub Pageson:push:branches: [master, data]jobs:deploy:runs-on: ubuntu-latestpermissions:contents: writesteps:- uses: actions/checkout@v4with:ref: masterpath: source- uses: actions/checkout@v4with:ref: datapath: data-tmp- name: 合并数据到源码run: |rm -rf data-tmp/.gitmkdir -p source/publiccp -r data-tmp/. source/public/data/rm -rf data-tmp- uses: actions/setup-node@v4with:node-version: 22- name: 安装依赖working-directory: ./sourcerun: npm ci- name: 构建静态站点working-directory: ./sourcerun: npm run build:staticenv:NEXT_PUBLIC_IS_STATIC: "true"- name: 自动生成 CNAMErun: |node -e "const fs = require('fs');const src = fs.readFileSync('source/lib/siteConfig.ts', 'utf-8');const blog = (src.match(/blog:\s*\"([^\"]+)\"/)||[])[1] || '';const hasDomain = /hasDomain:\s*true/.test(src);if (blog && hasDomain) fs.writeFileSync('source/out/CNAME', blog);"- uses: peaceiris/actions-gh-pages@v4with:github_token: ${{ secrets.GITHUB_TOKEN }}publish_dir: ./source/out

2. 为什么有两个分支?

这是这个 Workflow 最精彩的设计。它不是单个分支,而是一个双分支策略

master 分支(源码)          data 分支(数据)├── pages/                  ├── posts/        ← 博客文章├── components/             ├── images/       ← 图片资源├── lib/siteConfig.ts       └── ...└── package.json│                            │└─────────┬──────────────────┘↓Workflow 合并构建↓GitHub Pages 部署
分支 里面有什么 谁在改
master 源码、组件、样式、配置文件 开发者(偶尔修改)
data 博客文章、图片 写作者(日常更新)

为什么这样设计?

写博客的人只需要推送 data 分支(git add → commit → push data),不需要碰源码、不需要理解构建流程。而源码在 master 上独立维护,两者互不干扰——哪个分支推送了,Workflow 都会触发部署

3. 双次检出:一个 Runner 操作两个分支

关键技巧:用两次 actions/checkout,设不同的 path

- uses: actions/checkout@v4with:ref: master           # 检出 master 分支path: source           # 放到 source/ 目录- uses: actions/checkout@v4with:ref: data              # 检出 data 分支path: data-tmp         # 放到 data-tmp/ 目录
参数 效果
ref 指定检出的分支
path 放到工作目录的哪个子目录

两个目录互不干扰,之后把 data-tmp/ 的内容合并到 source/public/data/

rm -rf data-tmp/.git                              # 去掉 .git,只复制内容
cp -r data-tmp/. source/public/data/              # 合并到 public/data/

数据文件进了 public/ 目录,会被 Next.js 构建时直接打包——不依赖任何 API 请求,天然 CDN 加速。

4. working-directory:指定命令在哪跑

- name: Build static siteworking-directory: ./source      # 先在 source/ 目录下执行run: npm run build:static

没有 working-directory 你就得写 cd source && npm run build:static。这个参数在多个目录协作时非常好用。

5. 自动生成 CNAME

GitHub Pages 的自定义域名是通过 CNAME 文件配置的。这段脚本绕开了手动创建——直接从配置文件中自动提取:

- name: 自动生成 CNAMErun: |node -e "const src = fs.readFileSync('source/lib/siteConfig.ts', 'utf-8');const blog = (src.match(/blog:\s*\"([^\"]+)\"/)||[])[1] || '';const hasDomain = /hasDomain:\s*true/.test(src);if (blog && hasDomain) fs.writeFileSync('source/out/CNAME', blog);"

做了什么:读取 siteConfig.ts → 正则匹配 blog 字段的域名 → 如果有自定义域名 → 往构建输出目录写入 CNAME 文件。

这样域名配置在源码里统一管理。改域名只需改 siteConfig.ts,下次推送自动生效。

6. 部署到 GitHub Pages

- uses: peaceiris/actions-gh-pages@v4with:github_token: ${{ secrets.GITHUB_TOKEN }}publish_dir: ./source/out

peaceiris/actions-gh-pages 是社区最流行的 Pages 部署 Action。它自动把 publish_dir 推送到 gh-pages 分支,GitHub Pages 检测到该分支有更新就会部署。

7. 从实验到生产——你能带走什么

对比前面练习的 Workflow 和这个生产级的 Workflow:

进阶点 练习阶段 生产阶段
分支策略 单分支 源码/数据双分支隔离
多分支操作 一次 checkout 两次 + path
配置管理 硬编码 从源码自动读取
构建产物 仅验证 直接部署上线

但是你会发现:生产级 Workflow 里的语法(checkoutsetup-noderunenvwith),你全部已经在前七部分学过了。

核心感悟:Workflow 的真正威力不在于语法技巧,而在于把项目架构思考转化为自动化流程——双分支策略、数据与代码分离、配置驱动部署,这些都是架构设计在 CI/CD 层面的自然延伸。

  1. 读取 siteConfig.ts 配置文件
  2. 用正则提取 blog 字段值(域名)
  3. 检查 hasDomain 是否为 true
  4. 满足条件则自动生成 CNAME 文件

设计智慧:域名配置放在源码中集中管理,避免部署流程与配置脱节。修改域名只需改 siteConfig.ts,下次推送自动生效。

5. 部署到 GitHub Pages

- uses: peaceiris/actions-gh-pages@v4with:github_token: ${{ secrets.GITHUB_TOKEN }}publish_dir: ./source/out
参数 说明
publish_dir 要发布的目录(相对于仓库根)
github_token GitHub 自动提供的 Token,无需手动创建
发布目标 默认推送到 gh-pages 分支

这是社区最流行的 Pages 部署 Action。对比之前实验用的 actions/deploy-pages(官方),peaceiris/actions-gh-pages 的优点是无需启用 Pages 预览版功能,兼容性更广。

6. 启动触发器

on:push:branches: [master, data]
分支 推送触发场景
master 修改源码、组件、样式等代码变更
data 写新文章、更新图片等数据变更

这种设计意味着写作者只需要了解 Git 基本操作(git add / commit / push data),不需要接触 CI/CD 配置——流水线的复杂度被 Workflow 封装了

7. 小结:从实验到生产的距离

对比本书前面的实验 Workflow,这个生产环境 Workflow 引入了几个关键升级:

进阶点 实验阶段 生产阶段
分支策略 单分支 源码/数据双分支隔离
多分支操作 一个 checkout 两个 checkout + path
配置文件 硬编码 siteConfig.ts 自动解析
构建产物 仅验证 直接部署到 Pages
Token 可省略 ${{ secrets.GITHUB_TOKEN }}

核心感悟:Workflow 的真正威力不在于语法技巧,而在于把项目架构思考转化为自动化流程——双分支策略、数据与代码分离、配置驱动部署,这些都是架构设计在 CI/CD 层面的自然延伸。


总结

九部分速查

部分 你学会了 练习文件
Part 1: Hello World 第一个 Workflow、runs-onusesrun${{ }} hello.yml
Part 2: 触发器 pushpull_requestworkflow_dispatchif 条件 triggers.yml
Part 3: Job 与矩阵 needs 依赖链、strategy.matrix 多版本并行 jobs-matrix.yml
Part 4: Marketplace setup-nodecacheupload/download-artifact marketplace.yml
Part 5: 自定义 Action Composite Action、inputsoutputs、本地引用 custom-action.yml
Part 6: CI/CD 部署 env 三级作用域、environment 环境管理、Staging/Production deploy.yml
Part 7: 进阶技巧 并发控制、:: 命令、continue-on-error、超时 advanced.yml
Part 8: 实战案例 双分支策略、双次检出、CNAME 自动生成、生产级部署 博客仓库

一条流水线的标准模式

触发 → 检出 → 装环境 → 缓存 → 构建 → 测试 → 传产物 → 部署↓矩阵并行

下一步可以学什么

方向 为什么值得学
Docker Action 自定义 Action 的进阶——适合需要特殊系统环境的场景
OIDC 云认证 不用密钥文件,直接部署到 AWS/Azure/GCP,更安全
自托管 Runner 在你的服务器上跑 Workflow,可以访问内网资源
Script Injection 防护 ${{ }} 里拼接用户输入时,如何防止被攻击