@startuml title Notifier 模块一期类图 skinparam shadowing false skinparam class { BackgroundColor #F8FBFF BorderColor #4C6A92 ArrowColor #4C6A92 FontName Noto Sans CJK SC } skinparam note { BackgroundColor #FFF7E6 BorderColor #B7791F FontName Noto Sans CJK SC } skinparam defaultFontName Noto Sans CJK SC class "Notifier\n通知器配置" as Notifier { +id: BigAutoField +merchant: Merchant +name: CharField +event_key: CharField +channel: CharField +template_key: CharField +is_enabled: Boolean = true +config: JSON +description: Text? +created_at: DateTime +updated_at: DateTime } class "Merchant\nbasic_info.Merchant" as Merchant { +id: BigAutoField +name: CharField } class "Mission Signals\n领域信号" as MissionSignals <> { +mission_created +mission_replied +mission_completed +mission_reply_rejected +mission_reopened +mission_cancelled } class "MissionHandlers\n信号处理器" as MissionHandlers <> { +on_mission_created(...) +on_mission_replied(...) +on_mission_completed(...) +on_mission_reply_rejected(...) +on_mission_reopened(...) +on_mission_cancelled(...) } class "NotifierDispatcher\n通知分发器" as NotifierDispatcher <> { +dispatch(event_key, merchant_id, payload) } class "TemplateRenderer\n模板渲染器" as TemplateRenderer <> { +render(template_key, payload): dict } abstract class "BaseNotifier\n渠道后端抽象" as BaseNotifier { +channel: str +notify(endpoint, event_key, payload, rendered): dict } class "WeComWebhookNotifier\n企业微信通知器" as WeComWebhookNotifier { +channel = \"wecom_webhook\" +notify(endpoint, event_key, payload, rendered): dict } class "EmailNotifier\n邮件通知器" as EmailNotifier { +channel = \"email\" +notify(endpoint, event_key, payload, rendered): dict } class "NotifierRegistry\n通知器注册表" as NotifierRegistry <> { +get(channel): BaseNotifier } class "NotifierTask\n异步任务入口" as NotifierTask <> { +dispatch_notification_event(...) } MissionSignals ..> MissionHandlers : connect MissionHandlers ..> NotifierTask : on_commit + delay NotifierTask ..> NotifierDispatcher : dispatch(...) NotifierDispatcher --> "0..*" Notifier : query by\nmerchant + event_key + is_enabled NotifierDispatcher ..> TemplateRenderer : render(...) NotifierDispatcher ..> NotifierRegistry : get(channel) NotifierRegistry ..> BaseNotifier BaseNotifier <|-- WeComWebhookNotifier BaseNotifier <|-- EmailNotifier Notifier "0..*" --> "1" Merchant : merchant note right of Notifier 一期简化设计: - 不拆 Endpoint / Subscription - 直接把 event_key 绑定在 Notifier 对象上 - 直接把 template_key 配置在 Notifier 对象上 适合通过 Django Admin 做: - 添加 / 删除 - 启用 / 禁用 - 编辑渠道参数 end note note bottom of Notifier config 为 JSON: 企业微信示例可包含: - webhook_key - mentioned_list - mentioned_mobile_list 同一商户下可配置多个 Notifier, 同一个 event_key 可命中多个 Notifier。 end note note right of TemplateRenderer 模板文件不落数据库。 Notifier 仅保存 template_key。 模板集中存放在固定目录,例如: notifier/templates/ end note note left of MissionHandlers handler 不直接拼企业微信内容, 也不直接读取 settings.py 中的静态 key。 handler 只负责把领域事件 转成 event_key + payload, 再交给 notifier 模块。 end note note bottom of NotifierDispatcher 一期暂不引入 NotificationDelivery 表。 发送结果通过日志记录即可, 不做数据库级发送审计。 风险: - 无法在数据库中重放 / 检索历史发送记录 - 排查依赖日志系统 end note note left of Notifier 设计取舍风险: 把 template_key 配到 Notifier 对象上, 会让“同一渠道目标用于多个事件”时产生配置重复。 但这能显著降低一期复杂度, 且更贴合 Admin 直接维护。 end note @enduml