# 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 失效可自动停用