# XuqmGroup SDK 设计规范 > 版本:v2.0 > 生效范围:**全平台** — Android、iOS、React Native、Flutter、HarmonyOS、微信小程序、Vue3、Java / Python / Go 服务端 SDK > 状态:需求确认,待各平台落地实现 --- ## 一、设计原则 1. **App 零感知初始化**:配置文件方式下,App 无需写任何初始化代码。 2. **一次认证,全局生效**:`setUserInfo()` 一次调用,所有子 SDK(Push、IM、License 等)自动同步状态。 3. **失败即失败**:初始化失败、认证失败均抛出错误,不静默降级。 4. **服务按需激活**:哪些服务(IM、推送、License 等)由租户平台配置决定,SDK 根据返回配置自动激活对应能力,App 层无需判断。 5. **子 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 主动调用,适用于无法提前写入配置文件或需要动态切换环境的场景。 ```ts // 使用默认公有平台(www.51szyx.com) await XuqmSDK.initialize({ appKey: 'your-app-key', debug: __DEV__ }) // 使用私有化部署平台 await XuqmSDK.initialize({ appKey: 'your-app-key', platformUrl: 'https://your-server.com' }) ``` **签名**: ```ts interface XuqmInitOptions { appKey: string platformUrl?: string // 平台地址,可选;不传则使用内置默认公有平台地址 debug?: boolean } XuqmSDK.initialize(options: XuqmInitOptions): Promise ``` **行为**: 1. `platformUrl` 未传时使用内置默认公有平台地址(`DEFAULT_TENANT_PLATFORM_URL`) 2. 请求 `{platformUrl}/api/sdk/config?appKey={appKey}` 3. 平台根据 appKey 和租户服务开通情况,返回该 App 专属的服务配置: ```json { "apiUrl": "...", // 通用 API 地址 "imWsUrl": "...", // IM WebSocket 地址(未开通则为 null) "fileServiceUrl": "...", // 文件服务地址 "pushEnabled": true, // 是否开通推送服务 "licenseEnabled": false, // 是否开通 License 服务 "imEnabled": true // 是否开通 IM 服务 } ``` 4. SDK 将服务配置保存,供所有子 SDK 使用。 5. **失败时直接抛出错误**,不降级。调用方必须处理异常。 --- #### 已移除(不再提供) | 方法 | 移除原因 | |------|---------| | `XuqmSDK.initializeFromLicense(file, options?)` | License 是独立小服务,不承担 SDK 初始化职责 | | `XuqmSDK.init(options): void` | 同步 init 不拉取服务配置,行为不完整;用方式 A 或方式 B 替代 | | `XuqmSDK.initWithConfigFile(encrypted)` | 由自动初始化机制内部调用,不对外暴露 | --- ### 2.2 用户认证:setUserInfo(核心枢纽) `setUserInfo` 是所有子 SDK 用户状态同步的入口。**登录成功后调用一次,所有子 SDK 自动激活**。 ```ts 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,用于租户平台配置的灰度/定向更新 | **登出时**: ```ts XuqmSDK.setUserInfo(null) ``` 触发所有子 SDK 登出:PushSDK 解绑 token,ImSDK 断开连接,清除所有用户上下文。 --- ### 2.3 其他方法(保留) ```ts XuqmSDK.getUserId(): string | null XuqmSDK.getUserInfo(): XuqmUserInfo | null XuqmSDK.awaitInitialization(): Promise // 等待初始化完成 ``` --- ## 三、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 ```ts // 设置离线推送开关 PushSDK.setOfflinePushEnabled(enabled: boolean): Promise // 设置免打扰时间段(24小时制) PushSDK.setQuietHours(start: string, end: string): Promise // 示例:PushSDK.setQuietHours('22:00', '08:00') // 清除免打扰设置 PushSDK.clearQuietHours(): Promise // 登出(通常不需要手动调用,setUserInfo(null) 会自动触发) PushSDK.logout(): Promise ``` ### 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 内部实现 ```ts // 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 过期或需要主动刷新时: ```ts const newSig = await api.refreshUserSig() ImSDK.refreshToken(newSig): Promise ``` 或通过重新调用 `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 插件注册 #### 批量注册(推荐) ```ts UpdateSDK.registerPlugins([ { moduleId: 'buz1' }, { moduleId: 'buz2' }, { moduleId: 'buz3' }, ]) ``` **版本号由 SDK 自动获取**,不需要 App 传入: - SDK 从本地 manifest 文件读取当前已安装的插件版本 - 如果 manifest 中无记录(首次安装),视为版本 `0.0.0` #### 单个注册(仍支持,向后兼容) ```ts UpdateSDK.registerPlugin({ moduleId: 'buz1' }) // 注意:不再接受 version 字段 ``` **类型定义变更**: ```ts // 旧 interface PluginMeta { moduleId: string version: string // ← 移除 } // 新 interface PluginRegistration { moduleId: string } ``` ### 5.2 App 整包更新 #### 检查更新 ```ts const info = await UpdateSDK.checkAppUpdate(bypassIgnore?: boolean): Promise ``` #### Android APK 直接下载安装(新增) ```ts // 下载 APK 并在完成后调起系统安装器 await UpdateSDK.downloadAndInstallApk( downloadUrl: string, options?: { onProgress?: (progress: number) => void // 0~1 sha256?: string // 校验值,有则验证 } ): Promise ``` **完整整包更新流程示例**: ```ts 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:检查更新信息 ```ts const info = await UpdateSDK.checkPluginUpdate(moduleId: string): Promise ``` App 层拿到 `info` 后可自行决定:弹窗提示用户、静默后台更新或忽略。 ```ts interface PluginUpdateInfo { needsUpdate: boolean latestVersion: string currentVersion: string // 新增:SDK 自动填入 changeLog?: string forceUpdate?: boolean // 新增:强制更新标识 minCommonVersion?: string // 要求的最低 common bundle 版本 } ``` #### 方法 2:执行更新 ```ts await UpdateSDK.updatePlugin( moduleId: string, options?: { onProgress?: (progress: number) => void silent?: boolean // true = 静默更新(不重载,下次启动生效) } ): Promise ``` **SDK 内部自动完成**: 1. 调用 `checkPluginUpdate()` 确认有更新 2. 下载 bundle(带进度回调) 3. 写入文件系统(`rn-bundles/..bundle`) 4. 若 `silent = false`(默认),触发宿主重载当前插件 **一步完成(静默后台更新)**: ```ts // 启动时后台静默检查并缓存,下次进入插件生效 await UpdateSDK.updatePlugin('buz1', { silent: true }) ``` **带确认弹窗的前台更新**: ```ts 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 就绪后直接使用其配置。 ```ts // 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` 共享配置和用户上下文。 --- ## 九、初始化完整流程参考 ```ts // 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 概念,需要单独定义 |