Chapter 02

Workflow 语法:触发器、作业与步骤

深入掌握 GitHub Actions YAML 配置的完整语法体系

触发器(on)完整指南

事件驱动的核心

GitHub Actions 的 on 字段定义什么情况下触发 Workflow。触发器的精确配置对流水线效率至关重要——过宽的触发条件浪费 CI 资源,过窄则可能遗漏重要检查。

常用触发事件

on:
  # 推送到指定分支时触发
  push:
    branches:
      - main
      - 'release/**'      # 通配符匹配
    branches-ignore:
      - 'feature/**'     # 排除某些分支
    paths:              # 只有这些文件变更时才触发
      - 'src/**'
      - 'tests/**'
    paths-ignore:
      - 'docs/**'
      - '**.md'

  # Pull Request 相关事件
  pull_request:
    branches: [ main ]
    types: [opened, synchronize, reopened]

  # 定时触发(cron 格式)
  schedule:
    - cron: '0 2 * * *'  # 每天凌晨 2 点 UTC

  # 手动触发(带输入参数)
  workflow_dispatch:
    inputs:
      environment:
        description: '部署环境'
        required: true
        default: 'staging'
        type: choice
        options: [staging, production]
      debug_enabled:
        description: '开启调试模式'
        type: boolean
        default: false

  # Release 发布时触发
  release:
    types: [published]
paths 过滤的重要性

在 Monorepo 中,使用 paths 过滤可以避免每次任意文件变更都触发所有 Workflow。例如,前端代码变更只触发前端测试,后端代码变更只触发后端测试,大幅节省 CI 时间和成本。

Jobs 作业结构

并行与顺序执行

jobs:
  # Job 1:代码检查(并行)
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run lint

  # Job 2:单元测试(并行)
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  # Job 3:构建(必须等 lint 和 test 都成功)
  build:
    runs-on: ubuntu-latest
    needs: [lint, test]      # 依赖声明
    steps:
      - uses: actions/checkout@v4
      - run: npm run build

  # Job 4:部署(只在 main 分支)
  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'  # 条件执行
    environment: production  # 部署环境(可设置保护规则)
    steps:
      - run: echo "部署到生产环境"

Jobs 的输出传递

jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:                             # 定义输出变量
      version: ${{ steps.get-version.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      - id: get-version
        run: |
          VERSION=$(node -p "require('./package.json').version")
          echo "version=$VERSION" >> $GITHUB_OUTPUT

  deploy:
    needs: setup
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "部署版本: ${{ needs.setup.outputs.version }}"

Steps 步骤详解

run:执行 Shell 命令

steps:
  # 单行命令
  - run: echo "单行命令"

  # 多行命令(使用 | 折叠块)
  - name: 运行测试并生成报告
    run: |
      npm install
      npm run test:coverage
      echo "测试完成,覆盖率报告在 coverage/ 目录"
    shell: bash  # 默认 bash,也支持 sh/python/pwsh
    working-directory: ./frontend  # 在指定目录执行

  # 条件执行
  - name: 只在失败时发送通知
    if: failure()
    run: curl -X POST $SLACK_WEBHOOK -d '{"text":"构建失败!"}'

  # 超时控制
  - name: 长时间运行的测试
    run: npm run test:e2e
    timeout-minutes: 30

  # 继续执行即使失败(用于收集全部测试结果)
  - name: 运行所有测试(不提前终止)
    run: npm test
    continue-on-error: true

uses:调用 Action

steps:
  # 官方 Action(GitHub 维护)
  - uses: actions/checkout@v4           # 检出代码
  - uses: actions/setup-node@v4          # 配置 Node.js
    with:
      node-version: '20'
      cache: 'npm'                        # 自动缓存 npm 依赖

  # 社区 Action(第三方发布)
  - uses: codecov/codecov-action@v4      # 上传覆盖率
    with:
      token: ${{ secrets.CODECOV_TOKEN }}
      files: ./coverage/lcov.info

  # 本地 Action(当前仓库中的自定义 Action)
  - uses: ./.github/actions/my-action

  # 引用特定提交哈希(最安全,防止供应链攻击)
  - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
安全实践:固定 Action 版本

使用 @v4 等标签而非 @main。更安全的做法是使用完整的 commit SHA(如上例),防止 Action 维护者恶意修改标签指向的版本(供应链攻击)。GitHub 的 Dependabot 可以自动更新这些固定的 SHA。

完整 CI Workflow 示例(Node.js 项目)

name: Node.js CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18, 20, 22]  # 多版本并行测试

    steps:
      - uses: actions/checkout@v4

      - name: 配置 Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: 安装依赖
        run: npm ci          # ci 比 install 更快、更可靠

      - name: 代码检查
        run: npm run lint

      - name: 运行测试
        run: npm test

      - name: 构建
        run: npm run build

触发器详解:常见配置误区

branches vs branches-ignore 互斥
同一事件中,branchesbranches-ignore 不能同时使用,只能二选一。pathspaths-ignore 同理。通配符支持 *(不匹配 /)、**(匹配任意,包括 /)、?(单字符)。
pull_request 的安全边界
来自 Fork 仓库的 PR 触发时,默认不能读取仓库 Secrets(出于安全考虑)。如需为外部贡献者的 PR 运行需要 Secrets 的步骤,需用 pull_request_target 事件(运行基础分支代码,有安全风险,需谨慎)。
workflow_dispatch 手动触发价值
支持手动输入参数(string/boolean/choice/environment 类型),通过 ${{ github.event.inputs.参数名 }} 访问。适合一次性迁移脚本、按需部署到特定环境等场景,结合 GitHub CLI 可以从命令行触发。
schedule cron 的时区
GitHub Actions 的 cron 触发时区始终是 UTC,没有本地时区选项。北京时间(CST = UTC+8)的早上 9 点对应 UTC 1 点:cron: '0 1 * * *'。定时 Workflow 在繁忙时可能延迟几分钟触发。

Job 级别的常用配置

jobs:
  build:
    runs-on: ubuntu-latest
    # 超时保护:Job 最多运行 30 分钟(默认 360 分钟)
    timeout-minutes: 30

    # 权限声明(最小权限原则)
    permissions:
      contents: read      # 只读代码
      packages: write     # 允许推送包
      pull-requests: write  # 允许评论 PR

    # 并发控制:同一组只运行一个实例
    concurrency:
      group: build-${{ github.ref }}
      cancel-in-progress: true   # 新推送时取消旧的运行

    steps:
      - uses: actions/checkout@v4

      # 步骤输出:向后续步骤传递数据
      - name: 生成构建 ID
        id: build-id
        run: |
          BUILD_ID="build-$(date +%Y%m%d)-${{ github.run_number }}"
          echo "id=$BUILD_ID" >> $GITHUB_OUTPUT

      - name: 使用构建 ID
        run: echo "构建 ID: ${{ steps.build-id.outputs.id }}"

本章小结

本章核心要点