diff --git a/docs/IM-前后端对接文档.md b/docs/IM-前后端对接文档.md index b1d60b9..a9152e9 100644 --- a/docs/IM-前后端对接文档.md +++ b/docs/IM-前后端对接文档.md @@ -467,7 +467,7 @@ X-MeetingIM-APIKey: <预置的普通 API Key> 客户端侧要求: - App 集成 JPush SDK,初始化使用极光 `AppKey`,不接触 `MasterSecret`。 -- 登录 IM 后将极光 alias 绑定为当前 `user_id`,例如 `usr...`;同一用户多设备允许绑定同一个 alias。 +- 登录 IM 后将极光 alias 绑定为当前 `user_id` 的安全别名:`user_id.replace("-", ".").replace("_", "|")`,例如 `usrd6Ag5opa-ww` 绑定为 `usrd6Ag5opa.ww`;同一用户多设备允许绑定同一个 alias。 - SDK 返回 `registration_id` 后调用 `PUT /v1/im/devices/{device_id}/push-token` 上报,`provider` 固定传 `jpush`。 - 点击 Push 后按 `extras.type` 跳转,再按服务端数据补偿,不能只凭 Push payload 渲染最终状态。 diff --git a/docs/third/IM-极光推送前后端对接文档.md b/docs/third/IM-极光推送前后端对接文档.md index 693df9d..5f33e45 100644 --- a/docs/third/IM-极光推送前后端对接文档.md +++ b/docs/third/IM-极光推送前后端对接文档.md @@ -24,7 +24,15 @@ - iOS:配置 APNs 证书或 Token Authentication,申请通知权限。 - HarmonyOS:按鸿蒙 SDK 文档初始化并配置通知分类。 -客户端启动后取得 `registration_id`,登录 IM 后把 alias 绑定为当前 `user_id`。同一用户多设备允许绑定同一个 alias。 +客户端启动后取得 `registration_id`,登录 IM 后把 alias 绑定为当前 `user_id` 的极光安全别名。同一用户多设备允许绑定同一个 alias。 + +极光 alias 不支持中横线 `-` 和下划线 `_`,而 IM `user_id` 可能包含这两个字符。客户端和后端统一使用以下映射: + +```text +jpush_alias = user_id.replace("-", ".").replace("_", "|") +``` + +示例:`usrd6Ag5opa-ww` 对应极光 alias `usrd6Ag5opa.ww`。 ### 2.2 上报 Push Token @@ -306,7 +314,7 @@ Content-Type: application/json | 步骤 | 操作 | 验证 | | --- | --- | --- | | 1 | 客户端初始化 JPush SDK | 能拿到 `registration_id`。 | -| 2 | 登录 IM 并绑定 alias=`user_id` | 极光控制台能查到别名或设备。 | +| 2 | 登录 IM 并绑定 alias=`user_id.replace("-", ".").replace("_", "|")` | 极光控制台能查到别名或设备。 | | 3 | 调用设备上报接口 | 服务端设备记录 provider=`jpush`。 | | 4 | 目标用户退出或断网 | IM 判断为离线或不可 WS 投递。 | | 5 | 发送 P2P 消息 | 离线用户收到 Push。 | diff --git a/docs/third/IM-极光推送完整调用流程.md b/docs/third/IM-极光推送完整调用流程.md index 7501147..8ca3eec 100644 --- a/docs/third/IM-极光推送完整调用流程.md +++ b/docs/third/IM-极光推送完整调用流程.md @@ -42,7 +42,7 @@ | `jpush_apns_production` | `false/true` | iOS APNs 环境,测试包用 false,生产包用 true。 | | `jpush_default_ttl_seconds` | `86400` | 离线 Push 默认保留时长。 | | `jpush_call_ttl_seconds` | `60` | 来电 Push 保留时长,建议短一些。 | -| `jpush_use_alias` | `true` | 是否优先使用 alias=user_id 投递。 | +| `jpush_use_alias` | `true` | 是否优先使用极光安全 alias 投递。 | | `jpush_test_model` | `false` | 极光测试模式,仅联调测试设备时开启。 | | `jpush_classification` | `1` | 重要业务通知建议按系统消息分类处理,需结合厂商通道规则确认。 | | `jpush_log_payload` | `false` | 生产关闭 payload 明文日志,避免敏感信息进入日志。 | @@ -59,7 +59,7 @@ 2. 初始化 SDK 使用 `jpush_app_key`。 3. 申请系统通知权限。 4. 取得 `registration_id`。 -5. 登录 IM 后绑定 alias,建议 alias 使用当前 IM `user_id`,例如 `usr...`。 +5. 登录 IM 后绑定 alias。极光 alias 不支持中横线 `-` 和下划线 `_`,客户端必须将当前 IM `user_id` 转成极光安全别名:`user_id.replace("-", ".").replace("_", "|")`。示例:`usrd6Ag5opa-ww` 对应 `usrd6Ag5opa.ww`。 6. 调用 IM 后端设备接口上报 Push Token。 ### 4.2 设备上报 @@ -357,7 +357,7 @@ Push 中的 `topic`、`seq`、`call_id` 只用于定位和补偿,不作为业 | 验收项 | 期望结果 | | --- | --- | -| 客户端初始化 | SDK 返回 registration_id,alias 绑定为当前 `usr...`。 | +| 客户端初始化 | SDK 返回 registration_id,alias 绑定为 `user_id.replace("-", ".").replace("_", "|")`。 | | 设备上报 | `PUT /v1/im/devices/{device_id}/push-token` 返回成功,服务端记录 provider=`jpush`。 | | 普通离线消息 | 用户离线后收到 Push,点击后进入会话并补齐 Topic seq。 | | 群 @ 提醒 | 被 @ 用户离线时只收到一次提醒,重连后定位到对应消息。 | diff --git a/server/push/jpush/push_jpush.go b/server/push/jpush/push_jpush.go index 3dd83d6..d7929ca 100644 --- a/server/push/jpush/push_jpush.go +++ b/server/push/jpush/push_jpush.go @@ -360,7 +360,7 @@ func preparePayloads(rcpt *push.Receipt, config *configType) ([]*jpushPayload, [ } var audience map[string][]string if useAlias { - audience = map[string][]string{"alias": {uid.UserId()}} + audience = map[string][]string{"alias": {aliasForUid(uid)}} } else { var registrationIDs []string for _, device := range devices[uid] { @@ -584,6 +584,12 @@ func containsString(values []string, target string) bool { return false } +func aliasForUid(uid t.Uid) string { + // JPush alias does not accept '-' or '_'. Tinode user IDs are base64url-like, + // so map them to legal characters which never appear in the original alphabet. + return strings.NewReplacer("-", ".", "_", "|").Replace(uid.UserId()) +} + func providerDevicesForUsers(tenantID t.TenantID, provider string, uids ...t.Uid) (map[t.Uid][]t.DeviceDef, int, error) { if tokens, ok := store.DevicePushTokens.(store.DevicePushTokenProviderQueryInterface); ok { return tokens.GetAllByProvider(tenantID, provider, uids...) diff --git a/server/push/jpush/push_jpush_test.go b/server/push/jpush/push_jpush_test.go index 799e544..9f0e949 100644 --- a/server/push/jpush/push_jpush_test.go +++ b/server/push/jpush/push_jpush_test.go @@ -97,7 +97,7 @@ func TestPreparePayloadsUsesAliasAndNormalizesP2PTopic(t *testing.T) { t.Fatalf("unexpected payload count: %d %#v", len(payloads), uids) } payload := payloads[0] - if got := payload.Audience["alias"][0]; got != callee.UserId() { + if got := payload.Audience["alias"][0]; got != aliasForUid(callee) { t.Fatalf("unexpected alias: %s", got) } userTopic, _ := types.P2PNameForUser(callee, topic) @@ -109,6 +109,16 @@ func TestPreparePayloadsUsesAliasAndNormalizesP2PTopic(t *testing.T) { } } +func TestAliasForUidUsesJPushSafeCharacters(t *testing.T) { + uid := types.ParseUserId("usrd6Ag5opa-ww") + if uid.IsZero() { + t.Fatal("test user id did not parse") + } + if got, want := aliasForUid(uid), "usrd6Ag5opa.ww"; got != want { + t.Fatalf("unexpected alias: got %s want %s", got, want) + } +} + func TestPreparePayloadsCanUseRegistrationID(t *testing.T) { oldTokens := store.DevicePushTokens uid := types.Uid(42)