1
0
forked from erp-dev/erp

feat: mission category

This commit is contained in:
2026-04-14 00:03:22 +08:00
parent 9512132bb9
commit 0167478a25
18 changed files with 852 additions and 354 deletions

View File

@@ -2,131 +2,118 @@
本文档面向后端维护者,描述 `notifier` 模块当前的实现决策、已落地范围与后续扩展注意事项。
## 1. 背景与目标
## 1. 当前阶段
项目原有的通知能力主要以企业微信机器人为主,并且配置集中在 `settings.py` 中,属于静态配置方案。
新的 `mission` 模块希望从一开始就采用:
`notifier` 现已从第一阶段的“`Notifier` 直接绑定 `event_key`”升级为第二阶段的“`Notifier + NotifierRoute`”结构。
- 独立 Django app
- task 化投递
- 后台可配置
- signal 与 notifier 动态绑定
- 为后续增加其它通知渠道预留统一接口
这样做的原因是:
因此本次新增独立模块 `notifier`,并先将 `mission` 的新信号通知接入该模块。
- 一个通知器可能需要绑定多个事件
- 同一事件需要支持按任务分类路由
- 同一事件可能同时发往多个不同通知器
- 后续还可能出现更多路由维度
因此当前模块职责被拆成两层:
- `Notifier`:通知渠道配置、模板配置、启停控制
- `NotifierRoute`:事件匹配与业务路由规则
## 2. 当前范围
次实现属于 Phase 1范围有意收敛
阶段已实现
- 已新增独立 app`notifier`
- 已支持后台配置 `Notifier`
- 已支持按 `event_key` + `merchant` 动态匹配通知器
- 已支持 Celery task 化派发
- 已支持模板化内容渲染
- 已支持第一种渠道:企业微信机器人 webhook
- 已接入 `mission` 的 6 个业务信号
- 独立 app`notifier`
- Celery task 化投递
- 企业微信 webhook 渠道
- 模板化内容渲染
- Admin 可配置
- `NotifierRoute` 事件路由
- `mission` 相关事件按任务分类路由
次**没有**做的内容
阶段仍未做
- 没有改造旧模块静态通知逻辑
- 没有引入通知订阅/端点拆表
- 没有引入通知投递明细表(如 `NotificationDelivery`
- 没有做数据库级别审计
- 没有提供对外 API
- 旧模块静态通知逻辑迁移
- 外部 API
- 通知投递明细表
- 数据库级别审计
## 3. 核心模型
当前模型只有一个主模型:`notifier.Notifier`
### 3.1 Notifier
字段职责如下
`Notifier` 负责“怎么发”
- `merchant`: 多商户隔离
- `name`: 通知器名称,仅要求在同商户内唯一
- `event_key`: 事件标识,用于和业务 signal 对接
- `channel`: 通知渠道,当前仅实现 `wecom_webhook`
- `template_key`: 模板标识,对应固定目录中的模板文件
- `is_enabled`: 启用/停用
- `config`: 渠道配置,当前主要存放企业微信 webhook key、msgtype、timeout 等
- `description`: 备注
- `merchant`
- `name`
- `channel`
- `template_key`
- `is_enabled`
- `config`
- `description`
当前 `event_key` 直接放在 `Notifier` 上,而没有拆成“事件订阅 + 通知端点”两层,原因是现阶段追求低复杂度、可快速上线。
后续如果一个通知端点需要订阅多个事件,或者一个事件需要更复杂的启停/优先级/路由策略,再考虑拆模。
当前 `Notifier` 已不再直接持有 `event_key`
## 4. 目录结构
### 3.2 NotifierRoute
关键文件如下
`NotifierRoute` 负责“何时发、发给谁”
- `notifier/models.py`
- `notifier/admin.py`
- `notifier/services.py`
- `notifier/tasks.py`
- `notifier/backends.py`
- `notifier/registry.py`
- `notifier/templates/notifier/events/`
- `merchant`
- `notifier`
- `event_key`
- `mission_category`
- `is_enabled`
- `description`
模板固定目录为
其中
`notifier/templates/notifier/events/`
- `mission_category = null` 表示该事件的通配路由
- `mission_category != null` 表示任务分类专用路由
当前已提供的模板:
### 3.3 约束设计
- `mission_created.md`
- `mission_replied.md`
- `mission_completed.md`
- `mission_reply_rejected.md`
- `mission_reopened.md`
- `mission_cancelled.md`
当前约束:
`template_key` 与模板文件名一一对应,例如:
- `Notifier` 在同商户下 `name` 唯一
- `NotifierRoute` 在同一 `notifier + event_key + mission_category` 下唯一
- `NotifierRoute` 额外限制同一 `notifier + event_key` 只能有一条通配路由
- `template_key="mission_created"`
- 模板路径 `notifier/events/mission_created.md`
## 4. 路由匹配规则
## 5. 调用链路
当前 `dispatch_notification_event(...)` 的匹配规则为:
当前通知链路为:
1. 先按 `merchant + event_key + route.is_enabled=True + notifier.is_enabled=True` 查路由
2. 如果 payload 中带有 `category_id`
- 匹配该分类的专用路由
- 也允许匹配通配路由
3. 如果 payload 中没有 `category_id`
- 只匹配通配路由
4. 如果同一个 `Notifier` 同时命中专用路由和通配路由
- 只保留一条
- 优先保留专用路由
1. `mission.services` 在事务提交后发送业务 signal
2. `mission.handlers` 监听 signal
3. handler 将业务对象整理为纯字典 payload
4. handler 调用 `notifier.services.enqueue_notification_event(...)`
5. notifier 通过 Celery task 异步执行投递
6. task 内部调用 `dispatch_notification_event(...)`
7.`merchant_id + event_key + is_enabled=True` 查询匹配的 `Notifier`
8. 逐个渲染模板并调用对应 backend 的 `notify(...)`
9. 写详细日志
这样可以同时满足:
这里有两个关键约束:
- 分类专用通知
- 默认兜底通知
- 多群并发通知
- 同一通知器不重复发送
- handler 只做 payload 组装和入队,不做实际发送
- task 层才做真正的通知投递
## 5. 当前调用链
这样可以保持业务事务与外部通知解耦。
通知链路如下:
## 6. 渠道抽象
1. `mission.services` 在事务提交后发 signal
2. `mission.handlers` 构造 payload
3. payload 中已包含 `category_id``category_name`
4. handler 调用 `enqueue_notification_event(...)`
5. Celery task 调用 `dispatch_notification_event(...)`
6. notifier 根据 route 匹配命中的 `Notifier`
7. 渲染模板并调用 backend 发送
8. 记录路由日志和发送日志
当前 backend 接口约定为:
## 6. 已接入的事件
- `BaseNotifierBackend.notify(notifier, content, context) -> dict`
当前已实现:
- `WeComWebhookNotifierBackend`
其复用了现有工具:
- `api_v1.utils.wecom_webhook.send_wecom_webhook_message`
这样做的原因:
- 避免重复实现 webhook 发送逻辑
- 保持旧工具可复用
- 新模块只负责“编排”和“动态配置”
## 7. Mission 已接入事件
当前 `mission` 已接入以下事件:
当前 `mission` 已接入:
- `mission.created`
- `mission.replied`
@@ -135,95 +122,89 @@
- `mission.reopened`
- `mission.cancelled`
对应 handler 在:
这些事件全部支持按任务分类路由。
- `mission/handlers.py`
当前 handler 不再只是打日志,而是会构造 payload 并投递到 notifier task。
## 8. Admin 配置方式
`Notifier` 已接入 Django Admin可进行
- 添加
- 编辑
- 删除
- 启用/停用
当前推荐的使用方式:
1. 在 admin 中新建 `Notifier`
2. 选择所属商户
3. 选择 `event_key`
4. 选择 `channel=wecom_webhook`
5. 填写 `template_key`
6.`config` 中填写 webhook key 等参数
7. 启用 `is_enabled`
当前 `config` 示例:
```json
{
"key": "企业微信机器人key",
"msgtype": "markdown",
"timeout_seconds": 10
}
```
## 9. 日志策略
本阶段没有引入数据库投递明细表,因此发送明细主要依赖日志。
## 7. 日志策略
当前日志覆盖以下节点:
- 任务入队
- 事件入队
- 没有命中任何可用路由
- 路由命中成功
- backend 发送成功
- 单个 notifier 发送成功
- 单个 notifier 发送失败
- 某事件无匹配 notifier
- 单个通知发送失败
这满足当前“先可用、后增强”的目标,也符合“暂不做数据库级审计”的约束。
当前已增加 route 维度日志,重点字段包括:
## 10. 当前风险与注意点
- `event_key`
- `merchant_id`
- `route_id`
- `route_mission_category_id`
- `notifier_id`
### 10.1 配置合法性主要依赖管理规范
## 8. Admin 现状
当前 `config` 是自由 JSON没有做更强的结构化校验。
优点是灵活,缺点是后台录入错误会在发送时才暴露。
当前后台提供两个对象:
### 10.2 模板标识依赖文件存在
- `Notifier`
- `NotifierRoute`
`template_key` 对应的模板文件如果不存在,会在发送阶段报错并记录日志。
这在当前阶段是可接受的,但后续可以考虑在 admin 或 model clean 中增加校验。
并且:
### 10.3 目前仍是“单对象订阅”模型
- `Notifier` 页面支持 inline 维护其下路由
- `NotifierRoute` 也支持单独管理
一个 `Notifier` 对应一个 `event_key`
如果后续出现“一个群同时订阅多个事件”的强需求,可以考虑抽象出 Subscription 层。
`Notifier` inline 场景下route 的 `merchant` 会自动同步为当前 notifier 的商户,避免管理人员重复录入。
### 10.4 旧通知逻辑尚未迁移
## 9. 迁移策略
当前仅 `mission` 新通知走 `notifier`
`printing``shipment` 等旧逻辑仍保留原来的静态方式,不应在本次改动中混改。
本次从旧结构迁到新结构时,做了自动回填:
## 11. 后续建议
- 对每条旧 `Notifier(event_key=...)`
- 自动创建一条 `NotifierRoute`
- `event_key` 原样继承
- `mission_category = null`
- `description` 标记为自动迁移生成
按优先级建议如下:
这样旧配置不会因为结构调整而丢失。
1. 在 admin 使用中观察 `config``template_key` 是否已足够稳定
2. 若 notifier 数量增多,再决定是否拆分“通知端点”与“事件订阅”
3. 若需要追踪投递历史,再增加 `NotificationDelivery`
4. 当 mission 通知稳定后,再考虑逐步迁移新业务节点到 notifier
## 10. 测试覆盖重点
当前测试已覆盖:
- 模板渲染
- backend 发送
- 路由按事件匹配
- 分类专用路由优先于通配路由
- 无专用路由时回退到通配路由
- 任务事件 handler 正常入队
## 11. 当前风险与注意点
### 11.1 当前只对 mission 做了分类路由
当前 `mission_category` 是显式写进 `NotifierRoute` 的业务字段。
这适合当前目标,但如果未来要扩展到其它业务模型的复杂路由,可能需要更抽象的路由条件模型。
### 11.2 config 仍是自由 JSON
后台录入错误仍可能在发送时才暴露。
当前依赖日志排查,后续可增加更强校验。
### 11.3 模板校验仍在发送期暴露
`template_key` 如果写错,会在渲染阶段报错并记录日志。
后续可在 admin 或 model clean 中增强校验。
## 12. 当前结论
当前方案已经满足
当前 `notifier` 已具备
- 独立模块
- admin 配置
- task 化通知
- 模板化内容
- 动态 signal -> notifier 路由
- 后续可扩展到多渠道
- 通知器配置
- 路由配置
- 分类分发
- 任务事件按分类路由
- 管理后台操作支持
- 日志与测试保障
同时复杂度仍控制在较低水平,适合作为第一阶段正式实现
这已经足以支撑你当前提出的“所有任务事件都支持按任务分类路由”的要求