docs: HMS-PUSH-PLAN.md 华为统一推送服务端集成方案

This commit is contained in:
2026-09-07 17:00:06 +08:00
parent 7278fcdf9a
commit 83d3c2a51a

226
docs/HMS-PUSH-PLAN.md Normal file
View File

@ -0,0 +1,226 @@
# 华为统一推送HMS Push服务端集成方案
> piAPI 维护方)编写 | 2026-09-07
---
## 一、背景
当前 Gateway 通过 SSEServer-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 天**,可与多账号方案并行推进。