- GET /api/v1/notifications(分页,时间倒序不分组)/unread-count /read(ids|all),
对外 camelCase(PRD 前端契约);数据为内存 mock(13 类型全覆盖,重启复位),
接真实数据只换 repositories/notification_mock.py
- vendor_push 补华为 Push Kit(OAuth+messages:send),抽通用 send_notification
(任意文案+extras+mock 模式),send_accessibility_disabled 改薄封装行为不变
- 新增 /api/v1/push/{vendors,templates,test}:凭据状态/13 类 PRD 文案模板/测试发送
(默认 mock,mock=false 真发,可联动插站内 mock 消息闭环验证已读)
- .env.example 补 HUAWEI_* 键;docs/api 新增 notifications.md + push-vendor-test.md
- alembic merge 1a924c274fce 收拢 direct_vendor_push_fields/feedback_type_reply
双 head,恢复 upgrade head 可用
- tests: test_notifications + test_push_center 共 35 例
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.5 KiB
消息通知中心(notifications 族)
所属:notifications 组(前缀
/api/v1/notifications,源app/api/v1/notifications.py) | 鉴权:全部 Bearer(消息按用户隔离) | ← 返回 API 索引对应 PRD《消息通知中心》(通知类型清单 / 点击跳转 / 字段元素 / 未读红点 / Push 文案)。 Push 侧(厂商直推 + 测试)见 push-vendor-test.md。
⚠️ 虚拟数据阶段:数据来自内存 mock(
app/repositories/notification_mock.py),每个用户首次访问生成一套固定样例(13 种类型全覆盖 + 今天/昨天/当年/跨年四种时间 + 已读未读混合,共 16 条,id 从 90001 起);已读状态在服务端内存维护,重启服务复位。后续接真实数据只换 repository 实现,接口契约不变。⚠️ 字段命名:本组接口(含 push 测试组)对外为 camelCase(
sentAt/isRead/pageSize…),与库内其他 snake_case 接口不同——按 PRD 前端契约对接,勿混用。
通知类型速查(13 种)
列表服务端已排好序:全列表按时间倒序(最新在前,不做分类分组——PRD §1 的"按分类分组"为笔误,2026-07-14 需求方确认取消),前端按返回顺序渲染即可。category 仅用于卡片头部的分类标签展示。
| category | 分类标签 | type | 类型 | cardStyle 版式 | actionText 操作行 | extra 里带什么 |
|---|---|---|---|---|---|---|
| withdraw_assistant | 提现助手 | reward_expiring |
金币现金奖励即将失效 | dual_amount 双金额卡 | 立即激活您的收益 | batchId |
| withdraw_assistant | 提现助手 | reward_expired |
金币现金奖励已失效 | dual_amount 双金额卡 | 立即赚取新收益 | — |
| withdraw_assistant | 提现助手 | withdraw_success |
提现成功 | withdraw 提现卡 | null(无操作行,点击仅消红点) | — |
| withdraw_assistant | 提现助手 | withdraw_failed |
提现失败,款项已退回 | withdraw 提现卡 | 重新提现 | withdrawId |
| system | 系统通知 | perm_accessibility |
比价功能异常(无障碍) | plain_text 纯文本卡 | 去开启 | permission:"accessibility" |
| system | 系统通知 | perm_battery |
比价续航保护异常(省电策略) | plain_text 纯文本卡 | 去开启 | permission:"battery" |
| system | 系统通知 | perm_autostart |
比价启动保护异常(自启动) | plain_text 纯文本卡 | 去开启 | permission:"autostart" |
| system | 系统通知 | perm_overlay |
比价按钮异常(悬浮窗) | plain_text 纯文本卡 | 去开启 | permission:"overlay" |
| feedback | 我的反馈 | feedback_reply |
官方回复 | plain_text 纯文本卡 | 查看详情 | feedbackId |
| feedback | 我的反馈 | feedback_reward |
反馈奖励(必带官方留言行) | coin_reward 金币奖励卡 | 查看反馈详情 | feedbackId |
| report | 我的爆料 | report_approved |
爆料审核通过 | coin_reward 金币奖励卡 | 查看爆料详情 | reportId |
| invite | 好友邀请 | invite_order_reward |
好友下单奖励到账 | friend_cash 好友现金卡 | 邀请更多好友赚现金 | inviteeNickname |
| invite | 好友邀请 | invite_remind |
好友催单提醒 | plain_text 纯文本卡 | 去提醒 TA | inviteeNickname, scrollTo:"remind" |
点击跳转逻辑按 PRD §2 由客户端按 type 分发;点击目标 = 整张卡片(不区分主体和操作行),任何点击都先调 POST /read 消该条红点。
GET /api/v1/notifications — 消息列表(分页)
入参(query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page |
int | ❌ | 页码,1 起,默认 1 |
pageSize |
int | ❌ | 每页条数,默认 20,最大 100 |
出参
{
"items": [
{
"id": 90001,
"category": "withdraw_assistant", // 分类 key(5 种,见上表)
"categoryLabel": "提现助手", // 卡片头部左上角分类标签
"type": "reward_expiring", // 类型 key(13 种,决定点击行为)
"cardStyle": "dual_amount", // 版式:dual_amount/withdraw/plain_text/coin_reward/friend_cash
"title": "金币现金奖励即将失效", // 卡片标题
"coins": 86, // 金币数,整数;无金币的版式为 null
"cashCents": 1280, // 现金金额(分);无现金的版式为 null
"cashYuan": "12.80", // 现金展示串(元,两位小数),与 cashCents 同源
"infoRows": [ // 信息行,已按 PRD 拼好文案,逐行 label: value 渲染
{ "label": "过期说明", "value": "您有86金币和12.80元现金即将失效,完成一次一键领券或一键比价即可激活收益" },
{ "label": "过期时间", "value": "3天后失效" }
],
"actionText": "立即激活您的收益", // 操作行;null = 无操作行(提现成功卡)
"extra": { "batchId": "batch_20260714" }, // 跳转/联动参数,按 type 取用(见上表)
"sentAt": "2026-07-14T14:59:58+08:00", // ISO8601 带时区
"isRead": false // false → 分类标签右侧显示 6px 红点(#E53935)
}
],
"page": 1,
"pageSize": 20,
"total": 16,
"hasMore": false,
"unreadCount": 12 // 与 /unread-count 同口径,进页面可顺手刷角标
}
时间显示规则(前端处理 sentAt):今天 →「今天」;昨天 →「昨天」;当年 →「M月D日」(不补零);跨年 →「YYYY年M月D日」。mock 样例特意覆盖了这四种情况。
数值约束(PRD §3):金币整数不带小数;现金/提现金额两位小数(直接用 cashYuan)。
GET /api/v1/notifications/unread-count — 未读总数(首页铃铛角标)
无入参。出参:
{ "count": 12, "badgeText": "12" } // count>99 时 badgeText="99+";count=0 时 badgeText=null → 整个角标隐藏
刷新时机(PRD §4):进入首页时、从通知中心/其他页面返回首页时(原型监听 pageshow)。
POST /api/v1/notifications/read — 标记已读
入参(JSON),两种模式二选一(同时传时 all 优先)
| 模式 | body | 使用场景 |
|---|---|---|
| 单条/多条 | { "ids": [90001, 90003] } |
① 点击某张消息卡片(点击后无论跳转/弹窗/无动作都算已读);② 用户点击 push 直达落地页后,客户端拿 push extras 里的 notificationId 同步置读 |
| 全量清零 | { "all": true } |
进入通知中心自动清零(只浏览列表就消红点,无需逐条点击;退出通知中心时也可再调一次兜底) |
出参
{ "ok": true, "markedCount": 2, "unreadCount": 10 } // unreadCount = 处理后剩余未读,可直接刷新角标
幂等:不存在/已读的 id 忽略,重复调用 markedCount=0 不报错。
错误:400 ids 与 all 都没传(或 ids 为空数组);401 未鉴权。
联调小抄
# 1. 登录拿 token(SMS mock:任意手机号 + 任意 6 位验证码)
curl -X POST :8770/api/v1/auth/sms/send -d '{"phone":"13800001234"}'
curl -X POST :8770/api/v1/auth/sms/login -d '{"phone":"13800001234","code":"123456"}'
# 2. 列表 / 角标 / 置读
curl ":8770/api/v1/notifications?page=1&pageSize=20" -H "Authorization: Bearer $TOKEN"
curl ":8770/api/v1/notifications/unread-count" -H "Authorization: Bearer $TOKEN"
curl -X POST ":8770/api/v1/notifications/read" -d '{"all":true}' -H "Authorization: Bearer $TOKEN"
要重置 mock 数据(重新生成整套样例):重启服务即可。想造「push → 站内联动」数据,用 POST /api/v1/push/test 的 createNotification:true。