XuqmGroup-RNSDK/docs/SDK设计规范.md
XuqmGroup 8fb59bb5f2 fix(common): platformUrl 可选,不传使用默认公有平台
XuqmInitOptions.platformUrl 改为可选字段。
initialize() 内部:platformUrl ?? DEFAULT_TENANT_PLATFORM_URL。
公有云用户无需传 platformUrl,私有化部署传自有平台地址。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 10:37:57 +08:00

17 KiB

XuqmGroup SDK 设计规范

版本v2.0
生效范围:全平台 — Android、iOS、React Native、Flutter、HarmonyOS、微信小程序、Vue3、Java / Python / Go 服务端 SDK
状态:需求确认,待各平台落地实现


一、设计原则

  1. App 零感知初始化配置文件方式下,App 无需写任何初始化代码。
  2. 一次认证,全局生效setUserInfo() 一次调用,所有子 SDKPush、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 无需调用任何初始化代码。

配置文件路径RNsrc/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>

行为

  1. platformUrl 未传时使用内置默认公有平台地址(DEFAULT_TENANT_PLATFORM_URL
  2. 请求 {platformUrl}/api/sdk/config?appKey={appKey}
  3. 平台根据 appKey 和租户服务开通情况,返回该 App 专属的服务配置:
    {
      "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 自动激活

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 内部自动完成

  1. 调用 checkPluginUpdate() 确认有更新
  2. 下载 bundle带进度回调
  3. 写入文件系统(rn-bundles/<moduleId>.<platform>.bundle
  4. 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


六、LicenseSDKLicense 服务 — @xuqm/rn-license

6.1 定位

License 是独立小服务,大多数租户不会开通。它:

  • 不承担 SDK 初始化职责
  • 不向其他 SDK 提供基础能力
  • 与 IM / Push / Update 等没有依赖关系
  • 使用 XuqmSDKapiUrlappKeyuserId 等公共上下文

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
Vue3H5 @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
服务端 SDKJava/Go/Python的 setUserInfo 等效方法 待设计 服务端无设备/Push 概念,需要单独定义