Files
MailUI4Agents/docs/HMS-PUSH-PLAN.md

227 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 华为统一推送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 天**,可与多账号方案并行推进。