XuqmInitOptions.platformUrl 改为可选字段。 initialize() 内部:platformUrl ?? DEFAULT_TENANT_PLATFORM_URL。 公有云用户无需传 platformUrl,私有化部署传自有平台地址。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
17 KiB
XuqmGroup SDK 设计规范
版本:v2.0
生效范围:全平台 — Android、iOS、React Native、Flutter、HarmonyOS、微信小程序、Vue3、Java / Python / Go 服务端 SDK
状态:需求确认,待各平台落地实现
一、设计原则
- App 零感知初始化:配置文件方式下,App 无需写任何初始化代码。
- 一次认证,全局生效:
setUserInfo()一次调用,所有子 SDK(Push、IM、License 等)自动同步状态。 - 失败即失败:初始化失败、认证失败均抛出错误,不静默降级。
- 服务按需激活:哪些服务(IM、推送、License 等)由租户平台配置决定,SDK 根据返回配置自动激活对应能力,App 层无需判断。
- 子 SDK 独立性:各子 SDK 之间通过
common共享上下文(appKey、baseUrl、userId 等),不互相硬依赖。
二、XuqmSDK(核心 — @xuqm/rn-common)
2.1 初始化方式(仅两种)
方式 A:配置文件自动初始化(推荐)
App 在构建阶段将加密配置文件(.xuqmconfig)放置到指定路径,SDK 在模块加载时自动读取并初始化。App 无需调用任何初始化代码。
配置文件路径(RN):src/assets/xuqm/config.xuqmconfig
配置文件格式:见 docs/配置文件规范.md
配置文件内容(加密前)包含:
appKey:应用标识platformUrl:平台地址(从哪里拉取服务配置)
SDK 读取配置文件后,自动调用 方式 B 完成完整初始化(拉取远程服务配置)。
方式 B:手动初始化
App 主动调用,适用于无法提前写入配置文件或需要动态切换环境的场景。
// 使用默认公有平台(www.51szyx.com)
await XuqmSDK.initialize({ appKey: 'your-app-key', debug: __DEV__ })
// 使用私有化部署平台
await XuqmSDK.initialize({ appKey: 'your-app-key', platformUrl: 'https://your-server.com' })
签名:
interface XuqmInitOptions {
appKey: string
platformUrl?: string // 平台地址,可选;不传则使用内置默认公有平台地址
debug?: boolean
}
XuqmSDK.initialize(options: XuqmInitOptions): Promise<void>
行为:
platformUrl未传时使用内置默认公有平台地址(DEFAULT_TENANT_PLATFORM_URL)- 请求
{platformUrl}/api/sdk/config?appKey={appKey} - 平台根据 appKey 和租户服务开通情况,返回该 App 专属的服务配置:
{ "apiUrl": "...", // 通用 API 地址 "imWsUrl": "...", // IM WebSocket 地址(未开通则为 null) "fileServiceUrl": "...", // 文件服务地址 "pushEnabled": true, // 是否开通推送服务 "licenseEnabled": false, // 是否开通 License 服务 "imEnabled": true // 是否开通 IM 服务 } - SDK 将服务配置保存,供所有子 SDK 使用。
- 失败时直接抛出错误,不降级。调用方必须处理异常。
已移除(不再提供)
| 方法 | 移除原因 |
|---|---|
XuqmSDK.initializeFromLicense(file, options?) |
License 是独立小服务,不承担 SDK 初始化职责 |
XuqmSDK.init(options): void |
同步 init 不拉取服务配置,行为不完整;用方式 A 或方式 B 替代 |
XuqmSDK.initWithConfigFile(encrypted) |
由自动初始化机制内部调用,不对外暴露 |
2.2 用户认证:setUserInfo(核心枢纽)
setUserInfo 是所有子 SDK 用户状态同步的入口。登录成功后调用一次,所有子 SDK 自动激活。
interface XuqmUserInfo {
userId: string // 必填
userSig?: string // IM 服务必填;租户未开通 IM 时可不传
name?: string
phone?: string
avatar?: string
}
XuqmSDK.setUserInfo(info: XuqmUserInfo): void
调用后的内部行为(自动、静默):
| 子 SDK | 触发动作 |
|---|---|
| PushSDK | 自动执行设备注册流程(见第三节) |
| ImSDK | 若 userSig 存在且平台开通了 IM,自动登录 IM 服务 |
| LicenseSDK | 更新用户上下文,供 License 验证使用 |
| UpdateSDK | 更新 userId,用于租户平台配置的灰度/定向更新 |
登出时:
XuqmSDK.setUserInfo(null)
触发所有子 SDK 登出:PushSDK 解绑 token,ImSDK 断开连接,清除所有用户上下文。
2.3 其他方法(保留)
XuqmSDK.getUserId(): string | null
XuqmSDK.getUserInfo(): XuqmUserInfo | null
XuqmSDK.awaitInitialization(): Promise<void> // 等待初始化完成
三、PushSDK(推送 — @xuqm/rn-push)
3.1 初始化方式
无需显式初始化。当 XuqmSDK.setUserInfo(info) 被调用时,PushSDK 自动执行以下流程:
setUserInfo(info)
└─ PushSDK 内部触发:
1. detectPushVendor() — 自动检测当前设备厂商(华为/小米/OPPO/vivo/荣耀/FCM/APNs)
2. fetchVendorConfig(vendor) — 从平台获取该厂商推送配置(AppID、Secret 等)
3. registerWithVendorSDK() — 调用厂商 SDK 完成设备注册
4. onTokenReceived(token) — 收到 token 后自动上报绑定到平台
App 层无需关心设备厂商类型,也无需手动调用注册流程。
3.2 App 层可调用的 API
// 设置离线推送开关
PushSDK.setOfflinePushEnabled(enabled: boolean): Promise<void>
// 设置免打扰时间段(24小时制)
PushSDK.setQuietHours(start: string, end: string): Promise<void>
// 示例:PushSDK.setQuietHours('22:00', '08:00')
// 清除免打扰设置
PushSDK.clearQuietHours(): Promise<void>
// 登出(通常不需要手动调用,setUserInfo(null) 会自动触发)
PushSDK.logout(): Promise<void>
3.3 已移除
| 方法 | 移除原因 |
|---|---|
PushSDK.initialize(userId?) |
由 setUserInfo 触发,不对外暴露 |
PushSDK.requestNativeRegistration() |
内部流程,不对外暴露 |
PushSDK.registerToken(userId, token, vendor?) |
内部流程,不对外暴露 |
PushSDK.onPushToken(callback) |
内部流程,不对外暴露 |
PushSDK.unregisterToken(userId) |
由 setUserInfo(null) 触发 |
PushSDK.setPendingToken / getPendingToken |
内部状态,不对外暴露 |
四、ImSDK(即时通讯 — @xuqm/rn-im)
4.1 初始化与登录
无需显式初始化。依赖 XuqmSDK.initialize() 完成服务配置获取后即可工作。
登录:通过 XuqmSDK.setUserInfo({ userId, userSig }) 自动触发,不需要 App 直接调用 ImSDK.login()。
userSig 获取方式:
- App 登录成功后,由 App 服务端根据业务逻辑生成并返回
- userSig 通常有时效性(建议 7 天),App 应在登录响应中一并返回
- App 在调用
setUserInfo时传入,无需关心 IM 内部实现
// App 登录后的标准流程
const loginResult = await api.login(phone, code)
XuqmSDK.setUserInfo({
userId: loginResult.userId,
userSig: loginResult.userSig, // 服务端随登录一并返回
name: loginResult.name,
phone: loginResult.phone,
})
// → PushSDK 自动注册
// → ImSDK 自动登录(若租户开通了 IM)
4.2 userSig 刷新
当 userSig 过期或需要主动刷新时:
const newSig = await api.refreshUserSig()
ImSDK.refreshToken(newSig): Promise<void>
或通过重新调用 setUserInfo 携带新 userSig。
4.3 IM 与 Push 共用用户上下文
Push 和 IM 共享 XuqmSDK 的用户上下文(userId),两者通过 setUserInfo 统一管理:
- 同一个 userId 用于 Push 设备绑定和 IM 登录
setUserInfo(null)统一触发 Push 解绑和 IM 断连
4.4 可用方法
ImSDK 的具体 API 方法(消息收发、群组、好友等)保持现有设计不变,见 docs/SDK-API参考.md 第 5 节。
仅以下方法调整:
| 方法 | 变化 |
|---|---|
ImSDK.login(userId, userSig) |
保留,但通常不需要 App 直接调用(由 setUserInfo 触发) |
ImSDK.refreshToken(userSig) |
新增,用于 userSig 过期刷新 |
ImSDK.disconnect() |
保留,通常不需要直接调用(setUserInfo(null) 触发) |
五、UpdateSDK(更新 — @xuqm/rn-update)
5.1 插件注册
批量注册(推荐)
UpdateSDK.registerPlugins([
{ moduleId: 'buz1' },
{ moduleId: 'buz2' },
{ moduleId: 'buz3' },
])
版本号由 SDK 自动获取,不需要 App 传入:
- SDK 从本地 manifest 文件读取当前已安装的插件版本
- 如果 manifest 中无记录(首次安装),视为版本
0.0.0
单个注册(仍支持,向后兼容)
UpdateSDK.registerPlugin({ moduleId: 'buz1' })
// 注意:不再接受 version 字段
类型定义变更:
// 旧
interface PluginMeta {
moduleId: string
version: string // ← 移除
}
// 新
interface PluginRegistration {
moduleId: string
}
5.2 App 整包更新
检查更新
const info = await UpdateSDK.checkAppUpdate(bypassIgnore?: boolean): Promise<AppUpdateInfo>
Android APK 直接下载安装(新增)
// 下载 APK 并在完成后调起系统安装器
await UpdateSDK.downloadAndInstallApk(
downloadUrl: string,
options?: {
onProgress?: (progress: number) => void // 0~1
sha256?: string // 校验值,有则验证
}
): Promise<void>
完整整包更新流程示例:
const info = await UpdateSDK.checkAppUpdate()
if (!info.needsUpdate) return
if (Platform.OS === 'android' && info.downloadUrl) {
// Android:直接下载安装
await UpdateSDK.downloadAndInstallApk(info.downloadUrl, {
onProgress: (p) => setProgress(p),
sha256: info.apkHash ?? undefined,
})
} else {
// iOS / Android 商店跳转
await UpdateSDK.openStore(info.appStoreUrl, info.marketUrl)
}
5.3 插件(Bundle)热更新
两个方法,SDK 自动完成所有步骤:
方法 1:检查更新信息
const info = await UpdateSDK.checkPluginUpdate(moduleId: string): Promise<PluginUpdateInfo>
App 层拿到 info 后可自行决定:弹窗提示用户、静默后台更新或忽略。
interface PluginUpdateInfo {
needsUpdate: boolean
latestVersion: string
currentVersion: string // 新增:SDK 自动填入
changeLog?: string
forceUpdate?: boolean // 新增:强制更新标识
minCommonVersion?: string // 要求的最低 common bundle 版本
}
方法 2:执行更新
await UpdateSDK.updatePlugin(
moduleId: string,
options?: {
onProgress?: (progress: number) => void
silent?: boolean // true = 静默更新(不重载,下次启动生效)
}
): Promise<void>
SDK 内部自动完成:
- 调用
checkPluginUpdate()确认有更新 - 下载 bundle(带进度回调)
- 写入文件系统(
rn-bundles/<moduleId>.<platform>.bundle) - 若
silent = false(默认),触发宿主重载当前插件
一步完成(静默后台更新):
// 启动时后台静默检查并缓存,下次进入插件生效
await UpdateSDK.updatePlugin('buz1', { silent: true })
带确认弹窗的前台更新:
const info = await UpdateSDK.checkPluginUpdate('buz1')
if (info.needsUpdate) {
const confirmed = info.forceUpdate || (await showConfirm(`发现新版本 ${info.latestVersion},是否更新?`))
if (confirmed) {
await UpdateSDK.updatePlugin('buz1', {
onProgress: (p) => setProgress(p),
})
}
}
5.4 已移除 / 废弃
| 方法 | 替代 |
|---|---|
UpdateSDK.checkAndCachePlugin(moduleId) |
UpdateSDK.updatePlugin(moduleId, { silent: true }) |
UpdateSDK.downloadPluginBundle(url) |
内部实现,不对外暴露 |
UpdateSDK.cachePluginBundle(...) |
内部实现,不对外暴露 |
UpdateSDK.getCachedPluginBundle(moduleId) |
内部实现,不对外暴露 |
PluginMeta.version(注册时传 version) |
SDK 自动从 manifest 读取 |
5.5 用户定向更新
灰度/定向更新的用户白名单在租户平台配置,SDK 无需感知。UpdateSDK 在检查更新时会自动携带当前 userId(来自 XuqmSDK.getUserId()),由服务端决定是否返回 needsUpdate: true。
六、LicenseSDK(License 服务 — @xuqm/rn-license)
6.1 定位
License 是独立小服务,大多数租户不会开通。它:
- 不承担 SDK 初始化职责
- 不向其他 SDK 提供基础能力
- 与 IM / Push / Update 等没有依赖关系
- 使用
XuqmSDK的apiUrl、appKey、userId等公共上下文
6.2 初始化方式
无需独立初始化。LicenseSDK 在调用任何方法前会内部调用 XuqmSDK.awaitInitialization(),等待核心 SDK 就绪后直接使用其配置。
// App 只需调用 XuqmSDK.initialize(),LicenseSDK 无需额外操作
await XuqmSDK.initialize({ appKey, platformUrl })
// ...用户登录...
XuqmSDK.setUserInfo({ userId, ... })
// 然后即可直接调用 LicenseSDK 方法
const license = await LicenseSDK.validateLicense(licenseId)
6.3 已移除
| 方法 | 移除原因 |
|---|---|
LicenseSDK.initialize(...) |
依赖 XuqmSDK,无需独立初始化 |
XuqmSDK.initializeFromLicense(file) |
License 文件不承担 SDK 初始化职责 |
七、SDK 整体架构图
App
├── XuqmSDK.initialize(appKey, platformUrl)
│ └─ 拉取平台配置 ──────────────────────────────┐
│ │ 返回各服务 URL 和开通状态
│ ← apiUrl / imWsUrl / fileServiceUrl ──────────┘
│
├── XuqmSDK.setUserInfo({ userId, userSig?, ... })
│ ├─ PushSDK ──── 自动检测厂商 → 获取厂商配置 → 注册设备 → 上报 token
│ ├─ ImSDK ─────── 自动登录(若 userSig 存在且 IM 已开通)
│ ├─ UpdateSDK ─── 更新 userId(用于定向更新)
│ └─ LicenseSDK ── 更新用户上下文
│
└── XuqmSDK.setUserInfo(null)
├─ PushSDK ──── 解绑 token
└─ ImSDK ─────── 断开连接
八、各子 SDK 依赖关系
@xuqm/rn-common ← XuqmSDK 核心、HTTP 客户端、设备信息、公共上下文
▲
│ 依赖(通过 awaitInitialization + getConfig + getUserId 等)
├── @xuqm/rn-push
├── @xuqm/rn-im
├── @xuqm/rn-update
├── @xuqm/rn-xwebview
└── @xuqm/rn-license
子 SDK 之间不互相依赖,均通过 @xuqm/rn-common 共享配置和用户上下文。
九、初始化完整流程参考
// 1. 初始化(通常在 App 入口,配置文件模式下可省略)
// 公有平台:不传 platformUrl
await XuqmSDK.initialize({ appKey: 'your-app-key', debug: __DEV__ })
// 私有化部署:传入自有平台地址
// await XuqmSDK.initialize({ appKey: 'your-app-key', platformUrl: 'https://your-server.com' })
// 2. 业务登录(App 自有登录逻辑)
const result = await api.login(phone, verifyCode)
// 3. 一次调用,全局生效
XuqmSDK.setUserInfo({
userId: result.userId,
userSig: result.userSig, // 服务端随登录返回
name: result.name,
phone: result.phone,
})
// → PushSDK 自动完成设备注册
// → ImSDK 自动完成 IM 登录(若开通)
// 4. 插件热更新(后台静默,启动时调用一次)
await UpdateSDK.registerPlugins([
{ moduleId: 'buz1' },
{ moduleId: 'buz2' },
])
await Promise.all([
UpdateSDK.updatePlugin('buz1', { silent: true }),
UpdateSDK.updatePlugin('buz2', { silent: true }),
])
// 5. 登出
XuqmSDK.setUserInfo(null)
// → PushSDK 解绑,ImSDK 断连,userId 清除
十、跨平台实现要求
本文档中所有设计均为跨平台通用需求,适用于:
| 平台 | SDK 包 |
|---|---|
| Android(原生) | xuqm-android-sdk |
| iOS(原生) | XuqmSDK.framework |
| React Native | @xuqm/rn-* |
| Flutter | xuqm_flutter_sdk |
| HarmonyOS | xuqm_harmony_sdk |
| 微信小程序 | xuqm-miniprogram-sdk |
| Vue3(H5) | @xuqm/vue-sdk |
| Java 服务端 | xuqm-java-sdk |
| Python 服务端 | xuqm-python-sdk |
| Go 服务端 | xuqm-go-sdk |
各平台在实现时应在 API 签名和行为上保持一致;平台差异(如服务端 SDK 无 Push/IM 客户端能力)通过能力矩阵文档说明。
十一、待讨论 / 待决策事项
| 事项 | 当前状态 | 说明 |
|---|---|---|
| userSig 过期自动刷新 | 待设计 | 是否由 SDK 内部定时刷新,还是 App 层感知过期后手动调用 refreshToken? |
| IM 未开通时 userSig 传入是否报警 | 待决策 | 建议在 debug 模式下打印警告 |
| PushSDK 厂商配置获取失败降级策略 | 待决策 | 获取厂商配置失败时是否还尝试使用上次缓存配置? |
| 配置文件格式版本向上兼容 | 进行中 | 见 docs/配置文件规范.md |
| 服务端 SDK(Java/Go/Python)的 setUserInfo 等效方法 | 待设计 | 服务端无设备/Push 概念,需要单独定义 |