From 83d3c2a51ad233f3e9f9cda24d394b235b0c6ed0 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Mon, 7 Sep 2026 17:00:06 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20HMS-PUSH-PLAN.md=20=E5=8D=8E=E4=B8=BA?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=8E=A8=E9=80=81=E6=9C=8D=E5=8A=A1=E7=AB=AF?= =?UTF-8?q?=E9=9B=86=E6=88=90=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/HMS-PUSH-PLAN.md | 226 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 docs/HMS-PUSH-PLAN.md diff --git a/docs/HMS-PUSH-PLAN.md b/docs/HMS-PUSH-PLAN.md new file mode 100644 index 0000000..3993771 --- /dev/null +++ b/docs/HMS-PUSH-PLAN.md @@ -0,0 +1,226 @@ +# 华为统一推送(HMS Push)服务端集成方案 + +> pi(API 维护方)编写 | 2026-09-07 + +--- + +## 一、背景 + +当前 Gateway 通过 SSE(Server-Sent Events)向在线客户端实时推送新邮件。但当客户端在后台或被系统杀死时,SSE 断开,用户无法收到新邮件通知。华为鸿蒙统一推送(HMS Push)可在设备关闭应用时仍推送通知——点击通知打开应用并跳转对应邮件。 + +**目标**:Gateway 在 SSE 之外增加推送管道,当 `new_mail` 到达时,对所有已注册推送 token 的设备发送 HMS Push 通知。 + +--- + +## 二、HMS Push API 工作方式 + +``` +客户端 (鸿蒙) 服务端 (Gateway) 华为 Push API + │ │ │ + │── 注册 HMS Push,获取 token ──→ │ │ + │ │ │ + │── POST /me/devices/push-token ──→│ 存储 token │ + │ │ │ + │ ...应用关闭/进入后台... │ │ + │ │ │ + │ ← new_mail SSE 断开 ────────── │ │ + │ │ │ + │ │── 调用 HMS Push API ────────→│ + │ │ (POST v1/messages:send) │ + │ │ │ + │ │ ← 推送到设备 ──│ + │ ← 通知栏弹出 ────────────────── │ │ + │── 用户点击通知 ──→ 打开应用 ──────│ │ │ +``` + +**HMS Push API 核心接口**: +``` +POST https://push-api.cloud.huawei.com/v1/{app_id}/messages:send +Authorization: Bearer {access_token} +Body: { + "validate_only": false, + "message": { + "token": ["push_token_1", ...], + "notification": { + "title": "新邮件:{subject}", + "body": "发件人:{from_name}" + }, + "data": { + "mail_id": "...", + "session_id": "...", + "action": "open_mail" + } + } +} +``` + +**认证**:需要华为开发者账号的 App ID + App Secret → 获取 access_token(有效期 1 小时,需刷新)。 + +--- + +## 三、服务端架构 + +### 3.1 数据模型 + +新增 `push_tokens` 表(SQLite + PostgreSQL 通用): + +```sql +CREATE TABLE push_tokens ( + user_id TEXT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE, + push_token TEXT NOT NULL, -- HMS Push token(设备唯一) + platform TEXT NOT NULL DEFAULT 'harmonyos', -- 标识来源(未来可扩展) + device_id TEXT NOT NULL DEFAULT '', -- 客户端生成的设备唯一 ID + created_at DATETIME DEFAULT (datetime('now')), + updated_at DATETIME DEFAULT (datetime('now')), + PRIMARY KEY (user_id, push_token) +); +``` + +- 每个用户可注册多个设备(手机、平板) +- 同一设备的 token 更新时 upsert(`device_id` 去重) +- 用户删除时级联清理 + +### 3.2 API 端点 + +``` +POST /me/devices/push-token 注册/更新推送 token +DELETE /me/devices/push-token 注销推送 token +GET /me/devices/push-tokens 查询已注册的设备列表 +``` + +请求体(注册): +```json +{ + "push_token": "CAHPxxx...", + "device_id": "device-uuid-xxx", + "platform": "harmonyos" +} +``` + +### 3.3 推送触发点 + +在 `notify/mail.go` 的 `Recipients()` 函数末尾增加推送调用: + +```go +// 现有 SSE 推送 +sse.Default.SendToRecipient(m.To.Name, "new_mail", payload(...)) + +// 新增:HMS Push 推送(异步,不阻塞 SSE) +go pushToHMS(m.To.Name, payload(...)) +``` + +`pushToHMS` 逻辑: +1. 查询该用户的所有 `push_tokens` +2. 组装 HMS Push 请求(title + body + data payload) +3. 调用 HMS Push API(带 access_token 刷新逻辑) +4. 失败静默(SSE 已保证在线设备收到,Push 是补充通道) + +### 3.4 access_token 管理 + +HMS Push API 需要 OAuth 2.0 access_token(有效期 ~3600s): + +```go +type HMSTokenManager struct { + appID string + appSecret string + token string + expiresAt time.Time +} + +func (m *HMSTokenManager) getToken() (string, error) { + if time.Now().Before(m.expiresAt) { + return m.token, nil + } + // POST https://oauth-login.cloud.huawei.com/oauth2/v3/token + // grant_type=client_credentials&client_id=appID&client_secret=appSecret + // ... + return newToken, nil +} +``` + +### 3.5 推送内容模板 + +``` +标题:新邮件 +正文:{from_name}:{subject} +数据:{ mail_id, session_id, from_name, subject, action: "open_mail" } +``` + +客户端收到通知后,打开应用并路由到对应邮件详情页。 + +--- + +## 四、配置项 + +```bash +# HMS Push 配置(环境变量) +HMS_APP_ID=10xxxxxx # 华为开发者 App ID +HMS_APP_SECRET=xxxxxx # App Secret +HMS_ENABLED=true # 开关(false 时只走 SSE 不推 HMS) +``` + +`HMS_ENABLED=false` 时 `pushToHMS` 直接 return,零开销。 + +--- + +## 五、鸿蒙客户端侧需要做的事 + +1. **集成 HMS Push SDK**:`@hms.push` 包 +2. **注册 token**:App 启动时调用 HMS Push 获取 token → `POST /me/devices/push-token` +3. **接收通知**:收到通知后解析 `data` 字段 → 路由到对应邮件 +4. **点击通知**:`action: "open_mail"` → 跳转到 `mail_id` 对应的邮件详情 + +--- + +## 六、时机与限制 + +| 场景 | 行为 | +|---|---| +| 客户端在线(SSE 连接) | SSE 已推送,**Push 不发**(避免重复通知) | +| 客户端后台/SSE 断开 | Push 发送,唤醒设备 | +| 用户已禁用通知 | 服务端不知情,Push 发送但被系统拦截(正常行为) | +| 同一用户多设备 | 所有注册的 token 都推送 | +| 推送频率限制 | HMS Push 免费版有 QPS 限制(按 App 而定),大量并发时需队列化 | + +**SSE 与 Push 的切换判断**:最简实现是**无条件推送**(SSE 在线时 Push 也会发,但客户端收到 SSE 后可以忽略 Push)。如果要精确判断,客户端在线时上报一个 `push_active` 标记,服务端据此跳过。但最简方案更可靠——依赖客户端状态判断有延迟风险。 + +--- + +## 七、鸿蒙端集成要点(供 dsh 参考) + +```typescript +// 鸿蒙端伪代码 +import push from '@hms.push'; + +// App 启动时 +async function registerPushToken() { + const token = await push.getToken({ scope: 'message' }); + if (token) { + await fetch(`${gateway}/me/devices/push-token`, { + method: 'POST', + headers: { Authorization: `Bearer ${userKey}` }, + body: JSON.stringify({ push_token: token, device_id: deviceId }) + }); + } +} + +// 收到推送 +push.onMessage((data) => { + if (data.action === 'open_mail') { + navigateToMail(data.mail_id); + } +}); +``` + +--- + +## 八、实施阶段 + +| 阶段 | 内容 | 估时 | +|---|---|---| +| **P1** | `push_tokens` 表 + API 端点 + HMS access_token 管理 | 1 天 | +| **P2** | `notify/mail.go` 接入 + 推送触发逻辑 | 0.5 天 | +| **P3** | 鸿蒙客户端集成(dsh 侧) | 由 dsh 实施 | +| **P4** | 测试 + 边界(token 刷新失败、推送频率、多设备) | 0.5 天 | + +总计 Gateway 侧 **1.5 天**,可与多账号方案并行推进。