MemoFlow · Notification 模块开发笔记
按版本记录从桌面通知到消息事实、投递策略与可靠渠道的设计。
Notification 模块目标
我希望 Goal、Task、Reminder 等模块产生提醒时,Notification 可以负责把信息呈现给用户。最初先考虑 Windows 右下角通知、声音和应用内弹窗,以后再根据需要接入邮件等渠道。
通知看起来只是“把消息弹出来”,但它实际上还要回答:用户是否应该收到、是否已经投递,以及用户有没有看过。
V1.0.0
先做一个通用通知入口。业务模块不用自己创建 Windows 弹窗,而是把标题、内容和触发信息交给 Notification。
桌面端先比较两种展示方式:
- 系统通知: 直接利用操作系统的提醒能力,与应用窗口解耦;但用户可能关闭通知权限,交互和样式也受系统限制。
- 应用自定义弹窗: 样式与交互更可控,适合带按钮的操作;但窗口管理、定位、多显示器和应用关闭后的行为都需要自己处理。
所以 Notification 可以统一内容,具体怎么显示交给设备端。声音、邮件等作为可选渠道,不要求第一版全部实现。
v1.1.0
现在需要让 Notification 与 Schedule 配合。Goal 或 Task 设定提醒时间,由调度侧在时间到达时触发通知请求。
直接“收到事件就弹窗”还存在一个问题:如果用户正好离线,或者事件重复消费,通知可能丢失,也可能被显示两次。单靠 UI 的一次函数调用,不能说明消息已经可靠地产生。
因此准备区分业务发出通知请求和设备实际显示通知。业务请求需要稳定身份与去重依据,界面展示失败也不能反过来删除业务曾经请求过通知这件事。
V2.0.0
这版把 Notification 拆成三个核心层次。
- Notification Fact: 一条用户能够在消息中心查看、标记已读的消息事实,独立于发送渠道。
- Workflow / Delivery Policy: 判断这类消息允许通过哪些渠道呈现,应用用户偏好和勿扰时间。
- Dispatch Outbox / Delivery Attempt: 管实际投递、重试、回执和失败记录。
选择这个结构,是因为通知存在、通知投递成功、用户已读是三个不同状态。例如静默时段禁止弹窗,并不意味着消息中心里不应该有这条提醒。
业务模块只提交受控的 NotificationRequested,Notification 以用户身份和幂等键生成 Fact。Notification Center 展示 Fact 的读取结果,而不是再造一条叫作 “InApp” 的独立消息真值。
v2.1.0
可靠投递还要考虑设备权限、断线和多渠道差异。
后台可以通过 Outbox、lease 和重试推进投递;Desktop 的系统通知需要设备能力与确认反馈;Web 的实时消息需要处理 SSE 断线、游标恢复和补发。一个渠道失败,不代表所有渠道都失败。
勿扰和用户偏好属于投递策略:可以延期或压制某种展示,但不应默认清除 Fact。用户已读只改变收件箱状态,也不证明系统通知真实弹出过。
这些规则需要尽量由 Notification 统一维护,不能由 Goal、Routine 等模块分别写一套“关闭弹窗”的判断。
V3.0.0
现在要让 Notification 只负责通知与用户交互的边界,而不接管原业务状态。
例如用户在提醒里选择“完成任务”,Notification 可以把操作转成有类型的 Action Intent,再交给 Task 的业务命令处理;不能直接修改 Task 表。Schedule / Scheduler 负责触发时机,Routine 决定该不该产生一次提醒,Notification 再判断如何呈现。
这样 Notification Center、桌面通知和 Web 实时入口能共享一条消息事实,又允许每种设备按自己的能力展示。