170 lines
5.6 KiB
Markdown
170 lines
5.6 KiB
Markdown
# App Push|每日提醒推送(客户端 + 后端)|Plan
|
||
|
||
> 阶段:技术计划(plan)
|
||
>
|
||
> 依据:`spec_kit/App Push/spec.md`
|
||
|
||
---
|
||
|
||
## 1. 总体思路
|
||
|
||
本期以 **Expo Push(Expo Push Token + Expo Push Service)** 为推送通道,实现“每日提醒”闭环:
|
||
|
||
- **客户端**:负责
|
||
- 生成/持久化 `client_user_id`(UUID)
|
||
- 引导用户设置每日次数(0~5,可跳过)
|
||
- 申请通知权限、获取 Expo Push Token、并上报后端
|
||
- 在个人主页的“每日提醒”弹窗修改次数或关闭,并同步到后端
|
||
- 上报 `timezone`(IANA)与 `locale`(用于文案语言)
|
||
- **后端**:负责
|
||
- 保存 token 绑定与用户推送偏好
|
||
- 每日按用户时区在 9:00~24:00 窗口内生成 \(N\in[0,5]\) 个随机抖动时间点
|
||
- 在这些时间点向用户发送推送(使用后端 `@overview.md` 约定的个性化推荐模板/文案策略)
|
||
- 幂等防重复(同一用户同一天同一序号只发一次)
|
||
|
||
---
|
||
|
||
## 2. 客户端设计
|
||
|
||
### 2.1 UUID(client_user_id)
|
||
|
||
- 复用 `spec_kit/Client User Identity/spec.md` 的结论:
|
||
- 默认 UUID v4
|
||
- 本地稳定持久化
|
||
- 获取时机:
|
||
- App 启动即确保已生成(便于后续任何上报都可带上)
|
||
|
||
### 2.2 Push 权限与 token 获取策略
|
||
|
||
- 在 Onboarding 的“每日提醒”设置页(或 push 引导页)进行权限申请:
|
||
- 先说明 → 再弹系统权限框
|
||
- token 获取与上报触发:
|
||
- 权限 granted 后立即获取 token 并上报
|
||
- 后续冷启动时可“补偿上报”(避免首轮失败导致后端缺 token)
|
||
|
||
### 2.3 每日提醒设置(0~5 次)
|
||
|
||
#### Onboarding
|
||
|
||
- 新增或复用一个 Onboarding 步骤:
|
||
- 0~5 次选择
|
||
- “跳过”按钮(跳过不阻塞)
|
||
- 当用户选择 `times_per_day > 0`:
|
||
- 引导用户开启系统通知权限
|
||
- 成功与否都进入主功能
|
||
|
||
#### 个人主页弹窗(Daily Reminder)
|
||
|
||
- 现有弹窗:
|
||
- 次数 +/-(限制 0~5)
|
||
- 开关(关闭等价 `times_per_day=0`)
|
||
- 权限 denied:给出清晰提示
|
||
- 修复:移除/关闭“测试模式强制无权限”逻辑(该逻辑会导致永远表现为无法开启)
|
||
|
||
### 2.4 客户端与后端接口调用
|
||
|
||
- 新增 `client/src/services/pushApi.ts`(参考现有 `legalApi.ts` / `recoApi.ts` 的风格),并使用统一 HTTP 封装 `client/src/utils/http.ts`
|
||
- 接口调用点:
|
||
- register:拿到 token 后
|
||
- preferences:用户完成选择或在个人主页修改后
|
||
|
||
---
|
||
|
||
## 3. 后端设计
|
||
|
||
### 3.1 数据模型(最小可用)
|
||
|
||
1) `push_tokens`
|
||
- 唯一键:`env + app_id + push_token`
|
||
- 字段:
|
||
- `client_user_id`(UUID string)
|
||
- `platform`(ios/android)
|
||
- `push_token`
|
||
- `app_id`
|
||
- `env`
|
||
- `is_active`
|
||
- `last_seen_at`
|
||
|
||
2) `push_preferences`
|
||
- 主键:`client_user_id`(或加 `env + app_id` 做隔离)
|
||
- 字段:
|
||
- `enabled`
|
||
- `times_per_day`(0~5)
|
||
- `timezone`(IANA,来自客户端)
|
||
- `locale`(用于文案选择)
|
||
- `updated_at`
|
||
|
||
3) `push_send_log`(幂等防重复)
|
||
- 目的:保证“同一用户同一天第 k 条只发一次”
|
||
- 唯一键:
|
||
- `client_user_id + local_date + slot_index`(slot_index=1..times_per_day)
|
||
- 字段:
|
||
- `scheduled_at`(用户时区的时间点)
|
||
- `sent_at`
|
||
- `status`(scheduled/sent/failed)
|
||
- `error`(可选)
|
||
|
||
> 注:不直接对数据库执行破坏性操作;通过迁移脚本落地表结构,执行迁移需你显式允许后才进行。
|
||
|
||
### 3.2 API
|
||
|
||
按 `spec_kit/App Push/spec.md`:
|
||
|
||
- `POST /v1/push/register`
|
||
- `PUT /v1/push/preferences`
|
||
- `GET /v1/push/preferences`
|
||
- `POST /v1/push/test`(仅 dev)
|
||
|
||
### 3.3 推送发送器(Expo)
|
||
|
||
- 使用 `https://exp.host/--/api/v2/push/send`
|
||
- 最小 payload:
|
||
- `to`:Expo Push Token
|
||
- `title/body`:来自个性化推荐模板
|
||
- `data`:深链路参数(可选)
|
||
- 失败处理:
|
||
- 对 “DeviceNotRegistered”等不可恢复错误,将 token 标记为 inactive
|
||
- 记录失败原因到 `push_send_log`
|
||
|
||
### 3.4 定时任务(每日 9~24,随机抖动)
|
||
|
||
策略:**每日“生成当天计划 + 分发 ETA 任务”**(推荐)
|
||
|
||
- 每日固定时刻(例如 UTC 00:10 或服务器本地时间某个点)执行一次“生成计划任务”:
|
||
- 对每个 `enabled && times_per_day>0` 的用户:
|
||
- 将用户时区当天的 9:00~24:00 转换为 UTC
|
||
- 随机生成 \(N\) 个时间点:
|
||
- 均匀切分窗口为 \(N\) 个区间
|
||
- 每个区间内随机选一个时间(抖动)
|
||
- 对每个 slot 写入 `push_send_log`(唯一键保证幂等)
|
||
- 再投递 Celery ETA 任务,到点调用“发送 push”
|
||
|
||
这样可以避免 worker 长时间 sleep,也便于观察“今天会推哪些”。
|
||
|
||
---
|
||
|
||
## 4. 个性化推荐模板(后端 @overview.md)
|
||
|
||
推送文案生成使用后端推荐模块中 “Push 场景模板/策略”:
|
||
|
||
- 输入:`client_user_id`、(可选)用户画像/行为统计
|
||
- 输出:`title/body`(多语言)
|
||
- 风险控制:Push 场景默认“降个性化/降风险”,避免敏感/医疗类文案(遵守后端已定义的安全规则)
|
||
|
||
> plan 阶段仅明确“调用点与输入输出”,具体实现复用后端推荐模块现有能力,避免重复逻辑。
|
||
|
||
---
|
||
|
||
## 5. 测试与验收(落地检查清单)
|
||
|
||
- 客户端:
|
||
- 首次进入可选择次数/跳过
|
||
- 权限 granted 时能拿到 Expo token 并上报
|
||
- 设置页改次数/关闭会调用 preferences 接口并持久化
|
||
- 后端:
|
||
- register/preferences 接口幂等可重试
|
||
- test 接口可在 dev 环境立刻推送到真机
|
||
- 每日计划生成不会重复排程(`push_send_log` 唯一键)
|
||
- token 失效可自动停用
|
||
|