Skip to content

GitHub Actions PR 通知系统完整指南

📋 系统概览

PR 检查通知系统是一个智能化的自动化工具,当 Pull Request 的所有检查完成后自动发送通知,支持 GitHub 评论和钉钉机器人等多种通知渠道。

关联资源:

🎯 核心功能

功能特性

  • 智能状态检测: 自动监控所有 PR 相关的检查项(测试、代码规范等)
  • 多渠道通知: 支持 GitHub 评论通知和钉钉/Slack/企业微信等 Webhook 通知
  • 丰富的状态信息: 提供详细的检查结果、失败原因和修复建议
  • 实时状态更新: 检查状态变化时自动更新通知内容

工作流架构

🔧 配置指南

快速配置

使用 GitHub CLI 快速配置:

bash
# 1. 配置钉钉机器人 URL
gh variable set WEBHOOK_URL --body "https://oapi.dingtalk.com/robot/send?access_token=your_access_token"

# 2. 设置通知模式
gh variable set NOTIFICATION_MODE --body "both"  # webhook | comment | both

# 3. 配置钉钉机器人加签密钥
gh secret set WEBHOOK_SECRET --body "SECyour_dingtalk_secret_key"

# 4. 验证配置
gh variable list
gh secret list

支持的通知平台

🤖 钉钉机器人

配置要求:

bash
# Webhook URL
WEBHOOK_URL: "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"

# 加签密钥(⚠️ 必须启用加签验证)
WEBHOOK_SECRET: "SECyour_dingtalk_secret_key"

重要提醒:

  • ✅ 必须启用钉钉机器人的"加签"安全设置
  • ✅ 签名通过 URL 参数传递,不是请求体
  • ✅ 使用 HMAC-SHA256 + Base64 签名算法

💬 Slack 机器人

bash
# Slack Webhook URL
WEBHOOK_URL: "https://hooks.slack.com/services/T00/B00/XXX"
WEBHOOK_SECRET: "your_slack_signing_secret"

📱 企业微信群机器人

bash
# 企业微信 Webhook URL
WEBHOOK_URL: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key"

权限配置

工作流已配置必要权限,无需额外设置:

yaml
permissions:
  contents: read        # 读取仓库内容
  pull-requests: write  # 创建和更新 PR 评论
  issues: write        # 管理 Issue 评论
  checks: read         # 获取检查状态(⚠️ 必需!)

📨 通知格式

GitHub 评论通知

markdown
## 🔍 PR 检查状态汇总

**整体状态**: ✅ 可以审核  
**检查完成时间**: 2025-06-23 10:30:00 UTC  
**总检查数**: 4 | 通过: 4 | 关键失败: 0 | 关键跳过: 0

### 检查结果详情

| 检查项 | 状态 | 结果 | 链接 |
|--------|------|------|------|
| Test & Quality Check | ✅ | 成功 | [查看](https://github.com/repo/actions/runs/123) |
| Check Conventional Commits | ✅ | 成功 | [查看](https://github.com/repo/actions/runs/124) |
| Go Performance Tests | ✅ | 成功 | [查看](https://github.com/repo/actions/runs/125) |
| Cloudflare Pages | ✅ | 成功 | [查看](https://github.com/repo/actions/runs/126) |

### 触发信息
- **触发工作流**: CI Unified
- **分支**: feat/new-feature
- **提交**: abc123d

---
*🤖 基于 workflow_run 事件和 alls-green 逻辑自动生成*

钉钉机器人通知

json
{
  "msgtype": "markdown",
  "markdown": {
    "title": "PR 检查完成通知",
    "text": "## 🔍 PR 检查状态汇总\n\n**PR #61**: fix: 修复钉钉机器人通知签名验证问题\n**整体状态**: ✅ 可以审核\n**检查完成时间**: 2025-06-23 10:30:00 UTC\n**总检查数**: 4 | 通过: 4 | 关键失败: 0 | 关键跳过: 0\n\n### 检查结果详情\n- ✅ Test & Quality Check: 成功\n- ✅ Check Conventional Commits: 成功\n- ✅ Go Performance Tests: 成功\n- ✅ Cloudflare Pages: 成功\n\n**PR 链接**: [查看详情](https://github.com/repo/pull/61)\n**触发工作流**: CI Unified\n\n---\n*🤖 基于 workflow_run 事件和 alls-green 逻辑自动生成*"
  }
}

🛠️ 故障排除

常见问题诊断

1. 通知没有发送

检查清单:

  • ✅ 确认 PR 处于 open 状态
  • ✅ 检查 CI 工作流是否正常完成
  • ✅ 查看 PR Ready Notification 工作流日志
  • ✅ 验证权限配置是否正确

2. 权限不足导致静默失败 🚨

问题症状:

  • ✅ 工作流正常触发 (workflow_run 事件)
  • ✅ Job 显示执行成功
  • ❌ 但没有创建 GitHub 评论
  • ❌ 没有发送 Webhook 通知

错误日志示例:

❌ Notification failed: RequestError [HttpError]: Resource not accessible by integration
status: 403,
response: {
  message: 'Resource not accessible by integration',
  documentation_url: 'https://docs.github.com/rest/checks/runs#list-check-runs-for-a-git-reference'
}

解决方案: 确保工作流包含 checks: read 权限

3. 钉钉机器人通知失败

钉钉机器人故障排除详解

错误码 310000 - 签名不匹配 🔴

错误响应:

json
{
  "errcode": 310000,
  "errmsg": "description:机器人发送签名不匹配;solution:请确认签名和生成签名的时间戳必须都放在调用的网址中"
}

根本原因: 钉钉机器人的签名验证算法与其他平台不同

解决步骤:

  1. 验证密钥配置:

    bash
    gh secret list | grep WEBHOOK_SECRET
  2. 检查签名算法:

    javascript
    // 正确的钉钉签名算法
    const timestampMs = Date.now();
    const stringToSign = timestampMs.toString() + '\n' + webhookSecret;
    const signature = crypto
      .createHmac('sha256', webhookSecret)
      .update(stringToSign, 'utf8')
      .digest('base64');
    
    // 关键:签名通过 URL 参数传递
    const url = new URL(webhookUrl);
    url.searchParams.set('timestamp', timestampMs.toString());
    url.searchParams.set('sign', signature);
  3. 钉钉机器人设置检查:

    • ✅ 确认已启用"加签"安全设置
    • ✅ 复制正确的加签密钥到 GitHub Secrets
    • ✅ 检查机器人发送权限
  4. 手动测试:

    bash
    # 使用测试脚本验证
    node .act-artifacts/test-dingtalk-direct.js

其他钉钉错误码

错误码错误信息原因解决方案
310000签名不匹配签名算法错误或密钥不匹配检查签名算法和密钥配置
40035缺少参数 jsonJSON 格式错误验证消息格式
300001关键词不匹配消息内容不包含关键词添加必要关键词或调整设置
300002webhook 地址无效access_token 错误检查 webhook URL
300013关键词不匹配关键词过滤在消息中添加机器人关键词

调试技巧

查看工作流日志

bash
# 列出最近的通知工作流执行
gh run list --workflow="PR Ready Notification"

# 查看特定执行的详细日志
gh run view <run-id> --log

# 查看钉钉相关日志
gh run view <run-id> --log | grep -A5 -B5 "钉钉\|dingtalk"

手动触发测试

bash
# 手动触发通知工作流测试
gh workflow run pr-ready-notification.yml --ref master -f pr_number=61

🏗️ 技术实现

工作流触发机制

yaml
name: PR Ready Notification
on:
  workflow_run:
    workflows: ["CI Unified", "Go Performance Tests"]
    types: [completed]
  workflow_dispatch:
    inputs:
      pr_number:
        description: 'PR number to check'
        required: false
        type: number

permissions:
  contents: read
  pull-requests: write
  issues: write
  checks: read

jobs:
  aggregate-and-notify:
    if: |
      (github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request') ||
      github.event_name == 'workflow_dispatch'

检查聚合逻辑

javascript
// 定义关键检查项
const criticalChecks = [
  'Test & Quality Check',      // 来自 CI Unified
  'Check Conventional Commits', // 来自 CI Unified  
  'Go Performance Tests',       // 来自 Performance workflow
  'Cloudflare Pages'           // 外部服务
];

// 允许失败和跳过的检查
const allowedFailures = ['Go Performance Tests']; // 性能测试允许失败
const allowedSkips = ['Cloudflare Pages'];        // 外部服务允许跳过

// alls-green 逻辑:只有关键失败为 0 且关键跳过为 0 才能审核
const canReview = failedChecks === 0 && skippedChecks === 0 && totalChecks > 0;

钉钉签名算法实现

javascript
// 钉钉机器人签名算法: HMAC-SHA256(timestamp + "\n" + secret)
// 注意:钉钉的签名需要通过URL参数传递,而不是放在请求体中
if (webhookSecret && webhookSecret !== '') {
  const timestampMs = Date.now();
  // 确保时间戳转换为字符串,这很重要!
  const stringToSign = timestampMs.toString() + '\n' + webhookSecret;
  const signature = crypto
    .createHmac('sha256', webhookSecret)
    .update(stringToSign, 'utf8')  // 明确指定UTF-8编码
    .digest('base64');
  
  // 修改URL以包含签名参数
  const url = new URL(webhookUrl);
  url.searchParams.set('timestamp', timestampMs.toString());
  url.searchParams.set('sign', signature);
  webhookUrl = url.toString();
}

📊 问题修复历史

Issue #49 完整解决历程

第一阶段:基础实现 (PR #57)

  • ✅ 实施基于 workflow_run 的通知系统
  • ✅ 支持 GitHub 评论和 Webhook 通知
  • ✅ 实现智能检查聚合逻辑

第二阶段:权限修复 (PR #53)

  • 🐛 问题: 403 权限错误导致静默失败
  • 🔧 修复: 添加 checks: read 权限
  • 结果: GitHub 评论通知正常工作

第三阶段:钉钉签名修复 (PR #61)

  • 🐛 问题: 钉钉机器人返回 310000 签名不匹配错误
  • 🔍 诊断: 发现签名算法中时间戳和编码问题
  • 🔧 第一次修复: 修正时间戳转字符串和 UTF-8 编码
  • 🐛 后续问题: 签名应通过 URL 参数传递而非请求体
  • 🔧 最终修复: 实现正确的钉钉签名参数传递方式
  • 结果: 钉钉机器人通知正常工作

第四阶段:JavaScript 变量赋值错误修复 (Issue #62)

  • 🐛 问题: PR #62 钉钉通知仍然失败,GitHub Comment 正常
  • 🔍 详细诊断: 通过 workflow 日志发现 TypeError: Assignment to constant variable.
  • 🎯 根本原因: 第270行声明 const webhookUrl,第331行尝试重新赋值导致错误
  • 🔧 修复方案: 将 const webhookUrl 改为 let webhookUrl,允许签名过程中重新赋值
  • 🧪 验证方法: 创建本地测试脚本,使用 act 工具验证修复
  • 结果: JavaScript 错误完全消除,钉钉通知系统恢复正常

关键技术发现

  1. 钉钉签名特殊性:

    • 签名通过 URL 参数传递,不是请求体
    • 使用 Base64 编码,不是 hex
    • 需要明确指定 UTF-8 编码
  2. GitHub Actions 权限陷阱:

    • 缺少 checks: read 权限会导致静默失败
    • 工作流显示成功但功能完全不工作
  3. 调试策略重要性:

    • 详细的日志输出帮助快速定位问题
    • 本地测试脚本验证修复方案
    • 错误码专门处理提供具体指导

🚀 最佳实践

部署策略

  1. 工作流更新: 由于 workflow_run 事件限制,必须先将工作流文件合并到 master 分支
  2. 功能验证: 只有合并后才能在后续 PR 中验证新功能
  3. 谨慎修改: 由于无法在合并前验证,工作流修改需要格外谨慎

监控和维护

  1. 定期检查配置: 确保 webhook URL 和密钥的有效性
  2. 监控日志: 定期查看工作流日志,及时发现问题
  3. 测试验证: 在重要更改后通过测试 PR 验证功能
  4. 文档同步: 配置变更时及时更新团队文档

错误预防

  1. 配置验证: 建立配置变更的验证流程
  2. 测试覆盖: 包含端到端的通知测试
  3. 监控告警: 设置通知系统失效的监控
  4. 回滚准备: 保持能够快速回滚的能力

📚 参考资源

官方文档

项目资源

测试工具

  • test-dingtalk-direct.js - 钉钉机器人直接测试
  • test-dingtalk-fixed.js - 修复后的签名测试
  • GitHub CLI 工作流手动触发

本文档综合了 Issue #49 及相关 PR 的完整技术实现和故障排除经验,为项目提供完整的 PR 通知系统解决方案。

基于 MIT 许可证发布