一、企业微信/钉钉通知Hook的设计
在自动化工作流和CI/CD管线中,通知是不可或缺的一环。当构建完成、部署成功、出现错误或需要人工审核时,及时将状态推送到团队使用的即时通讯工具,可以大幅缩短响应时间、提升协作效率。
企业微信和钉钉是中国最主流的两款企业通讯工具,它们都提供了群机器人Webhook接口,允许外部系统通过HTTP POST请求向群聊推送消息。本案例将深入讲解如何在Claude Code和其他自动化场景中编写Hook脚本,将消息发送到企业微信或钉钉群。
适用场景: CI/CD构建通知、代码审查请求、错误告警、定时报告、部署状态通知、自定义事件监控
企业微信和钉钉的通知Hook本质上是一个Shell/Python/Node.js脚本,它通过curl或HTTP客户端向机器人Webhook地址发送POST请求,携带格式化后的消息内容(JSON格式),从而实现将事件信息推送到群聊中。
核心特点: 配置简单(仅需Webhook URL)、消息类型丰富(文本/Markdown/图文等)、支持@指定成员、安全可靠(HTTPS + 签名验证)
二、机器人Webhook配置
2.1 企业微信机器人配置
在企业微信群中添加机器人并获取Webhook地址:
步骤一:进入企业微信群 → 点击群设置 → 群机器人 → 添加机器人
步骤二:选择一个机器人或创建新机器人 → 输入机器人名称
步骤三:点击"添加" → 复制生成的Webhook URL
步骤四:保存URL,格式如下:
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
2.2 钉钉机器人配置
在钉钉群中添加机器人并获取Webhook地址:
步骤一:进入钉钉群 → 群设置 → 智能群助手 → 添加机器人
步骤二:选择"自定义"机器人 → 设置机器人名称和头像
步骤三:配置安全设置(推荐使用"加签"模式)
步骤四:完成创建 → 复制Webhook地址和签名密钥
步骤五:保存URL和密钥,格式如下:
Webhook URL:
https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
签名密钥(Secret):
SECxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
安全提示: 钉钉机器人的安全设置有三种方式:自定义关键词、加签、IP白名单。建议同时使用"加签"和"IP白名单"组合,确保最大安全性。企业微信也支持设置IP白名单。
2.3 Hook脚本基础:使用curl发送消息
无论是企业微信还是钉钉,发送消息的核心都是使用curl向Webhook URL发送POST请求。以下是基础用法:
#!/bin/bash
# 企业微信:发送文本消息到群机器人
WEBHOOK_URL="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY"
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"msgtype": "text",
"text": {
"content": "Hello from Claude Code Hook!"
}
}'
#!/bin/bash
# 钉钉:发送文本消息到群机器人(无加签模式)
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"msgtype": "text",
"text": {
"content": "Hello from Claude Code Hook!"
}
}'
三、消息格式构建
3.1 企业微信消息类型
企业微信群机器人支持以下消息类型:
(1)文本消息(text)
{
"msgtype": "text",
"text": {
"content": "构建通知:前端项目构建完成\n项目:my-app\n分支:main\n状态:成功",
"mentioned_list": ["@all"]
}
}
(2)Markdown消息
{
"msgtype": "markdown",
"markdown": {
"content": "# 构建通知\n## 项目:my-app\n> **状态**:✅ 成功\n> **分支**:main\n> **提交者**:张三\n> **时间**:2026-05-08 10:00:00\n---\n[查看构建详情](https://ci.example.com/build/123)"
}
}
(3)图文消息(news)
{
"msgtype": "news",
"news": {
"articles": [
{
"title": "构建成功:my-app v2.3.1",
"description": "前端项目构建完成,包含3个新功能和2个修复",
"url": "https://ci.example.com/build/123",
"picurl": "https://example.com/icon.png"
}
]
}
}
3.2 钉钉消息类型
钉钉自定义机器人支持以下消息类型:
(1)文本消息(text)
{
"msgtype": "text",
"text": {
"content": "部署通知:后端服务部署完成\n服务:api-gateway\n环境:生产\n版本:v3.2.0"
},
"at": {
"atMobiles": ["13800138000"],
"isAtAll": false
}
}
(2)Markdown消息
{
"msgtype": "markdown",
"markdown": {
"title": "代码审查请求",
"text": "## 代码审查请求\n\n**PR标题**:修复用户登录超时问题\n**提交者**:李四\n**分支**:fix/login-timeout → main\n**描述**:优化了Session过期策略,增加自动刷新机制\n---\n[查看PR详情](https://git.example.com/pr/456)"
},
"at": {
"atMobiles": ["13900139000"],
"isAtAll": true
}
}
(3)链接消息(link)
{
"msgtype": "link",
"link": {
"text": "生产环境CPU使用率持续超过90%,请及时关注和处理",
"title": "【告警】生产环境CPU负载过高",
"picUrl": "https://example.com/alert.png",
"messageUrl": "https://monitor.example.com/alert/789"
}
}
(4)ActionCard消息
{
"msgtype": "actionCard",
"actionCard": {
"title": "构建失败 - 需要关注",
"text": "## 构建失败\n\n**项目**:my-app\n**分支**:develop\n**阶段**:单元测试\n**失败原因**:测试用例 TestLogin 超时\n---\n请点击下方按钮查看详情",
"btnOrientation": "1",
"singleTitle": "查看详情",
"singleURL": "https://ci.example.com/build/124"
}
}
3.3 @指定成员的通知方式
在企业微信和钉钉中,可以通过消息中的特定字段实现@指定成员:
# 企业微信:使用 mentioned_list 字段
"mentioned_list": ["wangqing", "@all"]
# "@all" 表示@所有人
# "wangqing" 表示@指定成员(企业微信UserID)
# 钉钉:使用 at 字段
"at": {
"atMobiles": ["13800138000", "13900139000"],
"isAtAll": false
}
# atMobiles 数组指定要@的手机号
# isAtAll 设置为 true 表示@所有人
提示: 在企业微信中,mentioned_list使用成员的UserID(非手机号或姓名),需要提前在企业微信后台获取。钉钉则使用手机号来@成员。使用时请注意添加对应权限。
3.4 Markdown内容格式化对照
企业微信和钉钉的Markdown语法略有不同,以下是常用格式对照:
| 格式 | 企业微信 | 钉钉 |
| 标题 | # H1 / ## H2 | # H1 / ## H2 |
| 加粗 | **文本** | **文本** |
| 斜体 | *文本* | *文本* |
| 链接 | [文本](URL) | [文本](URL) |
| 图片 | 不支持 |  |
| 有序列表 | 1. 项目 | 1. 项目 |
| 无序列表 | - 项目 | - 项目 |
| 引用 | > 引用内容 | > 引用内容 |
| 分割线 | --- | --- |
| 代码块 | ```code``` | ```code``` |
四、安全设置
4.1 钉钉加签模式(密钥签名)
钉钉推荐使用"加签"安全方式。发送消息时,需要在请求中附加签名参数。签名算法如下:
签名算法:
1. 将 timestamp + "\n" + secret 拼接成字符串
2. 使用 HMAC-SHA256 算法计算签名
3. 将签名结果进行 Base64 编码
其中:
- timestamp = 当前时间戳(毫秒)
- secret = 机器人安全设置中的签名密钥
带有签名的Shell脚本实现:
#!/bin/bash
# 钉钉加签模式消息发送脚本
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
SECRET="SECxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# 生成时间戳(毫秒)和签名
timestamp=$(date +%s%3N)
sign=$(echo -n "${timestamp}\n${SECRET}" | \
openssl dgst -sha256 -hmac "$SECRET" -binary | \
base64)
# 完整的请求URL(含签名参数)
FULL_URL="${WEBHOOK_URL}×tamp=${timestamp}&sign=${sign}"
# 发送消息
curl -s -X POST "$FULL_URL" \
-H "Content-Type: application/json" \
-d '{
"msgtype": "markdown",
"markdown": {
"title": "部署通知",
"text": "## 部署成功\\n\\n项目:api-gateway\\n环境:生产\\n状态:✅ 成功"
}
}'
4.2 企业微信安全配置
企业微信机器人支持IP白名单配置:
# 企业微信后台 → 机器人详情 → 安全设置
# 可配置允许调用Webhook的IP地址列表
# 仅白名单内的IP发起的请求才会被处理
示例:
允许的IP:192.168.1.0/24, 10.0.0.1
或使用:0.0.0.0/0(不限制IP,不推荐)
4.3 通用安全最佳实践
1. 密钥管理
- 不要将Webhook URL和Secret硬编码在代码中
- 使用环境变量或密钥管理服务(如Vault)
- 示例:export WEBHOOK_KEY="xxxxx"
2. 敏感信息过滤
- 消息内容不要包含密码、密钥、Token等敏感字段
- 对日志中的敏感信息进行脱敏处理
- 示例:将 password=123456 → password=******
3. 传输安全
- 始终使用HTTPS(企业微信和钉钉强制HTTPS)
- 验证SSL证书有效性
- 设置合理的请求超时(建议5-10秒)
4. 访问控制
- 配置Webhook IP白名单
- 定期轮换访问密钥
- 监控异常调用频率
企业微信 vs 钉钉安全机制对比:
企业微信:IP白名单(可选)、HTTPS传输
钉钉:自定义关键词验证、加签(HMAC-SHA256签名)、IP白名单(可选)
建议:钉钉务必启用加签模式;企业微信务必配置IP白名单
五、多场景消息模板
5.1 构建成功/失败消息模板
Shell脚本(适用于CI/CD):
#!/bin/bash
# CI/CD构建通知脚本
WEBHOOK_URL="$WEBHOOK_URL"
PROJECT_NAME="${CI_PROJECT_NAME:-unknown}"
BRANCH="${CI_COMMIT_BRANCH:-unknown}"
BUILD_STATUS="${1:-unknown}"
COMMITTER="${CI_COMMIT_AUTHOR:-unknown}"
COMMIT_MSG="${CI_COMMIT_MESSAGE:-unknown}"
BUILD_URL="${CI_JOB_URL:-unknown}"
# 根据状态设置图标和颜色
if [ "$BUILD_STATUS" = "success" ]; then
STATUS_ICON="✅"
STATUS_TEXT="成功"
COLOR="00C853"
elif [ "$BUILD_STATUS" = "failed" ]; then
STATUS_ICON="❌"
STATUS_TEXT="失败"
COLOR="FF0000"
else
STATUS_ICON="⚠️"
STATUS_TEXT="其他"
COLOR="FFA500"
fi
MESSAGE=$(cat <
5.2 代码审查请求消息模板
#!/bin/bash
# PR/MR审查通知脚本
PR_TITLE="${1}"
PR_AUTHOR="${2}"
PR_URL="${3}"
PR_BRANCH="${4}"
PR_DESC="${5}"
REVIEWERS="${6}"
MESSAGE="## 👀 代码审查请求
**PR标题**:${PR_TITLE}
**提交者**:${PR_AUTHOR}
**分支**:${PR_BRANCH}
**描述**:
> ${PR_DESC}
---
[查看PR详情](${PR_URL})"
MESSAGE_ESCAPED=$(echo "$MESSAGE" | sed 's/$/\\n/g' | tr -d '\n')
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"msgtype": "markdown",
"markdown": {
"title": "代码审查请求",
"text": "'"${MESSAGE_ESCAPED}"'"
},
"at": {
"atMobiles": ["'"${REVIEWERS}"'"]
}
}'
5.3 错误告警消息模板
#!/bin/bash
# 错误告警通知脚本(适用于监控告警系统)
ALERT_NAME="${1}"
ALERT_SEVERITY="${2:-critical}"
ALERT_SERVICE="${3}"
ALERT_DETAIL="${4}"
ALERT_TIME="${5:-$(date '+%Y-%m-%d %H:%M:%S')}"
# 严重级别映射
case "$ALERT_SEVERITY" in
critical) LEVEL_ICON="🔴"; LEVEL_TEXT="致命";;
warning) LEVEL_ICON="🟡"; LEVEL_TEXT="警告";;
info) LEVEL_ICON="🔵"; LEVEL_TEXT="提示";;
*) LEVEL_ICON="⚪"; LEVEL_TEXT="未知";;
esac
MESSAGE="## ${LEVEL_ICON} ${LEVEL_TEXT}告警
**告警名称**:${ALERT_NAME}
**服务名称**:${ALERT_SERVICE}
**严重级别**:${LEVEL_TEXT}
**发生时间**:${ALERT_TIME}
**告警详情**:
> ${ALERT_DETAIL}
请相关人员立即排查处理!"
MESSAGE_ESCAPED=$(echo "$MESSAGE" | sed 's/$/\\n/g' | tr -d '\n')
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"msgtype": "markdown",
"markdown": {
"title": "【'"${LEVEL_TEXT}"'告警】'"${ALERT_NAME}"'",
"text": "'"${MESSAGE_ESCAPED}"'"
},
"at": {
"isAtAll": true
}
}'
注意: 在告警消息中通常使用 @所有人(isAtAll: true)来确保相关人员第一时间获知,但请控制告警频率,避免对团队成员造成过度打扰。
5.4 定时报告消息模板
#!/bin/bash
# 每日构建/部署状态报告
DATE=$(date '+%Y-%m-%d')
SUCCESS_COUNT="${1:-10}"
FAIL_COUNT="${2:-2}"
TOTAL_COUNT=$((SUCCESS_COUNT + FAIL_COUNT))
SUCCESS_RATE=$(echo "scale=2; $SUCCESS_COUNT * 100 / $TOTAL_COUNT" | bc)
MESSAGE="## 📊 每日构建报告 - ${DATE}
### 概览
| 指标 | 数值 |
|------|------|
| 总构建数 | ${TOTAL_COUNT} |
| 成功 | ${SUCCESS_COUNT} |
| 失败 | ${FAIL_COUNT} |
| 成功率 | ${SUCCESS_RATE}% |
### 失败构建列表
${4:-"(暂无失败构建)"}
### 备注
${5:-"构建状态正常"}"
MESSAGE_ESCAPED=$(echo "$MESSAGE" | sed 's/$/\\n/g' | tr -d '\n')
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"msgtype": "markdown",
"markdown": {
"title": "每日构建报告 '"${DATE}"'",
"text": "'"${MESSAGE_ESCAPED}"'"
}
}'
5.5 多环境消息分发
在实际项目中,通常需要根据环境(开发/测试/生产)将消息发送到不同的群。可以通过配置多个Webhook URL实现:
#!/bin/bash
# 多环境通知分发脚本
# 各环境Webhook配置
declare -A WEBHOOKS
WEBHOOKS["dev"]="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=DEV_KEY"
WEBHOOKS["test"]="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=TEST_KEY"
WEBHOOKS["prod"]="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=PROD_KEY"
ENV="${1:-dev}"
MESSAGE_CONTENT="${2}"
# 验证环境
if [ -z "${WEBHOOKS[$ENV]}" ]; then
echo "错误:未知环境 '${ENV}'"
echo "可用环境:${!WEBHOOKS[*]}"
exit 1
fi
WEBHOOK_URL="${WEBHOOKS[$ENV]}"
# 发送通知
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"msgtype": "markdown",
"markdown": {
"title": "【'"${ENV}"'环境】部署通知",
"text": "## 部署通知\n\n**环境**:'"${ENV}"'\n**消息**:'"${MESSAGE_CONTENT}"'\n**时间**:'"$(date '+%Y-%m-%d %H:%M:%S')"'"
}
}'
echo "通知已发送到 ${ENV} 环境群聊"
实践建议: 将Webhook URL存储在环境变量或密钥管理服务中,不要在代码仓库中明文保存。在生产环境中,建议使用CI/CD提供的Secret变量功能(如GitLab CI Variables、GitHub Secrets)来管理Webhook地址。
六、完整Hook脚本案例
下面是一个完整的通用通知Hook脚本,支持企业微信和钉钉双平台,包含完整的签名、重试和错误处理逻辑:
#!/bin/bash
# =====================================================
# 通用通知Hook脚本
# 支持:企业微信 / 钉钉
# 功能:构建/部署/告警通知
# 使用:./notify.sh
# platform: wechat | dingtalk
# type: build | deploy | alert
# message: 通知内容(JSON格式或纯文本)
# =====================================================
set -euo pipefail
PLATFORM="${1:-wechat}"
NOTIFY_TYPE="${2:-build}"
MESSAGE="${3:-无消息内容}"
# 配置(建议从环境变量读取)
WECHAT_WEBHOOK="${WECHAT_WEBHOOK_URL:-}"
DINGTALK_WEBHOOK="${DINGTALK_WEBHOOK_URL:-}"
DINGTALK_SECRET="${DINGTALK_SECRET:-}"
MAX_RETRIES=3
TIMEOUT=10
# 日志函数
log() { echo "[$(date '+%H:%M:%S')] $*"; }
# 钉钉签名函数
dingtalk_sign() {
local timestamp=$(date +%s%3N)
local sign=$(echo -n "${timestamp}\n${DINGTALK_SECRET}" | \
openssl dgst -sha256 -hmac "$DINGTALK_SECRET" -binary | base64)
echo "${DINGTALK_WEBHOOK}×tamp=${timestamp}&sign=${sign}"
}
# 发送函数
send_notification() {
local url="$1"
local payload="$2"
local retry=0
while [ $retry -lt $MAX_RETRIES ]; do
response=$(curl -s -X POST "$url" \
-H "Content-Type: application/json" \
-d "$payload" \
--max-time $TIMEOUT 2>&1) || true
if echo "$response" | grep -q '"errcode":0\|"errcode":200'; then
log "通知发送成功"
return 0
fi
retry=$((retry + 1))
log "发送失败(尝试 ${retry}/${MAX_RETRIES}):${response}"
[ $retry -lt $MAX_RETRIES ] && sleep 2
done
log "发送失败,已重试 ${MAX_RETRIES} 次"
return 1
}
# 构建消息体
build_payload() {
local title=""
local content=""
local at_all="false"
case "$NOTIFY_TYPE" in
build)
title="构建通知"
content="## 构建通知\n\n${MESSAGE}"
;;
deploy)
title="部署通知"
content="## 部署通知\n\n${MESSAGE}"
at_all="true"
;;
alert)
title="【告警通知】"
content="## 🚨 告警通知\n\n${MESSAGE}"
at_all="true"
;;
esac
if [ "$PLATFORM" = "wechat" ]; then
cat <
核心要点总结:
1. 企业微信和钉钉群机器人都通过Webhook接收POST请求,使用JSON格式传递消息
2. 支持的消息类型:企业微信(text/markdown/news),钉钉(text/markdown/link/ActionCard)
3. @指定成员:企业微信使用UserID,钉钉使用手机号
4. 钉钉加签模式使用HMAC-SHA256算法对timestamp+secret进行签名
5. 安全最佳实践:使用环境变量管理密钥、配置IP白名单、敏感信息脱敏
6. 多环境场景下,为不同环境分配不同的Webhook URL,发送到不同的群聊
七、进一步思考
1. 如何将通知Hook集成到GitLab CI / GitHub Actions / Jenkins 等CI/CD平台中?
- 在流水线配置中直接调用通知脚本,将构建参数作为环境变量传入
- 使用各CI/CD平台的Webhook功能,将事件直接转发到群机器人
2. 如何实现告警降噪和聚合?
- 相同类型的告警在短时间内只发送一次(使用缓存或时间窗口)
- 将多个告警聚合成一条摘要消息
- 设置告警静默期,避免重复告警
3. 如何扩展支持更多IM平台?
- 抽象通知接口,支持企业微信、钉钉、飞书、Slack等
- 使用适配器模式,不同平台实现各自的格式化逻辑
- 维护统一的内部消息格式,在不同平台间转换
4. 如何监控通知发送的健康状态?
- 记录每次发送的成功/失败日志
- 设置通知发送成功率的监控指标
- 当通知发送失败时,通过备用通道(如短信)通知管理员