forked from erp-dev/erp
feat: mission category
This commit is contained in:
@@ -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 路由
|
||||
- 后续可扩展到多渠道
|
||||
- 通知器配置
|
||||
- 路由配置
|
||||
- 分类分发
|
||||
- 任务事件按分类路由
|
||||
- 管理后台操作支持
|
||||
- 日志与测试保障
|
||||
|
||||
同时复杂度仍控制在较低水平,适合作为第一阶段正式实现。
|
||||
这已经足以支撑你当前提出的“所有任务事件都支持按任务分类路由”的要求。
|
||||
|
||||
Reference in New Issue
Block a user