diff --git a/docs/index.html b/docs/index.html index d658899..ca74e0c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -44,6 +44,11 @@

设计方案 / Specs

+ +
系统通知机制(Spec ③)HTML
+
App 内统一通知收件箱(铃铛+列表),六类:重要/新特性/新闻/到账(个人定向)/版本/活动。单 notices 表 + user_id(NULL=广播);已读用服务端 last_read_at 水位多端同步;GET /v1/notices(合并广播+定向,unread_count) + POST /read。生产三路:nodectl notice 子命令(手工) / 事件钩子同事务(购买开通·邀请·首充·TG 到账,零孤儿通知) / 发版脚本联动插 version 公告。邮件仅 important+显式 --email(SMTP 直发+email_sent_at 幂等)。不做:APNs/FCM(二期)/逐条已读/偏好开关/六语内容。原型先行:两端通知视图先统一(桌面pill vs 移动icon)再动代码。
+
docs/notifications-design.html
+
邀请奖励 + 奖励任务(加入 TG 频道)(Spec ②)HTML
用「送 Pro 会员天数」驱动增长,两条并行途径共用发天数原语(applySubscription source=invite/task)。① 邀请两段式双方都得:注册各 +3、被邀请人首充双方再各 +7(每转化 +10/+10)。② 奖励任务首个「加入 Telegram 频道」+3,经 Bot getChatMember 真校验(token 绑号→/tg/webhook→查成员→发)。防刷:自邀请拦截 + invitee 唯一 + dp_uuid 设备去重 + 注册段月度封顶(首充不封)+ telegram_id 全局唯一领。数据:users +invite_code/first_paid_at、referrals、通用 reward_claims。不做现金/多级/退群回收;通知集成留接缝待 Spec③。
diff --git a/docs/notifications-design.html b/docs/notifications-design.html new file mode 100644 index 0000000..e6e2e1d --- /dev/null +++ b/docs/notifications-design.html @@ -0,0 +1,235 @@ + + + + + +系统通知机制 — 设计 + + + +
+← 文档索引 +

系统通知机制

+

设计文档 · 2026-07-13 · Spec ③(系统通知)· 状态 已评审定稿 · 配套实现计划见 索引「实现计划」分类(落定后互链)

+ +
+目标: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 节正式接通。

+ +
+ +