a8ac5dc0c7
- 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>
103 lines
6.0 KiB
Markdown
103 lines
6.0 KiB
Markdown
# 厂商推送测试三件套(push 族)
|
|
|
|
> 所属:push 组(前缀 `/api/v1/push`,源 `app/api/v1/push.py`) | 鉴权:**全部 Bearer** | [← 返回 API 索引](./README.md)
|
|
>
|
|
> 发送实现:`app/integrations/vendor_push.py`(荣耀 / **华为** / 小米 / OPPO / vivo 五通道,
|
|
> `send_notification()` 通用入口)。站内消息中心见 [notifications.md](./notifications.md)。
|
|
> 与 `POST /api/v1/device/push-test`(无障碍召回通道延迟自测)互补:本组面向消息中心 13 类 push 的文案/参数/通道联调。
|
|
>
|
|
> 字段命名同 notifications 族:**camelCase**。
|
|
|
|
## 链路总览
|
|
|
|
```
|
|
真实业务事件(奖励过期/提现回执/审核通过/…)
|
|
└→ vendor_push.send_notification(vendor, token, title, body, extras)
|
|
extras = { type, notificationId, ...业务参数 } ← 客户端深链 + 已读联动的钥匙
|
|
客户端点击 push → 按 extras.type 直达落地页(与站内点击一致)
|
|
→ 调 POST /notifications/read {ids:[extras.notificationId]} 同步消红点(PRD §4)
|
|
```
|
|
|
|
推送目标来源:客户端集成各厂商 push SDK 拿到 regId/token 后,通过 `POST /api/v1/device/register` 上报 `push_vendor` + `push_token`,服务端存 `device_liveness` 表。
|
|
|
|
## GET /api/v1/push/vendors — 厂商配置状态
|
|
|
|
检查 5 家厂商服务端凭据是否配齐(只读 .env,不打厂商接口)。`missingKeys` 即还要补的配置键;mock 测试不依赖任何凭据。
|
|
|
|
```jsonc
|
|
{ "vendors": [
|
|
{ "vendor": "honor", "label": "荣耀", "configured": false, "missingKeys": ["HONOR_PUSH_APP_ID", "HONOR_PUSH_CLIENT_ID", "HONOR_PUSH_CLIENT_SECRET"] },
|
|
{ "vendor": "huawei", "label": "华为", "configured": false, "missingKeys": ["HUAWEI_PUSH_APP_ID", "HUAWEI_PUSH_APP_SECRET"] },
|
|
{ "vendor": "xiaomi", "label": "小米", "configured": true, "missingKeys": [] },
|
|
{ "vendor": "oppo", "label": "OPPO", "configured": false, "missingKeys": ["OPPO_PUSH_APP_KEY", "OPPO_PUSH_MASTER_SECRET"] },
|
|
{ "vendor": "vivo", "label": "vivo", "configured": false, "missingKeys": ["VIVO_PUSH_APP_ID", "VIVO_PUSH_APP_KEY", "VIVO_PUSH_APP_SECRET"] }
|
|
] }
|
|
```
|
|
|
|
## GET /api/v1/push/templates — 13 类通知的 push 模板预览
|
|
|
|
PRD §5 的 13 条 push 文案(标题固定 ≤11 字不带变量;正文 `{var}` 为变量,示例值即 PRD 示例)。对文案、看变量名用。
|
|
|
|
```jsonc
|
|
{ "templates": [
|
|
{
|
|
"type": "withdraw_success",
|
|
"category": "withdraw_assistant", "categoryLabel": "提现助手", "cardStyle": "withdraw",
|
|
"pushTitle": "提现到账提醒",
|
|
"pushBodySample": "¥0.50已存入您的微信钱包,点击查看到账详情", // 用示例值渲染后的效果
|
|
"pushBodyTemplate": "¥{amount}已存入您的微信钱包,点击查看到账详情",
|
|
"variables": ["amount"],
|
|
"sampleVars": { "amount": "0.50" }
|
|
}
|
|
// ... 共 13 条,顺序即 PRD 编号
|
|
] }
|
|
```
|
|
|
|
## POST /api/v1/push/test — 测试发送(默认 mock)
|
|
|
|
**入参(JSON)**
|
|
|
|
| 字段 | 类型 | 必填 | 说明 |
|
|
|---|---|---|---|
|
|
| `vendor` | string | ❌* | `honor/huawei/xiaomi/oppo/vivo`,中文「华为」「小米」等别名也识别;留空时用 `deviceId` 设备上报的 vendor |
|
|
| `pushToken` | string | ❌* | 厂商 push token/regId;留空则按 `deviceId` 反查已注册设备(*mock 模式两者都缺时用占位 token,只看渲染结果*) |
|
|
| `deviceId` | string | ❌ | 客户端 `DeviceId.get()` 的设备 id,用于反查 vendor+token |
|
|
| `type` | string | ❌ | 13 种类型 key 之一 → 按 PRD 模板渲染;不传且没直给文案 → 发通用测试文案 |
|
|
| `vars` | object | ❌ | 覆盖模板变量,如 `{"coins":"520","cash":"6.66"}`;缺省用 PRD 示例值 |
|
|
| `title` / `content` | string | ❌ | 直接指定标题/正文(优先于 type 模板) |
|
|
| `createNotification` | bool | ❌ | true = 同时往该用户消息中心插一条同类型未读 mock 通知,extras 带其 `notificationId` → 可闭环验证「点 push → 调 /notifications/read 消红点」(仅 type 合法时生效) |
|
|
| `mock` | bool | ❌ | **默认 true = 不真调厂商 API**,回显渲染结果;false = 真发到手机(要求该厂商凭据已配) |
|
|
|
|
**出参**
|
|
|
|
```jsonc
|
|
{
|
|
"ok": true, "mock": true, "vendor": "huawei",
|
|
"title": "反馈奖励已到账",
|
|
"body": "谢谢您帮傻瓜比价变得更好,300金币已到账,还有一条给您的留言~",
|
|
"extras": { "type": "feedback_reward", "feedbackId": "3002", "notificationId": "90017" },
|
|
"notificationId": 90017, // createNotification=true 时的站内 mock 通知 id
|
|
"missingKeys": ["HUAWEI_PUSH_APP_ID", "HUAWEI_PUSH_APP_SECRET"], // 真发前还缺的配置(真发成功时必为空)
|
|
"vendorResponse": null // 真发时为厂商 API 原始响应
|
|
}
|
|
```
|
|
|
|
**错误**:`400` vendor/type 非法、真发但凭据未配(detail 列缺失键);`409` 真发但拿不到 pushToken;`502` 厂商 API 返回失败(detail 带厂商原始错误)。
|
|
|
|
**真发注意**:
|
|
- 目标手机必须先装 App 且客户端已集成对应厂商 SDK、`/device/register` 上报过 token;
|
|
- vivo 未上架前走测试推送(`VIVO_PUSH_MODE=1`),目标手机需在 vivo 开放平台加入测试设备;
|
|
- 小米新设备需在开放平台把签名/包名配好,token 才有效。
|
|
|
|
## 厂商凭据怎么拿(.env 键名)
|
|
|
|
| 厂商 | 后台 | 需要的键 |
|
|
|---|---|---|
|
|
| 华为 | AGC 控制台 → 项目设置 → 常规 → 应用 | `HUAWEI_PUSH_APP_ID`、`HUAWEI_PUSH_APP_SECRET`(OAuth client_id 即 AppId) |
|
|
| 荣耀 | 荣耀开发者服务平台 → 推送服务 | `HONOR_PUSH_APP_ID`、`HONOR_PUSH_CLIENT_ID`、`HONOR_PUSH_CLIENT_SECRET` |
|
|
| 小米 | 开放平台 → 消息推送 → 应用秘钥 | `XIAOMI_PUSH_APP_SECRET`(服务端只要这个;AppID/AppKey 是客户端 SDK 用) |
|
|
| OPPO | 开放平台 → 推送服务 | `OPPO_PUSH_APP_KEY`、`OPPO_PUSH_MASTER_SECRET`(注意是**服务端 MasterSecret**) |
|
|
| vivo | 开放平台 → 推送 | `VIVO_PUSH_APP_ID`、`VIVO_PUSH_APP_KEY`、`VIVO_PUSH_APP_SECRET` |
|
|
|
|
各家发送协议差异(鉴权方式/成功码/payload 结构)封装在 `integrations/vendor_push.py`,业务侧只面对 `send_notification()`。
|