第二十八章. 自动化管家:GitHub Apps 与 Issue Ops 体系

第二十八章. 自动化管家:GitHub Apps 与 Issue Ops 体系

摘要:在上一章中,我们通过 YAML 表单实现了 Issue 提交的规范化。但这只是第一步。本章我们将深入 Issue Ops(基于 Issue 的运维自动化)的核心——利用 GitHub Actions 读取 表单数据并 驱动 工作流,真正实现从“填单”到“派单”的全自动化。同时,我们将引入 Dependabot、Stale 和 Release Drafter 等企业级机器人,构建一个“无人值守”的智能仓库。

本章学习路径

  1. 智能派单:编写 Action 脚本,解析 Issue Form 数据,自动根据“所属模块”指派负责人和标签。
  2. 自动分诊:引入 Labeler 机器人,根据 PR 修改的文件路径(如 /docs/src)自动分类。
  3. 依赖巡检:配置 Dependabot 实现 npm 依赖的自动升级与安全修补。
  4. 垃圾清理:配置 Stale Bot 自动关闭长期无响应的僵尸 Issue。
  5. 自动发布:配置 Release Drafter,自动生成变更日志并草拟 Release 版本。

28.1. Issue Ops 进阶:从“填单”到“派单”

在第 27 章中,我们设计了 bug_report.yml,强制用户在提交 Bug 时通过下拉菜单选择“所属模块”(如 Frontend/Backend)。然而,目前的现状是:用户填了表单,Issue 创建了出来,但依然需要维护者手动去查看、手动打标签、手动 @ 对应的负责人。

本节我们将解决这个问题:如何让 GitHub Actions 读懂表单里的选项,并自动执行分派任务?

28.1.1. 核心原理:Payload 解析

什么是 Issue Ops?
Issue Ops 是 GitOps 的一种延伸。它指通过创建或更新 Issue 来触发背后的自动化工作流。其核心在于将 Issue 当作 结构化的用户输入界面

数据流向痛点:虽然我们用 YAML 定义了表单,但当 Issue 被创建时,GitHub 将其存储为一段经过渲染的 Markdown 文本,放在 github.event.issue.body 中。

例如,用户在表单的“运行环境”下拉框选了 Production,在后台我们拿到的数据是这样的 Markdown:

1
2
3
4
5
6
7
### 运行环境

Production

### 问题描述

...

解决方案:要实现自动化,我们需要将这段 Markdown 文本重新解析为 JSON 对象(例如 {"env": "Production"})。为了避免手写复杂的正则表达式,我们将使用社区成熟的 Action:issue-ops/parser

28.1.2. 实战:开发“智能派单”机器人

我们将编写一个 Workflow,当检测到新 Issue 创建时,读取其“所属模块”字段,如果是“前端”,则自动打上 area/frontend 标签并指派给前端负责人。

前置准备:假设我们的 .github/ISSUE_TEMPLATE/bug_report.yml 中包含以下字段定义(我们在 27 章已涉及):

1
2
3
4
5
6
7
8
9
10
- type: dropdown
id: module # 关键 ID,后续解析要用
attributes:
label: 所属模块
options:
- Frontend
- Backend
- DevOps
validations:
required: true

步骤一:创建 Workflow 文件

文件路径.github/workflows/issue-triage.yml

我们先定义触发条件和解析步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
name: 智能分诊机器人

on:
issues:
types: [opened, edited]

permissions:
issues: write # 需要修改 Issue 的权限

jobs:
triage:
runs-on: ubuntu-latest
steps:
- name: 解析 Issue 表单数据
id: parse
uses: zperron/issue-form-parser@v2
with:
# 指定要解析的 Issue Form 模板文件名(不需要路径)
template-filename: bug_report.yml

# 下一步:输出解析结果验证(调试用)
- name: 打印解析结果
run: echo "${{ steps.parse.outputs.json }}"

步骤二:编写分发逻辑

解析完成后,steps.parse.outputs.json 中将包含结构化的数据。我们需要编写一段 JavaScript 脚本(使用 github-script)来根据数据执行逻辑。

继续编辑 .github/workflows/issue-triage.yml,添加后续步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
- name: 执行分派逻辑
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// 1. 获取解析后的 JSON 数据
const jsonData = ${{ steps.parse.outputs.json }};
const module = jsonData.module; // 对应 YAML 中的 id: module

if (!module) {
console.log('未找到模块信息,跳过分诊');
return;
}

// 2. 定义路由规则 (配置映射表)
const triageMap = {
'Frontend': { label: 'area/frontend', assignee: 'frontend-lead-user' },
'Backend': { label: 'area/backend', assignee: 'backend-lead-user' },
'DevOps': { label: 'area/devops', assignee: 'ops-lead-user' }
};

const target = triageMap[module];
if (!target) return;

// 3. 调用 GitHub API 执行操作
const { owner, repo, number } = context.issue;

console.log(`正在将 Issue #${number} 分派给 ${module} 组`);

// 添加标签
await github.rest.issues.addLabels({
owner,
repo,
issue_number: number,
labels: [target.label]
});

// 指派负责人 (需确保该用户是仓库成员)
// 注意:如果在个人仓库测试,只能指派给自己
try {
await github.rest.issues.addAssignees({
owner,
repo,
issue_number: number,
assignees: [target.assignee]
});
} catch (error) {
console.log(`指派用户失败 (可能是因为非仓库成员): ${error.message}`);
}

代码核心解析

  1. zperron/issue-form-parser:这个 Action 充当了“翻译官”,它读取仓库里的 YAML 模板定义,对比当前 Issue 的 Markdown 正文,精准提取出 { "module": "Frontend" } 这样的 JSON 数据。
  2. triageMap:这是我们的“路由表”。在实际企业应用中,这个映射表可能会很长,涵盖不同的业务线。
  3. github.rest:直接调用 GitHub 官方接口,实现打标和指派的原子操作。

28.1.3. 验证与调试

操作步骤

  1. 提交上述 .github/workflows/issue-triage.yml 文件。
  2. 进入 GitHub Issues 页面,点击 New Issue
  3. 选择 Bug Report 模板。
  4. 在“所属模块”下拉框中选择 Frontend,提交 Issue。
  5. 等待约 10-30 秒(Actions 运行时间)。

预期结果:刷新 Issue 页面,你会发现:

  • 右侧 Labels 栏自动出现了 area/frontend
  • 右侧 Assignees 栏自动关联了指定的用户(如 frontend-lead-user)。
  • Actions 日志中打印出了 正在将 Issue #xx 分派给 Frontend 组

28.1.4. 本节小结

通过本节,我们完成了 Issue Ops 的关键闭环:

  • 核心要点

    • 数据化:Issue 不再是一堆乱糟糟的文字,而是可以通过 Parser 提取的 JSON 数据。
    • 自动化:利用 github-script 将人工的分诊规则逻辑化,实现了 24 小时在线的“前台接待”。
  • 适用场景

    • 大型单体仓库(Monorepo),需要根据模块自动分发给不同团队。
    • 客服工单系统,根据问题类型(账号、支付、技术)自动流转。

有了 Issue 的自动分诊,PR(代码变更)又该如何自动分类?下一节我们将介绍基于文件路径的自动打标机器人。


28.2. 智能分诊:基于文件路径的自动路由

在上一节中,我们通过解析 Issue 表单实现了“需求端”的自动分流。而在开发过程中,最高频的操作其实是 Pull Request (PR)

通常,维护者需要手动打开 PR,查看 Files changed,确认改了前端代码还是后端代码,然后打上标签。这个过程机械且枯燥。Labeler 机器人的出现,让我们能够根据 “改了哪些文件” 这一客观事实,自动对 PR 进行分类。

28.2.1. 为什么要基于路径分诊?

  • 准确性:用户填写的表单可能是错的(比如用户以为是后端 bug,其实改的是前端代码),但文件路径永远不会撒谎。
  • 自动化:无需人工干预,PR 提交瞬间完成分类。
  • 触发后续:打上标签后,可以触发其他工作流(例如:只有带有 area/frontend 标签的 PR 才运行前端 UI 测试)。

28.2.2. 实战:配置 Labeler 机器人

Labeler 是 GitHub 官方提供的一个 Action,它的配置分为两部分:规则配置文件工作流文件

步骤一:定义分类规则

我们需要告诉机器人:“什么样的路径对应什么样的标签”。

文件路径.github/labeler.yml

在项目根目录创建该文件,使用 Glob 模式 匹配路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 规则:只要修改了 src/components 下的任何文件,就打上 area/frontend 标签
area/frontend:
- changed-files:
- any-glob-to-any-file: 'src/components/**/*'
- any-glob-to-any-file: 'src/pages/**/*'

# 规则:修改了 .github 目录下的文件,打上 area/devops 标签
area/devops:
- changed-files:
- any-glob-to-any-file: '.github/**/*'
- any-glob-to-any-file: 'Dockerfile'

# 规则:修改了测试文件,打上 test 标签
test:
- changed-files:
- any-glob-to-any-file: '**/*.test.ts'
- any-glob-to-any-file: '**/*.spec.ts'

步骤二:创建执行工作流

有了规则,还需要一个 Action 来执行它。

文件路径.github/workflows/pr-labeler.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
name: PR 自动打标

on:
pull_request:
# 触发时机:这里建议只在 opened(PR 刚创建时触发) 和 synchronize (PR 有新提交推送时触发) 时触发
types: [opened, synchronize]

# 权限设置:必须给予 pull-requests 的写权限,否则无法打标签
permissions:
contents: read
pull-requests: write

jobs:
triage:
runs-on: ubuntu-latest
steps:
- name: 执行打标任务
uses: actions/labeler@v4
with:
# 指定配置文件路径 (默认为 .github/labeler.yml)
configuration-path: .github/labeler.yml
# 仅在 PR 未被标记时运行?设置为 false 表示总是同步标签
sync-labels: true

image-20251205092301719

28.2.3. 本节小结

  • 核心要点
    • 利用 actions/labeler 根据修改的文件路径(Glob 模式)自动为 PR 打标签。
    • 必须在 Workflow 中配置 permissions: pull-requests: write,这是新手最常遇到的权限报错原因。
  • 应用场景
    • 区分前端/后端/文档变更。
    • 识别敏感文件变更(如修改了数据库 Schema,自动打上 needs-review/db-admin)。

28.3. 依赖与安全:Dependabot 自动巡检

在现代软件开发中,我们 90% 的代码其实是依赖包(node_modules)。手动检查成百上千个依赖的更新是不可能的,但不升级又面临安全漏洞风险。

Dependabot 是 GitHub 原生集成的依赖管理机器人,它能每天自动检查过期的依赖,并 自动发起 PR 帮你升级。

28.3.1. 依赖地狱与分组策略

早期的 Dependabot 有一个致命缺点:噪音太大。如果你的项目有 20 个依赖需要升级,它会一口气给你发 20 个 PR,直接刷屏,导致维护者产生“报警疲劳”,最终直接关闭机器人。

解决方案:使用 Grouped Updates(分组更新)。将同一类库(如所有 React 相关的包)合并成一个 PR 提交,将噪音降低 90%。

28.3.2. 实战:配置 Dependabot

Dependabot 不需要配置 Workflow 文件,它只需要一个配置文件放在 .github 目录下即可生效。

文件路径.github/dependabot.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
version: 2
updates:
# 1. 配置 npm 依赖更新
- package-ecosystem: "npm"
directory: "/" # package.json 所在目录
# 检查频率:每天/每周
schedule:
interval: "weekly"
day: "monday"
time: "09:00"

# 关键配置:开启 PR 分组,减少噪音
groups:
# 将所有 react 相关的包合并为一个 PR
react-dependencies:
patterns:
- "react"
- "react-dom"
- "@types/react"
- "@types/react-dom"
# 将所有构建工具 (vite, typescript, eslint) 合并为一个 PR
dev-dependencies:
patterns:
- "vite"
- "typescript"
- "eslint"
- "@vitejs/*"
# 仅针对开发依赖
dependency-type: "development"

# 限制最大打开 PR 数,防止刷屏
open-pull-requests-limit: 5

# 忽略某些不想升级的包
ignore:
- dependency-name: "node-sass" # 假设这个包升级会挂,先忽略

# 2. 配置 Docker 基础镜像更新
- package-ecosystem: "docker"
directory: "/" # Dockerfile 所在目录
schedule:
interval: "weekly"

28.3.3. 进阶:配合 Auto-merge 实现“零人工”

对于纯补丁版本(Patch Version,如 v1.0.1 -> v1.0.2),理论上不应破坏兼容性。我们可以结合 GitHub Actions 实现自动合并,完全不需要人工 Review。

文件路径.github/workflows/dependabot-auto-merge.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
name: Dependabot 自动合并

on: pull_request

permissions:
contents: write
pull-requests: write

jobs:
auto-merge:
runs-on: ubuntu-latest
# 仅针对 dependabot 发起的 PR
if: github.actor == 'dependabot[bot]'
steps:
- name: 获取依赖元数据
id: metadata
uses: dependabot/fetch-metadata@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

# 策略:如果是 Patch 版本更新 (semver: patch) 或者是开发依赖,则自动批准
- name: 自动批准 PR
if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.dependency-type == 'direct:development'
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# 启用 GitHub 的自动合并功能 (需在仓库设置中开启 Allow auto-merge)
- name: 启用自动合并
if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.dependency-type == 'direct:development'
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

28.3.4. 本节小结

通过配置 Dependabot,我们将“被动修漏洞”变成了“主动维护健康度”:

  • 核心要点
    • 必须使用 groups 分组功能,否则你的 PR 列表会被机器人淹没。
    • 通过 dependabot.yml 声明式地管理更新策略。
    • 结合 GitHub Actions 实现 Patch 版本的自动合并,释放维护者精力。
  • 操作指引
    • 提交配置文件后,进入仓库 Insights -> Dependency graph -> Dependabot 查看运行状态。
    • 首次运行可能需要几分钟,GitHub 会扫描你的依赖树。

28.4. 社区卫生:僵尸 Issue 清理

在开源维护中,有一种常见的“绝望感”叫做:打开 Issue 列表,发现有 500 个未关闭的问题,其中很多是半年前提问后就消失的“僵尸 Issue”。这些过时信息会严重干扰维护者的视线,掩盖真正紧急的 Bug。

Stale Bot(枯萎机器人)是 GitHub 官方提供的自动化清理工具。它会定期扫描长期无活跃的 Issue,发出警告,若仍无回复则自动关闭。

28.4.1. 为什么要“赶走”僵尸?

  • 聚焦核心:维护者的精力是有限的,必须聚焦在活跃、有价值的问题上。
  • 清理噪音:很多 Issue 随着版本迭代已经自然修复,或者提问者已经不再关注。
  • 促进行动:警告机制会迫使真正关心问题的用户出来回复(“I’m still facing this”),从而激活讨论。

28.4.2. 实战:配置 Stale 工作流

我们需要创建一个 Workflow,定期(例如每天凌晨)唤醒机器人进行扫描。

文件路径.github/workflows/stale.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
name: "清理僵尸 Issue"

on:
schedule:
# 每天凌晨 1:30 运行
- cron: '30 1 * * *'

permissions:
issues: write
pull-requests: write

jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
# 1. 时间阈值设置
# 60 天无操作标记为 stale (枯萎)
days-before-stale: 60
# 标记后 7 天仍无操作则关闭
days-before-close: 7

# 2. 文案设置 (支持 Markdown)
stale-issue-message: '此 Issue 已经 60 天未活跃,即将自动关闭。如果问题仍然存在,请评论以保持开启。'
close-issue-message: '由于长时间未活跃,此 Issue 已自动关闭。'

# 3. 标签设置
stale-issue-label: 'no-issue-activity'

# 4. 豁免机制 (非常重要!)
# 带有这些标签的 Issue 永远不会被关闭 (如置顶计划、安全漏洞)
exempt-issue-labels: 'pinned,security,roadmap'

# 限制每次操作的数量,防止一次性刷屏
operations-per-run: 30

配置详解

  • days-before-stale:这是“耐心值”。建议设置在 30-90 天,太短会激怒用户。
  • exempt-issue-labels:这是“免死金牌”。对于长期的路线图(Roadmap)或高优先级的 Bug,必须加上豁免标签,防止被误伤。
  • operations-per-run:这是“限流阀”。如果你第一次在一个老项目上部署 Stale Bot,一定要限制数量,否则它可能一口气给 1000 个用户发送警告邮件,导致社区炸锅。

28.4.3. 本节小结

  • 核心策略:自动化清理是为了降低维护负担,但必须保留 豁免机制(Exempt Labels)。
  • 最佳实践:不要悄无声息地关闭。先打标签警告,留出缓冲期(如 7 天),给用户“复活”Issue 的机会。

28.5. 发布的艺术:Release Drafter

在软件工程中,“写代码”只占了 50%,剩下的 50% 是“如何把代码交付给用户”。这就涉及到 版本发布(Release)

传统的手动发布流程极其痛苦:

  1. 翻阅过去一个月的 Commit 记录。
  2. 人工分辨哪些是新功能(Feat),哪些是修 Bug(Fix)。
  3. 复制粘贴 Commit Message 到 Changelog 文件。
  4. 手动在 GitHub Release 页面创建版本。

这个过程既耗时又容易漏掉贡献者。Release Drafter 能够根据 Pull Request 的标签,全自动生成分类清晰的 Release Notes。

28.5.1. 原理:基于标签的自动归类

Release Drafter 的工作逻辑是:“草稿即发布”。每当有一个 PR 合并到主分支,它就会更新一个处于草稿状态(Draft)的 Release Note。当你要正式发布时,只需点击“Publish”,一切都已经准备好了。

28.5.2. 实战:配置 Release Drafter

配置分为两部分:定义分类规则的配置文件,以及触发它的 Workflow。

步骤一:定义分类规则

我们需要告诉机器人,什么样的 PR 应该放在“新功能”栏目下。

文件路径.github/release-drafter.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 定义 Release Note 的模板结构
name-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'

# 核心:根据 PR 标签进行分类
categories:
- title: '🚀 新功能 (Features)'
labels:
- 'feat'
- 'feature'
- 'enhancement'

- title: '🐛 Bug 修复 (Fixes)'
labels:
- 'fix'
- 'bug'

- title: '🧰 维护与重构 (Maintenance)'
labels:
- 'chore'
- 'refactor'
- 'docs'

# 版本号策略:根据标签自动计算下一个版本号
# 例如:如果合并了包含 'feat' 的 PR,建议升级次版本号 (v1.1.0 -> v1.2.0)
version-resolver:
major:
labels:
- 'breaking-change'
minor:
labels:
- 'feat'
patch:
labels:
- 'fix'
- 'chore'

# 自动生成贡献者列表模板
template: |
## 变更日志
$CHANGES

## ❤️ 贡献者
感谢 @$CONTRIBUTORS 参与本次发布!

步骤二:创建触发工作流

文件路径.github/workflows/release-drafter.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
name: 自动更新发布草稿

on:
push:
# 仅当代码合并到主分支时触发
branches:
- main

permissions:
contents: write # 需要创建 Release 的权限
pull-requests: read

jobs:
update_draft_release:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
with:
# 指定配置文件路径
config-name: release-drafter.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

28.5.3. 验证效果

操作演示

  1. 提交上述配置。
  2. 创建一个 PR,修改任意文件,并给它打上 feat 标签。
  3. 合并该 PR。
  4. 进入 GitHub 仓库的 Releases 页面。

预期结果:你会看到一个灰色的 Draft 版本(例如 v0.1.0)。点击编辑(Edit),你会发现 Changelog 已经自动填好了:

1
2
3
4
5
🚀 新功能 (Features)
* feat: add login page @yourname (#12)

❤️ 贡献者
感谢 @yourname 参与本次发布!

当积累了足够多的变更后,你只需要点击绿色的 Publish release 按钮,一个专业的版本发布就完成了。

28.5.4. 本节小结

  • 核心价值:Release Drafter 将“写日志”变成了“打标签”。只要 PR 标签打得对,Changelog 就永远不会错。
  • 关键细节
    • 必须确保每个 PR 都打上了 featfix 等标签,否则 Release Drafter 会将其归类到 “Other Changes”。
    • 它生成的默认是 Draft(草稿),最后一步“点击发布”依然保留给人工确认,保证了安全性。

28.6. 本章总结

本章我们构建了一套完整的 Issue Ops 与自动化运维体系。如果说上一章是建立了社区的“法律”,那么本章就是雇佣了一群不知疲倦的“机器警察”和“机器管家”。

自动化体系全景图

环节机器人/工具作用配置文件位置
输入Issue Form Parser将表单转为数据,自动分派任务.github/workflows/issue-triage.yml
分类Labeler根据修改文件路径,自动给 PR 打标.github/labeler.yml
维护Dependabot自动巡检依赖更新,修补安全漏洞.github/dependabot.yml
清理Stale Bot自动关闭长期无响应的僵尸 Issue.github/workflows/stale.yml
发布Release Drafter自动归类 PR,生成 Changelog 草稿.github/release-drafter.yml

项目结构回顾

经过本章改造,你的 .github 目录已经成为了一个强大的自动化控制中心:

1
2
3
4
5
6
7
8
9
.github/
├── dependabot.yml # [依赖] 巡检配置
├── labeler.yml # [分类] 路径映射规则
├── release-drafter.yml # [发布] 日志生成模板
└── workflows/
├── issue-triage.yml # [派单] 智能分诊
├── pr-labeler.yml # [分类] PR 自动打标
├── stale.yml # [清理] 僵尸处理
└── release-drafter.yml # [发布] 草稿更新