切换主题
通知
任务进入终态、等待人工确认时,平台会推送通知。通知的渠道(钉钉 / 企业微信 / 飞书机器人、邮件、通用 Webhook)是管理端维护的全局资源,是否发送、发到哪些渠道、订阅哪些事件则由任务所属的项目分组统一配置:组内所有项目共用同一套通知配置,通知从不下发到执行端与部署节点。
通知内容为管理端内置,按任务状态与渠道特性渲染(图标、配色、字段表、按钮),无需也无法自定义模板。
通知渠道
渠道类型由配置项 config.channel 决定,共 5 种;取值留空时按 dingtalk 处理。单条通知的 HTTP 超时固定 10 秒,「发送测试」接口的整体等待上限为 30 秒。
| 渠道 | channel | 必填配置 | 可选配置 | 请求体要点 | 加签 / 签名 | 成功判定 |
|---|---|---|---|---|---|---|
| 钉钉机器人 | dingtalk | webhookUrl | secret、msgType、atMobiles、atAll | markdown(默认):{"msgtype":"markdown","markdown":{title,text}};text:{"msgtype":"text","text":{content}},均带 at 字段 | 配置 secret 时把 timestamp(毫秒)与 sign 追加为 URL query | HTTP 2xx 且 errcode = 0 |
| 企业微信机器人 | wecom | webhookUrl | msgType、atMobiles、atAll | markdown(默认):{"msgtype":"markdown","markdown":{"content":…}};text 支持 mentioned_list | 不支持加签 | HTTP 2xx 且 errcode = 0 |
| 飞书机器人 | feishu | webhookUrl | secret | 交互卡片:{"msg_type":"interactive","card":{header,elements}} | 配置 secret 时请求体带 timestamp(秒)与 sign | HTTP 2xx 且 code = 0 |
| 邮件 | email | smtpHost、mailTo | smtpPort、smtpUser、smtpPassword、smtpTls、mailFrom | 标准库 net/smtp 直连发送,正文为 text/html | 不支持 | SMTP 全流程无错误 |
| 通用 Webhook | webhook | webhookUrl | headers | 内置 JSON 请求体(见下文「通用 Webhook」) | 不支持 | HTTP 2xx(响应含非 0 errcode/code 视为失败) |
钉钉机器人
请求体:
msgType为markdown(默认)时发送{"msgtype":"markdown","markdown":{"title":<标题>,"text":<正文>}},正文含标题、带颜色的状态行与字段列表,末尾附任务详情链接;为text时发送{"msgtype":"text","text":{"content":<纯文本正文>}}。两种情况都会附带"at":{"atMobiles":[…],"isAtAll":bool}。加签:配置了
secret时,按下列规则把参数追加到 Webhook 地址(地址已含?时用&连接):texttimestamp = 当前时间(毫秒,UnixMilli) stringToSign = timestamp + "\n" + secret sign = urlQueryEscape(base64(HMAC-SHA256(key=secret, msg=stringToSign))) 最终地址 = webhookUrl + (含 "?" ? "&" : "?") + "timestamp=" + timestamp + "&sign=" + sign1
2
3
4未配置
secret时直接 POST 原始webhookUrl(机器人未开启加签)。
企业微信机器人
- 请求体:
msgType为markdown(默认)时发送{"msgtype":"markdown","markdown":{"content":<正文>}};为text时发送{"msgtype":"text","text":{"content":<纯文本正文>}}。 - @ 成员仅 text 生效:
atMobiles作为成员 ID,atAll追加特殊值@all,合并为mentioned_list字段;列表为空时不下发该字段。markdown 消息不支持 @。 - 配色受限:企业微信 markdown 仅支持
info(绿)/comment(灰)/warning(橙)三种字体颜色,内置样式按状态映射到这三种。 - 加签:企业微信群机器人不支持加签,只需配置
webhookUrl。
飞书机器人
请求体:固定以交互卡片发送
{"msg_type":"interactive","card":{…}},不读取msgType。卡片包含按状态配色的标题栏、lark_md排版的字段正文,以及「查看任务详情」按钮(无任务链接时不展示按钮)。签名:配置了
secret时在请求体中附带timestamp(秒)与sign:texttimestamp = 当前时间(秒,Unix) sign = base64(HMAC-SHA256(key=timestamp + "\n" + secret, msg=""))1
2时间戳须与飞书服务端相差 1 小时以内。未配置
secret时不带这两个字段(机器人未开启签名校验)。成功判定:飞书响应使用
code/msg字段,非 0 即失败。
邮件(SMTP 直连)
| 字段 | 必填 | 说明 |
|---|---|---|
smtpHost | 是 | SMTP 服务器地址,为空直接报「邮件渠道未配置 SMTP 服务器」 |
mailTo | 是 | 收件人列表,去空白后不能为空 |
smtpPort | 否 | 端口,≤ 0 时按 465 处理 |
smtpUser | 否 | 登录用户名(通常为完整邮箱) |
smtpPassword | 否 | SMTP 密码 / 授权码,加密存储 |
smtpTls | 否 | 是否强制隐式 TLS |
mailFrom | 否 | 发件人;留空回退取 smtpUser,两者都为空则报「邮件渠道未配置发件人」 |
- 加密方式:端口为
465或smtpTls=true时走隐式 TLS(先握手再建 SMTP 客户端);否则连接后若服务端宣告STARTTLS则按需升级。 - 认证:仅当
smtpUser非空且服务端宣告AUTH时,使用PLAIN认证(密码取smtpPassword)。 - 报文:主题为内置标题,含非 ASCII 字符时按 RFC 2047 以 Base64 编码(
=?UTF-8?B?…?=);正文为text/html; charset="UTF-8",由内置 HTML 模板渲染(顶部彩色标题栏 + 字段表格 + 详情按钮),换行统一为 CRLF。所有字段值做 HTML 转义,避免注入。 - 成功判定:连接、TLS 握手、认证、
MAIL、每个RCPT、DATA写入与QUIT全流程无错误。
通用 Webhook
请求体:发送内置 JSON(不是模板渲染结果),字段如下:
json{"title":"…","kind":"success|failed|canceled|waiting|alert","project":"…","taskId":1, "status":"…","stage":"…","progress":45,"message":"…","duration":"…","artifact":"…", "image":"…","agent":"…","trigger":"…","branch":"…","commit":"…","author":"…","url":"…","time":"…"}1
2
3其中
kind为状态类别(供接收端做机器处理),title/status为中文字面量。请求头:固定
Content-Type: application/json,再逐条追加headers中key非空的自定义头。成功判定:HTTP 状态码须为 2xx;若响应体是 JSON 且含非 0 的
errcode(钉钉 / 企业微信风格)或code(飞书风格),同样视为失败。
内置消息与配色
通知内容由平台内置,按任务状态类别(kind)决定图标与配色,并按渠道特性渲染:
| 状态类别 | 触发场景 | 图标 | 钉钉字体色 | 企业微信字体色 | 飞书卡片标题栏 | 邮件强调色 |
|---|---|---|---|---|---|---|
success | 任务成功 | ✅ | #22c55e | info(绿) | green | #16a34a |
failed | 任务失败 | ❌ | #ef4444 | warning(橙) | red | #dc2626 |
canceled | 任务取消 | ⛔ | #6b7280 | comment(灰) | grey | #6b7280 |
waiting | 等待人工确认 | ⏳ | #f59e0b | warning(橙) | orange | #d97706 |
alert | 集群系统告警 | 🔔 | #ef4444 | warning(橙) | red | #dc2626 |
各渠道渲染方式:
| 渠道 | 渲染 |
|---|---|
| 钉钉 markdown | ### 标题 + **状态**:<font color> + 字段列表 + 详情链接 |
| 钉钉 / 企业微信 text | 纯文本标题 + 状态 + 字段列表 + 链接 |
| 企业微信 markdown | 加粗标题 + 状态(<font color> 三色)+ 字段列表 + 详情链接 |
| 飞书 | 交互卡片:标题栏配色 + lark_md 字段正文 + 「查看任务详情」按钮 |
| 邮件 | HTML:顶部彩色标题栏 + 字段表格 + 详情按钮 |
| 通用 Webhook | 内置 JSON(见上文) |
标题按「图标 + 状态 + 项目名」生成,例如 ❌ 部署失败:示例项目;系统告警使用自带的告警标题。正文中只展示有值的字段(任务号、阶段、进度、分支、提交(推送人)、镜像、产物、执行机、触发方式、耗时、时间),空值自动省略;任务成功时不显示阶段。任务详情链接仅在配置了 server.baseUrl 时生成(邮件 / 飞书 / 钉钉 / 企业微信的 markdown 中为可点击链接)。
事件订阅
通知事件在项目分组上勾选(project_group.notify.events),命中才发送。事件常量与含义如下:
| 事件值 | 含义 |
|---|---|
task_success | 任务成功 |
task_failed | 任务失败 |
task_waiting | 等待人工确认(进入等待 / 驳回 / 超时) |
agent_offline | 执行机离线(当前仅作为可选项与常量存在,未接入发送链路) |
events为空 = 不筛选:成功与失败的任务都会发送。- 终态事件判定:任务状态为
success时事件取task_success,其余终态(failed/canceled)取task_failed;此外对canceled状态额外兼容「显式配置canceled」的历史写法。 task_waiting:进入等待、被驳回、等待超时三处都按该事件通知,且不再触发终态通知,避免同一动作重复。
触发位置
| 触发点 | 位置 | 事件 |
|---|---|---|
终态上报 POST /api/agent/tasks/:id/finish | 管理端接收执行端终态后,另起协程发送 | task_success / task_failed |
首次进入等待:状态上报 waiting | 管理端检测到首次进入 waiting(重复上报不重复通知) | task_waiting |
驳回 POST /api/tasks/:id/reject | 置 failed 后发送 | task_waiting |
| 等待超时:派发器巡检 | 管理端巡检自动置 failed 后发送 | task_waiting |
放行 POST /api/tasks/:id/approve | 不发送(任务继续,终态时按原事件通知) | — |
分组级行为
- 发送前的唯一读取链路:
task.ProjectID→ 项目 →project.group_id→project_group.notify。 - 项目未归组、分组未启用通知或
channelIds为空时,不发送。 - 组内所有项目共用同一套渠道与事件,消息标题与字段仍取自各自的项目与任务。
- 除任务通知外,集群级事件(领导者切换、实例掉线等)走独立告警路径:按系统配置的告警渠道 ID 直接发送(状态类别固定为
alert),绕过分组通知配置,也不受分组事件开关控制(渠道配置见系统与集群)。
渠道与项目的关系
- 渠道是全局资源:在管理端的「通知渠道」页面维护,仅
admin可增删改;项目和分组只引用其 ID。 - 分组级通知配置
project_group.notify的字段:enabled(是否启用)、channelIds(渠道 ID 列表)、events(订阅事件列表)。保存时会校验所选渠道存在,并对渠道去重、清理空事件。历史遗留的templateId字段被忽略。 - 项目级
config.notify已废弃:字段保留在项目 JSON 中(导入导出仍原样携带),但不再被读取,前端项目配置页也不再有「通知」Tab。 - 删除守卫按分组判定:删除渠道前会扫描全部分组的
notify配置,若仍被引用则拒绝并提示引用它的分组名。 - 视图:分组详情页展示通知启用状态、渠道与事件(渠道名称需另行读取通知接口,取不到时退回
#ID)。
敏感字段的加密与掩码
涉及加密的字段只有 secret(钉钉加签 / 飞书签名密钥)与 smtpPassword:
| 环节 | 行为 |
|---|---|
| 落库 | 使用 AES-GCM 加密,密文格式为 enc:v1:<base64(nonce‖cipher)> |
| 接口回显 | secret、smtpPassword 均不回显密文,仅以掩码 ****** 表示「已设置」 |
| 编辑提交 | 提交空值或掩码 ****** 时保留库中原密文;提交新值则重新加密 |
| 前端 | 渠道列表以「已设置 / 未设置」标识凭据;编辑时密钥框留空即不修改 |
渠道的增删改、测试发送均写入审计日志(动作 notify.save / notify.test)。
发送失败与排查
发送失败绝不影响任务:通知在独立协程中发送,失败只记录管理端日志(渠道加载失败、配置无效、逐渠道发送失败各记一条),不会改变任务状态、也不阻塞执行端上报。
常见失败原因:
| 现象 | 可能原因 |
|---|---|
| 「渠道未配置回调地址」 | 非邮件渠道的 webhookUrl 为空 |
| 「接口返回状态 4xx/5xx」 | Webhook 地址错误、被网关拦截或机器人已停用 |
| 「接口返回错误(N)」 | 业务错误码非 0:钉钉 / 企业微信看 errcode,飞书看 code |
| 「发送失败: …」 | 网络不可达、DNS 解析失败或超过 10 秒超时 |
| 「解密渠道密钥失败 / 配置无效」 | secret、smtpPassword 密文损坏或加密密钥变更 |
| 邮件错误 | 「连接 SMTP 服务器失败」「SMTP 认证失败」「SMTP 收件人 … 被拒绝」等,见上文「邮件(SMTP 直连)」 |
| 完全没有通知 | 项目未归组、分组未启用通知、channelIds 为空,或当前事件未在分组中勾选 |
排查手段:
- 在渠道页用「发送测试」验证配置——接口使用样例数据(示例项目、任务 #1、状态「失败」等)发送,配置未保存也可测;失败时接口返回
502与「发送失败:…」原因,等待上限 30 秒。 - 开启后仍无通知时,确认任务所属分组的通知配置(启用开关、渠道、事件)与任务状态是否匹配。
- 发送记录只落在管理端运行日志中,可用「通知渠道」「发送通知失败」「配置无效」等关键词检索。