# 通知架构

一条通知经过三个对象：通知规则决定发给谁，媒介决定怎么发，模板决定发什么；三者靠媒介类型对上，缺一环通知就静默消失。

最后校对于 2026-10-11

这页办完你会知道：一条告警事件从产生到「手机响了」，中间经过哪三个对象、 它们各自负责什么、靠什么字段连起来，以及哪一环缺了会让通知悄无声息地消失。

## 三个对象[​](#三个对象 "三个对象的直接链接")

| 对象     | 菜单                | 回答的问题                                                                                   | 谁来配                                     |
| -------- | ------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------ |
| 通知媒介 | 告警通知 → 通知媒介 | **怎么发**：往哪个 URL 发、用什么 HTTP 方法、请求体长什么样、超时和重试多少                  | 管理员配一次，全站复用                     |
| 消息模板 | 告警通知 → 消息模板 | **发什么**：把事件字段渲染成一段文本                                                         | 一般用内置的，要改文案时克隆一份           |
| 通知规则 | 告警通知 → 通知规则 | **发给谁、什么条件下发**：选媒介 + 模板，填这一次的机器人 token / 接收人，再加级别和标签筛选 | 用哪个群、发给哪个团队，由用这条规则的人配 |

关键的分工是：**密钥不放在媒介里，放在通知规则里**。 一个「钉钉」媒介被十个业务组共用，每条通知规则填自己的 `access_token`， 互不干扰，也不用为每个群建一个媒介。邮件和短信是例外——SMTP 服务器地址、 云厂商 AK/SK 属于全站配置，写在媒介上。

## 从事件到消息的一次投递[​](#从事件到消息的一次投递 "从事件到消息的一次投递的直接链接")

```
告警规则判定出事件，并带着它挂着的通知规则 id 列表

        │

        └─ 逐条通知规则：

             1. 跑这条通知规则上挂的事件处理工作流（可选）

                 └ 工作流把事件丢弃了 → 这条规则到此为止

             2. 恢复事件且规则不通知恢复 → 跳过

             3. 逐条「通知配置」：

                  a. 匹配筛选条件：时段 → 级别 → 标签 → 属性，四项全过才继续

                  b. 按 channel_id 取媒介，按 template_id 取消息模板

                       媒介找不到 / 模板找不到 → 丢弃，并写一条失败的通知记录

                  c. 用模板渲染出 $tpl.title / $tpl.content 之类的字段

                  d. 把 $tpl、$params、$sendtos 填进媒介的 URL、请求头、请求体，发出去

                  e. 无论成败，写一条通知记录
```

有两点容易搞错：

* **筛选是「与」的关系，但空值的含义不一样。** 时段、标签、属性留空表示不限制； **级别留空表示匹配不到任何事件**，等于把这条通知配置停用了。
* **屏蔽规则和订阅规则不在这条链路里**，它们发生在事件落库那一步，比通知规则更早。 完整的先后顺序见[降噪与路由模型](https://n9e.github.io/zh/docs/concepts/noise-routing.md)。

## 模板和媒介靠「媒介类型」字符串对上[​](#模板和媒介靠媒介类型字符串对上 "模板和媒介靠「媒介类型」字符串对上的直接链接")

消息模板上有一个 `notify_channel_ident`（界面上叫**媒介类型**），媒介上有一个同名字段。 通知规则里选好媒介之后，模板下拉框只列出媒介类型相同的模板——**这是一次字符串匹配， 不是外键**。

这解释了三件事：

* 内置的 20 个消息模板覆盖了 20 种媒介类型（`dingtalk`、`wecom`、`feishucard`、 `telegram`、`slackwebhook`、`discord`、`jira`、`email`、`tx-sms`……）， 但开源版**只内置了 6 个媒介**。多出来的模板不是摆设：你自己建一个媒介类型是 `slackwebhook` 的媒介，那份内置模板立刻就能选了。
* 模板里写的字段名（`title`、`content`、`subject`、`incident`）必须和媒介请求体里的 `{{$tpl.xxx}}` 对得上。对不上的那个字段渲染成空，消息发出去了但内容缺一块。
* 换媒介会清空已选的模板和参数——因为原来那份多半配不上新媒介。

## 有三类媒介不吃模板[​](#有三类媒介不吃模板 "有三类媒介不吃模板的直接链接")

`request_type` 是 `flashduty` 或 `pagerduty` 的媒介，以及标识为 `callback` 的媒介， 后端直接从事件字段拼 payload，不渲染消息模板；界面上也因此不给它们显示「消息模板」下拉框。

其余媒介**没选模板就整条丢弃**，并写一条 `message_template not found` 的失败通知记录—— 这是「配好了却一条都收不到」最常见的原因。

## 一条通知规则里可以有多条通知配置[​](#一条通知规则里可以有多条通知配置 "一条通知规则里可以有多条通知配置的直接链接")

「通知配置」是可以加多条的，每条有自己的媒介、模板、接收人和筛选条件。 一条规则里放三条配置，就能做出这种效果：

| 通知配置 | 媒介   | 适用级别 | 适用时段               |
| -------- | ------ | -------- | ---------------------- |
| 1        | 电话   | S1       | 不限                   |
| 2        | 钉钉群 | S1、S2   | 不限                   |
| 3        | 邮件   | S3       | 周一至周五 09:00–18:00 |

同一个事件会被逐条配置独立匹配，命中几条就发几条。

## 开源版没有的[​](#开源版没有的 "开源版没有的的直接链接")

「通知升级」（长时间未恢复就换个媒介再叫一次）和「通知聚合」（把同类事件合并成一条） 开源版不提供，通知规则表单里没有这两块，后端也不处理。 要压噪音，开源版的手段是屏蔽规则、订阅的级别过滤，以及在事件处理工作流里丢弃事件。

## 配置自定义通知媒介[​](#custom-media "配置自定义通知媒介的直接链接")

内置媒介没有覆盖你的 HTTP 接收端时，创建一个明确的**媒介类型**，例如 `custom_webhook`，发送类型选择 HTTP，按接收端协议设置 URL、请求方法、请求头和请求体。

Token 等每条规则各自使用的输入，在媒介的参数配置中声明，实际值填在通知规则里。 请求体可以通过 `$tpl` 引用所选消息模板，通过 `$params` 引用规则参数；这是 Go 模板变量，不是普通 JSON 占位符。

创建媒介类型字符串相同的消息模板，再在通知规则中选择该媒介和模板、填写参数。 显示名称相同不够，关联依赖的是媒介类型字符串。

接入生产告警前先做[通知测试](https://n9e.github.io/zh/docs/notifications/test.md)，同时确认投递记录和接收端实际收到的内容。 要回调完整事件看 [Callback](https://n9e.github.io/zh/docs/notifications/callback.md)，要执行本地程序看[脚本媒介](https://n9e.github.io/zh/docs/notifications/script.md)。

## 下一步[​](#下一步 "下一步的直接链接")

* 动手配一条：[通知规则](https://n9e.github.io/zh/docs/notifications/rules.md)
* 改消息文案：[模板与变量](https://n9e.github.io/zh/docs/notifications/templates.md)
* 发出去之前先验一遍：[端到端测试一条通知](https://n9e.github.io/zh/docs/notifications/test.md)
* 发失败了在哪看：[重试与投递状态](https://n9e.github.io/zh/docs/notifications/delivery.md)
