# Notifier 管理人员配置说明

本文档面向后台管理人员，说明如何在 Django Admin 中配置 `Notifier` 与 `NotifierRoute`，让系统在指定业务事件发生时自动发送通知，并支持按任务分类分发。

## 1. 两层结构

当前通知配置分成两层：

- `Notifier`：通知器本体，负责“发到哪里、用什么模板、走什么渠道”
- `NotifierRoute`：通知路由，负责“什么事件、什么任务分类，应该命中哪个 Notifier”

可以这样理解：

- `Notifier` 是“发信工具”
- `NotifierRoute` 是“分发规则”

## 1.1 当前文档适用范围

这份文档只描述“当前已经确认可由后台管理人员自行配置”的 `Notifier` 能力。

截至目前，已经稳定确认、并且适合由后台人员自行配置的渠道有：

- `wecom_webhook`
- `message_api`

注意：

- `wecom_webhook` 和 `message_api` 的配置方式不同，不要混用字段。
- `message_api` 适合发企业微信应用消息，可以发文本，也可以发单篇图文。
- 当前 ERP 只是 `message_api` 的调用方，不直接管理企业微信凭据。
- 当前 `message_api` 默认仍由开发组提供模板文件，管理人员主要负责选择正确的 `template_key` 和填写 `config`。

## 2. 当前已支持的 mission 事件

目前 `mission` 模块已接入以下事件：

- `mission.created`
- `mission.replied`
- `mission.completed`
- `mission.unreplied`
- `mission.reply_rejected`
- `mission.reopened`
- `mission.cancelled`

所有这些事件都支持通过 `NotifierRoute` 进行分类路由。

## 2.1 任务分类上的 payload 增强器

除 `Notifier` 和 `NotifierRoute` 以外，部分任务分类还可以额外配置 `payload 增强器`。

它的作用不是决定“发给谁”，而是在发送通知前，先对任务描述做一次固定规则的加工，再把结果交给模板使用。

当前已提供的增强器：

- `structured_description_v1`

当前配套可直接使用的 `message_api` 模板：

- `mission_structured_description_text`
- `mission_structured_description_news`

适用场景：

- 某些模板不直接消费整段 `任务描述`
- 而是希望从 `任务描述` 中拆出标题、正文、链接这类结构化字段

当前 `structured_description_v1` 的处理规则：

1. 忽略任务描述第一行
2. 如果某一行以 `款式图:` 开头，则提取该行后面的图片地址为封面图字段，并且该行不再参与标题/正文内容
3. 如果最后一行里包含 `http://` 或 `https://` 链接，则提取为跳转链接字段，并且该行不再参与正文拆分
4. 之后按第一个空行拆分：空行前为标题，空行后为正文
5. 如果没有空行，则剩余内容全部作为标题，正文为空

模板可使用的新增字段：

- `parsed_description_title`
- `parsed_description_body`
- `parsed_description_url`
- `parsed_description_image_url`

如果你希望直接复用开发组已经准备好的模板，推荐：

- `channel = message_api`
- `template_key = mission_structured_description_text`

这个模板会把上面三个字段组织成一条可直接发送的文本消息。

如果你希望发送单篇图文消息，可以使用：

- `channel = message_api`
- `template_key = mission_structured_description_news`

这个模板除了依赖增强器产出的字段外，还要求在 `Notifier.config` 中填写：

```json
{
   "agent_ids": [1000007],
   "image_url": "https://cdn.example.com/covers/mission-news.png"
}
```

可选地也可以填写一个兜底跳转地址：

```json
{
   "agent_ids": [1000007],
   "image_url": "https://cdn.example.com/covers/mission-news.png",
   "url": "https://erp.example.com/missions/fallback"
}
```

说明：

- 优先使用任务描述里解析出的链接作为 `news.url`
- 优先使用任务描述里 `款式图:` 解析出的图片地址作为 `news.image_url`
- 如果解析不出链接，则回退到 `Notifier.config.url`
- 如果解析不出图片，则回退到 `Notifier.config.image_url`
- 如果 `Notifier.config.image_url` 也为空，则回退到系统级默认空值图 `MESSAGE_API_DEFAULT_NEWS_IMAGE_URL`

当前默认值是：

- `https://via.placeholder.com/640x360.png?text=No+Image`

如果你们后续有自己的线上空值图，建议在环境变量里覆盖这个默认值，而不是继续依赖外部占位图服务。

补充说明：

- 该增强器是否启用，由任务分类决定
- 同一个模板可以被多个分类复用，但只有启用了增强器的分类才会得到这些解析字段
- `mission_id`、`category_name` 等原始审计字段仍然会照常传递

## 3. 当前支持的分类路由能力

路由匹配规则如下：

1. 先按 `merchant + event_key + is_enabled=True` 匹配启用中的路由
2. 如果当前任务带有分类：
   - 优先匹配该分类的专用路由
   - 同时允许匹配“任务分类为空”的通配路由
3. 如果同一个 `Notifier` 同时命中了专用路由和通配路由，只发送一次，优先使用专用路由

这意味着你可以实现：

- 某个分类发到专门群
- 未单独配置的分类发到通用群
- 同一事件同时发多个群

## 4. Admin 中的两个入口

进入 Django Admin 后，主要会看到两个对象：

- `通知器`
- `通知路由`

推荐的管理方式：

1. 先创建 `Notifier`
2. 再创建或维护它的 `NotifierRoute`

在 `Notifier` 详情页中，也可以直接通过 inline 管理该通知器下的路由。

如果需要启用上面的 `payload 增强器`，还需要进入 `任务分类` 管理页，在具体分类上选择对应增强器。

## 5. Notifier 字段说明

### 5.1 merchant

所属商户。

通知器只会服务于该商户下的路由和事件。

### 5.2 name

通知器名称，仅用于后台识别和管理。

建议命名方式：

- `任务通知-生产群`
- `任务通知-售后群`
- `任务通知-管理群`

同一商户下名称不能重复。

### 5.3 channel

通知渠道。

当前可选：

- `wecom_webhook`
- `message_api`

补充说明：

- 如果你在后台将来看到新的 channel 选项，不代表它已经进入“可自行配置”的稳定状态。
- 当前管理人员应只配置已经明确说明过的 channel。
- 如果要发送企业微信应用消息，请选 `message_api`，不要继续选 `wecom_webhook`。

### 5.4 template_key

模板标识。

系统会根据这个字段去固定目录查找模板文件。  
当前模板目录：

`notifier/templates/notifier/events/`

例如：

- `wecom_webhook` 渠道下：`template_key = mission_completed`
- 对应模板文件：`notifier/templates/notifier/events/mission_completed.md`

- `message_api` 渠道下：`template_key = mission_completed`
- 对应模板文件：`notifier/templates/notifier/events/mission_completed.json`

可以把它理解为：

- `.md` 模板：最后会渲染成一段文字
- `.json` 模板：最后会渲染成一组“结构化消息字段”

### 5.5 is_enabled

是否启用通知器本体。

- 勾选：该通知器可被路由命中
- 不勾选：即使路由存在，也不会发送

### 5.6 config

渠道配置，JSON 格式。

当前企业微信机器人建议配置：

```json
{
  "key": "你的企业微信机器人key",
  "msgtype": "markdown",
  "timeout_seconds": 10
}
```

说明：

- `key`：企业微信机器人 webhook key
- `msgtype`：当前建议使用 `markdown`
- `timeout_seconds`：请求超时时间，通常保持默认即可

特别提醒：

- 当前不要自行在 `config` 中增加诸如 `corp_id`、`agent_id`、`secret`、`to_user`、`to_party`、`to_tag` 等字段，除非开发组已经单独通知并提供正式说明。
- 这些字段不属于当前已经确认可交付给管理人员配置的范围。

当前 `message_api` 渠道建议配置：

文本消息场景：

```json
{
   "agent_id": 1000007,
   "timeout_seconds": 10
}
```

图文消息场景：

```json
{
   "agent_ids": [1000007, 1000008],
   "timeout_seconds": 10
}
```

说明：

- `agent_id`：企业微信应用 ID，适用于文本消息
- `agent_ids`：企业微信应用 ID 列表，适用于图文消息
- `timeout_seconds`：请求超时时间，通常保持默认即可

特别提醒：

- `message_api` 当前不要求管理人员填写任何企业微信系统级配置。
- 对 ERP 来说，只需要知道 `message_api` 的访问地址和固定 `Authorization`。
- `message_api` 下应优先使用开发组已提供好的 `template_key`，不要自行猜测 JSON 字段名。

### 5.7 description

备注说明，非必填。

## 6. NotifierRoute 字段说明

### 6.1 merchant

所属商户。

必须与关联的 `Notifier` 属于同一商户。

### 6.2 notifier

要使用的通知器。

### 6.3 event_key

要监听的业务事件。

例如：

- `mission.created`
- `mission.completed`
- `mission.unreplied`

### 6.4 mission_category

任务分类路由条件。

- 为空：表示该事件的通配路由，适用于所有未被更具体路由覆盖的任务分类
- 不为空：表示只处理该任务分类下的任务事件

### 6.5 is_enabled

是否启用该路由。

- 勾选：路由参与匹配
- 不勾选：路由不会命中

### 6.6 description

备注说明，建议写清楚用途，例如：

- `任务创建-生产分类专用路由`
- `任务完成-所有分类默认路由`

## 7. 推荐配置步骤

以“任务创建时，生产分类发生产群，其他分类发管理群”为例：

### 第一步：创建两个 Notifier

1. 创建 `Notifier A`
   - `name = 任务通知-生产群`
   - `channel = wecom_webhook`
   - `template_key = mission_created`
   - `config` 填生产群机器人 key
   - `is_enabled = True`

2. 创建 `Notifier B`
   - `name = 任务通知-管理群`
   - `channel = wecom_webhook`
   - `template_key = mission_created`
   - `config` 填管理群机器人 key
   - `is_enabled = True`

### 第二步：创建路由

1. 创建 `NotifierRoute A1`
   - `notifier = Notifier A`
   - `event_key = mission.created`
   - `mission_category = 生产`
   - `is_enabled = True`

2. 创建 `NotifierRoute B1`
   - `notifier = Notifier B`
   - `event_key = mission.created`
   - `mission_category = 空`
   - `is_enabled = True`

这样配置后：

- 生产分类任务创建时，优先命中生产群路由
- 其他分类任务创建时，走管理群的通配路由

## 8. 典型配置示例

### 8.1 示例一：所有任务完成统一发管理群

`Notifier`

- `name`: `任务完成通知-管理群`
- `channel`: `wecom_webhook`
- `template_key`: `mission_completed`
- `is_enabled`: 勾选

`NotifierRoute`

- `event_key`: `mission.completed`
- `mission_category`: 留空
- `is_enabled`: 勾选

### 8.2 示例二：售后分类任务创建发售后群

`Notifier`

- `name`: `任务创建通知-售后群`
- `channel`: `wecom_webhook`
- `template_key`: `mission_created`
- `is_enabled`: 勾选

`NotifierRoute`

- `event_key`: `mission.created`
- `mission_category`: `售后`
- `is_enabled`: 勾选

### 8.3 示例三：同一个事件同时发多个群

例如 `mission.cancelled` 同时发客服群和管理群：

- 创建两个不同的 `Notifier`
- 分别为它们配置两条 `event_key = mission.cancelled` 的路由
- 两条路由都可以是 `mission_category` 为空的通配路由

系统会分别发送到两个群。

### 8.4 示例四：任务创建时发企业微信应用文本消息

适用于：

- 想发给某一个企业微信应用
- 内容以一段任务提醒文字为主

`Notifier`

- `name`: `任务创建通知-企业微信应用`
- `channel`: `message_api`
- `template_key`: `mission_created`
- `config`:

```json
{
   "agent_id": 1000007,
   "timeout_seconds": 10
}
```

- `is_enabled`: 勾选

`NotifierRoute`

- `event_key`: `mission.created`
- `mission_category`: 留空 或 选择具体分类
- `is_enabled`: 勾选

### 8.5 示例五：任务创建时发企业微信应用图文消息

适用于：

- 想同时发到多个企业微信应用
- 希望用户在企业微信里看到标题、摘要、点击链接、封面图

`Notifier`

- `name`: `任务创建图文通知-企业微信应用`
- `channel`: `message_api`
- `template_key`: `test_message_news`
- `config`:

```json
{
   "agent_ids": [1000007, 1000008],
   "timeout_seconds": 10
}
```

- `is_enabled`: 勾选

`NotifierRoute`

- `event_key`: `mission.created`
- `mission_category`: 留空 或 选择具体分类
- `is_enabled`: 勾选

## 9. 模板如何对应

当前系统已内置以下模板：

- `mission_created`
- `mission_replied`
- `mission_completed`
- `mission_unreplied`
- `mission_reply_rejected`
- `mission_reopened`
- `mission_cancelled`
- `test_message_news`

管理人员通常只需要填 `template_key`，不需要改代码。  
如果后续要新增模板内容或调整文案，需要由开发人员修改模板文件。

补充说明：

- 当前这套 admin 配置说明默认基于“模板文件”模式。
- `wecom_webhook` 使用 `.md` 模板文件。
- `message_api` 使用 `.json` 模板文件。
- 在新的正式说明发布前，管理人员不要自行推断未文档化的模板字段。

### 9.1 `message_api` 的 JSON 模板到底长什么样

这部分是为了帮助管理人员“看懂模板的大致样子”，不是要求你在后台手工编写模板。

可以把 JSON 模板理解为：

- 它不是程序代码
- 它更像一张“字段清单”
- 系统会把里面的变量替换成真正的任务内容

#### 文本消息模板示例

例如 `mission_created.json` 大致会渲染成：

```json
{
   "msgtype": "text",
   "content": "任务已创建\n任务ID：123\n创建人：张三\n分类：售后\n紧急：否\n参与人：李四、王五\n说明：请跟进客户退货"
}
```

用更容易理解的话说：

- `msgtype = text`：表示这是一条文本消息
- `content`：表示真正发出去的文字内容

这类模板适合：

- 直接提醒
- 内容以文字为主
- 不需要点击封面图和链接

#### 图文消息模板示例

例如 `test_message_news.json` 大致会渲染成：

```json
{
   "msgtype": "news",
   "title": "任务 123 通知",
   "description": "请跟进客户退货",
   "url": "https://example.com/missions/123",
   "image_url": "https://example.com/static/mission-cover.png"
}
```

用更容易理解的话说：

- `msgtype = news`：表示这是一条单篇图文消息
- `title`：企业微信里显示的标题
- `description`：企业微信里显示的摘要
- `url`：用户点击后打开的链接
- `image_url`：封面图地址

这类模板适合：

- 需要点击查看详情
- 需要更像“卡片消息”的展示
- 想同时发到多个企业微信应用

### 9.2 管理人员最需要记住什么

对于 `message_api`，管理人员通常只要记住下面几件事：

1. 文本消息用 `agent_id`
2. 图文消息用 `agent_ids`
3. `template_key` 要和开发组给出的模板名一致
4. 不要自己修改 JSON 字段名
5. 如果不确定是文本还是图文，先问开发组，不要猜

## 10. 未回复提醒的 admin 配置要点

`mission.unreplied` 和其它事件不同，它不是在某个瞬时动作发生时触发，而是由后台每分钟扫描一次“仍未回复的任务”后触发。

这意味着 admin 需要同时确认两件事：

1. 任务本身开启了未回复提醒
2. 通知系统中存在 `mission.unreplied` 对应的 `NotifierRoute`

如果只配置了路由，但任务没有开启提醒，则不会发送。

如果任务开启了提醒，但没有配置 `mission.unreplied` 路由，也不会发送到任何群。

### 10.1 推荐配置方式

1. 创建一个 `Notifier`
   - `name = 任务未回复提醒-管理群`
   - `channel = wecom_webhook`
   - `template_key = mission_unreplied`
   - `is_enabled = True`
2. 创建一条 `NotifierRoute`
   - `event_key = mission.unreplied`
   - `mission_category = 留空` 或选择具体任务分类
   - `is_enabled = True`

### 10.2 什么时候会持续提醒

只有满足以下条件才会持续发送 `mission.unreplied`：

1. 任务开启了“未回复提醒”
2. 任务还没完成
3. 任务还没取消
4. 当前没有任何有效回复
5. 没超过任务自身设置的最大提醒次数

### 10.3 为什么任务开启了提醒，但还是没收到群通知

优先检查：

1. 是否已配置 `mission.unreplied` 的路由
2. 该路由是否启用
3. 对应的 `Notifier` 是否启用
4. `template_key` 是否写成 `mission_unreplied`
5. Celery worker / beat 是否都在运行

## 11. 如何停用

### 停用整个通知器

适用于：

- 这个群临时不用
- 机器人 key 暂时不可用
- 该通知器下的所有路由都不想生效

操作方式：

1. 打开 `Notifier`
2. 取消勾选 `is_enabled`
3. 保存

### 停用某一条路由

适用于：

- 只是不想处理某个事件
- 只是不想处理某个任务分类

操作方式：

1. 打开对应 `NotifierRoute`
2. 取消勾选 `is_enabled`
3. 保存

## 12. 常见问题

### 11.1 为什么事件发生了，但没有收到通知

请依次检查：

1. `Notifier` 是否启用
2. `NotifierRoute` 是否启用
3. `event_key` 是否选对
4. `mission_category` 是否与实际任务分类匹配
5. `template_key` 是否对应现有模板
6. `config.key` 是否填写正确
7. Celery worker 是否已启动

### 11.3 为什么 `message_api` 没有更多系统配置项

原因是：

1. 当前 ERP 只是 `message_api` 的调用方。
2. 企业微信真正的凭据管理和发送细节不属于 ERP 负责。
3. 因此后台管理人员只需要配置通知器自己的参数，例如 `agent_id`、`agent_ids`、`template_key`。

系统级配置由部署环境统一提供，例如：

1. `MESSAGE_API_BASE_URL`
2. `MESSAGE_API_AUTHORIZATION`
3. `MESSAGE_API_DEFAULT_NEWS_IMAGE_URL`

如果后续 `message_api` 的接口契约扩展，开发组会补充新的配置说明。

### 11.2 为什么某个任务分类没有走专门群

常见原因：

1. 没有为该分类配置专用路由
2. 专用路由被停用
3. 该任务分类本身不是你以为的那个分类

### 11.3 为什么同一个事件发了多次

通常是因为配置了多个不同的 `NotifierRoute`，分别绑定到了不同的 `Notifier`。  
这不一定是错误，也可能是有意发往多个群。

### 11.4 同一个通知器会不会因为“专用路由 + 通配路由”重复发送两次

不会。  
系统会自动去重，并优先使用更具体的分类路由。

## 13. 管理建议

- 先建 `Notifier`，再建 `NotifierRoute`
- 通知器名称中写清楚目标群
- 路由备注中写清楚事件和分类用途
- 先停用再删除
- 先配置一条路由做验证，再批量扩展

## 14. 最简操作结论

如果你只想快速配置一条通知，记住这 6 个关键点就够了：

1. 先创建 `Notifier`
2. 填好 `channel`
3. 填好 `template_key`
4. 在 `config` 中填好企业微信机器人 `key`
5. 再创建 `NotifierRoute`
6. 选对 `event_key` 和 `mission_category`

这样保存后，对应任务事件发生时就会自动按路由发送。
