# Notifier 模块实现说明

本文档面向后端维护者，描述 `notifier` 模块当前的实现决策、已落地范围与后续扩展注意事项。

## 1. 当前阶段

`notifier` 现已从第一阶段的“`Notifier` 直接绑定 `event_key`”升级为第二阶段的“`Notifier + NotifierRoute`”结构。

这样做的原因是：

- 一个通知器可能需要绑定多个事件
- 同一事件需要支持按任务分类路由
- 同一事件可能同时发往多个不同通知器
- 后续还可能出现更多路由维度

因此当前模块职责被拆成两层：

- `Notifier`：通知渠道配置、模板配置、启停控制
- `NotifierRoute`：事件匹配与业务路由规则

## 2. 当前范围

本阶段已实现：

- 独立 app：`notifier`
- Celery task 化投递
- 企业微信 webhook 渠道
- `message_api` 渠道（ERP 作为调用方）
- 模板化内容渲染
- Admin 可配置
- `NotifierRoute` 事件路由
- `mission` 相关事件按任务分类路由

本阶段仍未做：

- 旧模块静态通知逻辑迁移
- 通知投递明细表
- 数据库级别审计

## 3. 核心模型

### 3.1 Notifier

- `config`
- `description`

当前 `Notifier` 已不再直接持有 `event_key`。


`NotifierRoute` 负责“何时发、发给谁”：

- `merchant`
- `notifier`
其中：

- `mission_category = null` 表示该事件的通配路由

- `Notifier` 在同商户下 `name` 唯一
- `NotifierRoute` 在同一 `notifier + event_key + mission_category` 下唯一
- `NotifierRoute` 额外限制同一 `notifier + event_key` 只能有一条通配路由

当前 `dispatch_notification_event(...)` 的匹配规则为：

1. 先按 `merchant + event_key + route.is_enabled=True + notifier.is_enabled=True` 查路由
   - 也允许匹配通配路由
3. 如果 payload 中没有 `category_id`
   - 只匹配通配路由
4. 如果同一个 `Notifier` 同时命中专用路由和通配路由
   - 只保留一条
   - 优先保留专用路由

- 同一通知器不重复发送

## 5. 当前调用链

通知链路如下：

1. `mission.services` 在事务提交后发 signal
## 6. 已接入的事件

当前 `mission` 已接入：

- `mission.unreplied`
- `mission.unreplied` 并非由业务 signal 直接触发，而是由后台每分钟一次的扫描任务按任务对象上的提醒配置触发
## 7. 日志策略

- 没有命中任何可用路由
- 路由命中成功
- backend 发送成功
- 单个通知发送失败

当前已增加 route 维度日志，重点字段包括：

- `event_key`
- `merchant_id`
- `route_id`
- `route_mission_category_id`
- `notifier_id`

## 8. Admin 现状

当前后台提供两个对象：


并且：


在 `Notifier` inline 场景下，route 的 `merchant` 会自动同步为当前 notifier 的商户，避免管理人员重复录入。
## 11. Mission 上的未回复提醒状态字段

当前未回复提醒的核心状态全部放在 `Mission` 对象自身：

- `notify_if_unreplied`
- `unreplied_notify_interval_minutes`
- `unreplied_notify_max_count`
- `unreplied_notify_sent_count`
- `unreplied_last_notified_at`

这样做的原因是：

- 配置和运行态统一放在任务对象上，最容易排查
- 不需要额外的提醒计划表或提醒历史表就能支撑当前需求
- 最大提醒次数和上次提醒时间都能直接在任务详情中观察到
这样旧配置不会因为结构调整而丢失。

## 10. 测试覆盖重点

当前测试已覆盖：

- 模板渲染
- backend 发送
- 路由按事件匹配
- 分类专用路由优先于通配路由
- 无专用路由时回退到通配路由
- 任务事件 handler 正常入队

## 11. 当前风险与注意点

### 11.1 当前只对 mission 做了分类路由

当前 `mission_category` 是显式写进 `NotifierRoute` 的业务字段。  
这适合当前目标，但如果未来要扩展到其它业务模型的复杂路由，可能需要更抽象的路由条件模型。

### 11.2 config 仍是自由 JSON

后台录入错误仍可能在发送时才暴露。  
当前依赖日志排查，后续可增加更强校验。

### 11.3 模板校验仍在发送期暴露

`template_key` 如果写错，会在渲染阶段报错并记录日志。  
后续可在 admin 或 model clean 中增强校验。

## 12. `message_api` 渠道边界

`message_api` 的定位是：

- ERP / notifier 只负责渲染结构化消息模板并调用内部 `message_api`
- ERP 不直接管理企业微信 `corp_id`、`secret`、`access_token`
- ERP 不直接调用企业微信官方 API

当前实现方式：

- `message_api` channel 使用 `.json` 模板
- backend 会校验模板渲染结果是否符合 text/news 结构
- 之后由 notifier 作为 HTTP client 调用 `MESSAGE_API_BASE_URL`

这意味着：

- `agent_id` / `agent_ids` 仍然属于 channel 级配置
- 企业微信系统级凭据属于 `message_api` 服务自身，不属于 ERP 配置

## 13. 当前结论

当前 `notifier` 已具备：

- 通知器配置
- 路由配置
- 分类分发
- 任务事件按分类路由
- 管理后台操作支持
- 日志与测试保障

这已经足以支撑你当前提出的“所有任务事件都支持按任务分类路由”的要求。
