← 文档索引

系统通知机制

设计文档 · 2026-07-13 · Spec ③(系统通知)· 状态 已评审定稿 · 配套实现计划见 notifications-plan.html

目标:App 内统一通知收件箱(顶栏铃铛 + 通知列表),覆盖六类内容:重要通知 / 新特性 / 新闻 / 到账(购买·邀请·任务奖励,个人定向)/ 版本 / 活动。
送达:App 内为主(打开/回前台拉取),邮件仅兜底「重要通知」且需显式指定;系统推送(APNs/FCM)明确二期。
决策(已定):单表 + audience(user_id 空=广播)· 已读用服务端 last_read_at 水位(多端同步)· 发版脚本联动插 version 公告 · 发布走 nodectl notice 子命令。
现状底子:GET /v1/notices 空壳已在(恒空列表);openapi 已有双语 Notice schema(需扩 type/link/unread);客户端占位屏/铃铛/五条 l10n 已备;jiu 无可参考实现(其通知为二期占位)。

1 · 类型口径(单一来源)

type标签(zh/en)受众典型内容生产方式
important重要 / Important广播(可定向)服务条款、安全、合规、线路维护nodectl(可 --email)
feature功能 / Feature广播新增大阪线路、协议选择上线nodectl
news新闻 / News广播产品动态、博客nodectl
reward到账 / Credited个人定向购买开通 ×31 天、邀请注册 +3、首充 +7、TG 任务 +3事件钩子自动(同事务)
version版本 / Update广播vX.Y.Z 发布 + 要点;点击接更新流程发版脚本联动
promo活动 / Promo广播¥6 限购上线、邀请双倍周、年付折扣nodectl

promo 与 feature/news 的边界:让用户「花钱/参与活动」的归 promo;产品功能上新归 feature;动态资讯归 news。类型集与客户端类型 pill、原型登记、l10n 标签三处须同集(L1 闸)。

2 · 数据流总览

nodectl notice add important/feature/news/promo 事件钩子(同事务) 购买开通 · 邀请/首充/TG 到账 release-client.sh 发版联动插 version 公告 notices 表 user_id NULL=广播 非空=个人定向 GET /v1/notices 广播+我的 · unread 按水位算 · unread_count POST /v1/notices/read users.notices_read_at = now(多端同步) 客户端 noticesProvider 启动延迟拉 + 回前台拉;铃铛红点=unread_count>0 打开列表 → 调 read 清红点 邮件兜底(SMTP) 仅 important + 显式 --email 系统推送(APNs/FCM)二期;openapi 描述过的「静态镜像 JSON 副本」意图保留、本期不实现。

3 · 数据模型(迁移 000026,sqlite/mysql 两套)

notices 新表:

类型说明
idPK 自增
typeCHECK/ENUM(六值)important|feature|news|reward|version|promo
user_idbigint NULLNULL=全员广播;非空=定向该用户(比字符串 audience 可索引);FK users(id)
title_zh / title_entext双语标题(内容仅 zh/en;ja/ko/ru/es 界面回落 en)
body_zh / body_entext NULL双语正文(Markdown;reward 类一行文案可空)
linktext NULL可选动作(version 类点击接更新流程等)
published_atdatetime排序与未读水位比较基准(Go 端算好传 ?)
expires_atdatetime NULL到期自动不再下发(promo 常用)
revoked_atdatetime NULL撤回(nodectl notice revoke)
email_sent_atdatetime NULL邮件兜底幂等标记(同公告只发一轮)
索引:(user_id, published_at);广播查询走 user_id IS NULL 分支

users 加一列:notices_read_at DATETIME NULL —— 已读水位:published_at > notices_read_at 即未读。不做逐条已读(YAGNI,水位方案多端同步且零关联表)。

4 · API(扩现有空壳 accountAPI.ListNotices)

端点行为
GET /v1/notices(protected)返回 {notices:[…], unread_count}:合并「user_id IS NULL + user_id=me」,过滤 revoked_at IS NOT NULLexpires_at < now,published_at 倒序 limit 50;每条含 id/type/title_zh/title_en/body_zh/body_en/link/published_at/unread(unread 服务端按水位算)。
POST /v1/notices/read(protected)UPDATE users SET notices_read_at=?(now)。客户端打开通知列表时调,红点全端清零。

5 · 生产者(三路)

5.1 nodectl notice 子命令(手工发布,important/feature/news/promo)

nodectl notice add --type important --title-zh 线路维护通知 --title-en "Line Maintenance" \
  [--body-zh …] [--body-en …] [--link …] [--expires 2026-08-01] [--email]
nodectl notice list [--all]          # 含已撤回/过期
nodectl notice revoke <id>

5.2 事件自动通知(reward 类,个人定向,同事务)

事件接缝(既有事务内追加一条 INSERT)文案模板(双语,服务端渲染入库)
购买开通pay webhook.settle(Grant→MarkPaid 之后、Commit 前)「已开通 Pro ·31 天 / Pro activated · 31 days」
邀请注册奖励(双方)reward OnRegister 两次 Grant 后「邀请奖励 +3 天已到账 / Invite reward +3 days credited」
首充奖励(双方)reward OnFirstPaidTx 两次 Grant 后「好友首购奖励 +7 天已到账 / …+7 days credited」
TG 任务reward ClaimTelegram Grant 后 Commit 前「任务奖励 +3 天已到账 / Task reward +3 days credited」

同事务不变量:通知与发放同 COMMIT/同回滚——用户看到「到账」通知 ⟺ 权益真到账,零孤儿通知(与钱路既有纪律一致)。实现为 reward/notices 侧一个 InsertNoticeTx(ctx, tx, userID, type, …) 小函数,各接缝一行调用。

5.3 发版联动(version 类)

6 · 邮件兜底

7 · 客户端

8 · 边界(YAGNI,本期不做)

9 · 测试要点

10 · 涉及文件(实现锚点)

# 后端
server/migrations/{mysql,sqlite}/000026_notices.{up,down}.sql   # notices 表 + users.notices_read_at
server/internal/notices/                    # 新包:store(查询合并/水位/InsertNoticeTx)+ service(邮件兜底)+ handler
server/internal/httpapi/account.go          # ListNotices 空壳替换为真实现(或移交 notices 包)
server/internal/reward/service.go           # OnRegister/OnFirstPaidTx/ClaimTelegram 接 InsertNoticeTx
server/internal/pay/webhook.go              # settle 接购买开通通知
server/cmd/nodectl/                          # notice add/list/revoke 子命令
scripts/ci/release-client.sh                 # 发版联动插 version 公告
server/api/openapi.yaml                      # Notice schema 扩 type/link/unread + /notices/read
# 客户端
design/prototype/screens/{ui-desktop,ui-mobile}.html  # 原型先行:两端通知视图统一 + 三类新示例
client/lib/state/notices_provider.dart       # 新:拉取/未读/read
client/lib/services/notices_api.dart         # 新:GET /v1/notices + POST /read
client/lib/screens/notifications_page.dart   # 占位 → 真实
client/lib/widgets/{notification_bell,content_top_bar}.dart + screens/account_page.dart  # 红点接 provider
client/lib/l10n/                              # notifTypeReward/Version/Promo 六语

下一步:writing-plans 出逐任务实现计划(md 真相源 + HTML 阅读版),Subagent 驱动执行。与 邀请奖励设计(Spec ②)的「奖励事件接缝」在 5.2 节正式接通。