diff --git a/docs-site/docs/.vitepress/config.ts b/docs-site/docs/.vitepress/config.ts index 3821e2d..a165558 100644 --- a/docs-site/docs/.vitepress/config.ts +++ b/docs-site/docs/.vitepress/config.ts @@ -54,13 +54,16 @@ export default defineConfig({ { text: 'IM 接入', link: '/ios/im' }, { text: '推送接入', link: '/ios/push' }, { text: '版本管理', link: '/ios/update' }, + { text: '授权管理', link: '/ios/license' }, ], '/rn/': [ { text: '概览', link: '/rn/' }, { text: '安装配置', link: '/rn/setup' }, { text: 'IM 接入', link: '/rn/im' }, { text: '群聊', link: '/rn/group' }, + { text: '推送接入', link: '/rn/push' }, { text: '版本管理', link: '/rn/update' }, + { text: '授权管理', link: '/rn/license' }, ], '/vue3/': [ { text: '概览', link: '/vue3/' }, @@ -71,6 +74,9 @@ export default defineConfig({ { text: '概览', link: '/flutter/' }, { text: '安装配置', link: '/flutter/setup' }, { text: 'IM 接入', link: '/flutter/im' }, + { text: '推送接入', link: '/flutter/push' }, + { text: '版本管理', link: '/flutter/update' }, + { text: '授权管理', link: '/flutter/license' }, ], '/harmony/': [ { text: '概览', link: '/harmony/' }, diff --git a/docs-site/docs/android/index.md b/docs-site/docs/android/index.md index db01dfb..360a99f 100644 --- a/docs-site/docs/android/index.md +++ b/docs-site/docs/android/index.md @@ -1,6 +1,6 @@ # Android SDK 接入指南 -**版本**:0.5.x(UserSig 鉴权 · 简化登录)· **最低 Android 版本**:API 24 (Android 7.0) · **语言**:Kotlin +**版本**:0.4.x · **最低 Android 版本**:API 24 (Android 7.0) · **语言**:Kotlin ## 功能模块 @@ -10,6 +10,7 @@ | sdk-im | `com.xuqm:sdk-im` | 单聊、群聊、消息收发、会话、好友、群组 | | sdk-push | `com.xuqm:sdk-push` | 自动检测厂商、设备 Token 注册(华为/小米/OPPO/vivo/荣耀/FCM) | | sdk-update | `com.xuqm:sdk-update` | App 更新检查、下载安装 | +| sdk-license | `com.xuqm:sdk-license` | 设备授权注册与验证 | ## 快速接入 @@ -28,10 +29,11 @@ dependencyResolutionManagement { ```kotlin // app/build.gradle.kts dependencies { - implementation("com.xuqm:sdk-core:0.4.2") - implementation("com.xuqm:sdk-im:0.4.2") - implementation("com.xuqm:sdk-push:0.4.2") // 按需 - implementation("com.xuqm:sdk-update:0.4.2") // 按需 + implementation("com.xuqm:sdk-core:0.4.10") + implementation("com.xuqm:sdk-im:0.4.10") + implementation("com.xuqm:sdk-push:0.4.10") // 按需 + implementation("com.xuqm:sdk-update:0.4.10") // 按需 + implementation("com.xuqm:sdk-license:0.4.10") // 按需 } ``` @@ -188,3 +190,13 @@ lifecycleScope.launch { } } ``` + +--- + +## 下一步 + +- [Android 安装配置 →](./setup) +- [Android IM 接入 →](./im) +- [Android 推送接入 →](./push) +- [Android 版本更新 →](./update) +- [Android 授权管理 →](./license) diff --git a/docs-site/docs/android/setup.md b/docs-site/docs/android/setup.md index 8629bc5..75dd6dc 100644 --- a/docs-site/docs/android/setup.md +++ b/docs-site/docs/android/setup.md @@ -1,6 +1,6 @@ # Android 安装配置 -**版本**:0.5.x · **最低 Android 版本**:API 24 (Android 7.0) · **语言**:Kotlin +**版本**:0.4.x · **最低 Android 版本**:API 24 (Android 7.0) · **语言**:Kotlin --- @@ -34,11 +34,11 @@ dependencyResolutionManagement { ```kotlin dependencies { - implementation("com.xuqm:sdk-core:0.4.2") - implementation("com.xuqm:sdk-im:0.4.2") - implementation("com.xuqm:sdk-push:0.4.2") // 按需 - implementation("com.xuqm:sdk-update:0.4.2") // 按需 - implementation("com.xuqm:sdk-license:0.4.2") // 按需 + implementation("com.xuqm:sdk-core:0.4.10") + implementation("com.xuqm:sdk-im:0.4.10") + implementation("com.xuqm:sdk-push:0.4.10") // 按需 + implementation("com.xuqm:sdk-update:0.4.10") // 按需 + implementation("com.xuqm:sdk-license:0.4.10") // 按需 } ``` diff --git a/docs-site/docs/flutter/index.md b/docs-site/docs/flutter/index.md index 3145c5e..fea2847 100644 --- a/docs-site/docs/flutter/index.md +++ b/docs-site/docs/flutter/index.md @@ -14,6 +14,7 @@ | `xuqm_flutter_im` | `packages/im` | 单聊、群聊、消息收发、会话、好友、群组 | | `xuqm_flutter_push` | `packages/push` | 设备 Token 注册、厂商检测(Android)/ APNs(iOS)| | `xuqm_flutter_update` | `packages/update` | App 版本检查、商店跳转、APK 下载(Android)| +| `xuqm_flutter_license` | `packages/license` | 设备授权注册与验证(License SDK)| --- @@ -26,7 +27,7 @@ dependencies: xuqm_flutter_sdk: git: url: https://xuqinmin.com/xuqinmin12/XuqmGroup-FlutterSDK.git - ref: v0.2.0 + ref: v0.2.2 ``` > Gitea Package Registry 暂不支持 Dart/Flutter 包格式,因此通过 Git Tag 方式发布。 @@ -48,11 +49,6 @@ await XuqmSDK.initialize(XuqmInitOptions( 初始化时会自动向服务端请求远程配置(IM API 地址等),若网络异常则回退到内置默认值。 - -```dart -XuqmSDK.init(XuqmInitOptions(appKey: 'your_app_key')); -``` - --- ### 2. IM 登录 diff --git a/docs-site/docs/flutter/license.md b/docs-site/docs/flutter/license.md new file mode 100644 index 0000000..94eebd7 --- /dev/null +++ b/docs-site/docs/flutter/license.md @@ -0,0 +1,205 @@ +# Flutter 授权管理(License SDK) + +**模块**:`xuqm_flutter_license` · **依赖**:`cryptography`、`device_info_plus`、`shared_preferences` + +--- + +## 1. 安装 + +在 `pubspec.yaml` 中添加: + +```yaml +dependencies: + xuqm_flutter_license: + git: + url: https://xuqinmin.com/xuqinmin12/XuqmGroup-FlutterSDK.git + ref: v0.2.2 + path: packages/license +``` + +--- + +## 2. 放置 License 文件 + +从租户平台下载 `.xuqmlicense` 加密文件,放入 Flutter assets: + +```yaml +# pubspec.yaml +flutter: + assets: + - assets/xuqm/license.xuqm +``` + +--- + +## 3. 检查授权 + +```dart +import 'package:flutter/services.dart' show rootBundle; +import 'package:xuqm_flutter_license/xuqm_license.dart'; + +Future verifyLicense() async { + // 从 assets 读取加密文件 + final content = await rootBundle.loadString('assets/xuqm/license.xuqm'); + + // 自动解密并初始化 + await initializeFromFile(content); + + // 检查授权(有缓存时直接返回,否则网络验证) + final result = await checkLicense(); + switch (result) { + case LicenseSuccess(reason: final r): + print('授权通过: $r'); + case LicenseError(message: final m): + print('授权失败: $m'); + } +} +``` + +--- + +## 4. 携带用户信息 + +```dart +final result = await checkLicense( + userInfo: LicenseUserInfo( + userId: 'user_001', + name: '张三', + email: 'zhangsan@company.com', + ), +); +``` + +--- + +## 5. API 说明 + +### initialize + +```dart +void initialize(String appKey, {String? baseUrl, String? deviceName}) +``` + +手动初始化,适用于不使用 License 文件的场景。 + +### initializeFromFile + +```dart +Future initializeFromFile(String encryptedContent) +``` + +从加密 License 文件内容自动解析 appKey 和 baseUrl 并初始化。 + +### checkLicense + +```dart +Future checkLicense({LicenseUserInfo? userInfo}) +``` + +返回 `LicenseSuccess(reason)` 或 `LicenseError(message)`(密封类,使用 `switch` 匹配)。 + +**缓存策略**:10 分钟有效期,有效期内不发起网络请求。 + +### getStatus + +```dart +Future getStatus() // LicenseStatus.ok / .denied / .unknown +``` + +### getDeviceId + +```dart +Future getDeviceId() +``` + +### clear + +```dart +Future clear() +``` + +--- + +## 6. 设备唯一码 + +| 平台 | 来源 | 说明 | +|------|------|------| +| Android | `androidInfo.id` | `Settings.Secure.ANDROID_ID` | +| iOS | `iosInfo.identifierForVendor` | 同 Vendor 下卸载重装不变 | +| 其他 | 生成 UUID 存入 SharedPreferences | fallback | + +--- + +## 7. 数据存储 + +| 数据 | 存储方式 | 说明 | +|------|----------|------| +| deviceId | SharedPreferences | 持久化 | +| token | SharedPreferences | 持久化 | +| 授权状态 | SharedPreferences | 非敏感,持久化 | +| statusTime | SharedPreferences | 缓存有效期判断 | + +--- + +## 8. 离线模式 + +- 首次激活需要网络连接 +- 激活后 10 分钟缓存内可离线使用 +- 网络异常时,若历史缓存成功,继续返回 `LicenseSuccess` + +--- + +## 9. 完整示例 + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:xuqm_flutter_sdk/xuqm_flutter_sdk.dart'; +import 'package:xuqm_flutter_license/xuqm_license.dart' as license; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await XuqmSDK.initialize(XuqmInitOptions(appKey: 'your_app_key')); + + // 初始化 License + final content = await rootBundle.loadString('assets/xuqm/license.xuqm'); + await license.initializeFromFile(content); + + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: FutureBuilder( + future: license.checkLicense( + userInfo: license.LicenseUserInfo(userId: 'user_001'), + ), + builder: (context, snapshot) { + if (!snapshot.hasData) return const CircularProgressIndicator(); + final result = snapshot.data!; + if (result is license.LicenseError) { + return Scaffold( + body: Center(child: Text('授权失败: ${result.message}')), + ); + } + return const HomePage(); + }, + ), + ); + } +} +``` + +--- + +## 10. 常见问题 + +**Q: 提示 `Invalid license file format`?** +检查 License 文件是否完整,以及是否已在 `pubspec.yaml` 的 `flutter.assets` 中声明。 + +**Q: 不同平台可以用同一个 License 吗?** +可以,所有平台共用同一个 AppKey,设备数量统一计算。 diff --git a/docs-site/docs/flutter/push.md b/docs-site/docs/flutter/push.md new file mode 100644 index 0000000..7eba089 --- /dev/null +++ b/docs-site/docs/flutter/push.md @@ -0,0 +1,144 @@ +# Flutter 推送接入指南 + +**模块**:`xuqm_flutter_push` · **支持**:华为、小米、OPPO、vivo、荣耀(Android)、APNs(iOS) + +--- + +## 1. 安装 + +在 `pubspec.yaml` 中添加: + +```yaml +dependencies: + xuqm_flutter_push: + git: + url: https://xuqinmin.com/xuqinmin12/XuqmGroup-FlutterSDK.git + ref: v0.2.0 + path: packages/push +``` + +--- + +## 2. Android 厂商推送集成 + +在 `android/app/build.gradle` 中按需添加厂商 SDK: + +```gradle +dependencies { + // 华为 HMS Push + implementation 'com.huawei.hms:push:6.9.0.300' + // 小米 Push(其他厂商同理) +} +``` + +--- + +## 3. 请求原生推送注册 + +```dart +import 'package:xuqm_flutter_push/xuqm_flutter_push.dart'; + +// 触发原生推送注册(Android: 厂商 token;iOS: APNs 权限) +await XuqmPushSdk.requestNativeRegistration(); +``` + +--- + +## 4. 监听推送 Token + +```dart +import 'package:xuqm_flutter_push/xuqm_flutter_push.dart'; + +// 监听 token 回调(广播 Stream,可多处监听) +final subscription = XuqmPushSdk.onPushToken.listen((event) async { + final token = event['token'] ?? ''; + final vendor = event['vendor'] ?? ''; + print('获取到 Token: $token, 厂商: $vendor'); + + // 登录后注册到服务端 + final push = XuqmPushSdk(); + await push.registerToken( + 'user_001', + token, + vendor: vendor, + platform: Platform.isIOS ? 'IOS' : 'ANDROID', + ); +}); + +// 页面销毁时取消监听 +subscription.cancel(); +``` + +--- + +## 5. 手动注册 Token + +```dart +import 'package:xuqm_flutter_push/xuqm_flutter_push.dart'; +import 'dart:io'; + +final push = XuqmPushSdk(); + +await push.registerToken( + 'user_001', // userId + 'device_token', // token + vendor: 'HUAWEI', + platform: Platform.isIOS ? 'IOS' : 'ANDROID', +); +``` + +--- + +## 6. 登出时注销 Token + +```dart +import 'package:xuqm_flutter_common/xuqm_flutter_common.dart' as common; + +final push = XuqmPushSdk(); +final deviceId = await common.apiRequest('/api/device/id'); + +await push.unregisterToken('user_001', deviceId: deviceId); +``` + +--- + +## 7. 检测厂商 + +```dart +final vendor = await XuqmPushSdk.detectVendor(); +print('当前推送厂商: $vendor'); // e.g. 'HUAWEI', 'XIAOMI', 'APNS' +``` + +--- + +## 8. 多模块统一登录 + +```dart +import 'package:xuqm_flutter_sdk/xuqm_flutter_sdk.dart'; +import 'package:xuqm_flutter_push/xuqm_flutter_push.dart'; + +// 初始化 SDK +await XuqmSDK.initialize(XuqmInitOptions(appKey: 'your_app_key')); + +// 请求原生注册,监听 token 后手动上报 +await XuqmPushSdk.requestNativeRegistration(); +XuqmPushSdk.onPushToken.listen((event) async { + final push = XuqmPushSdk(); + await push.registerToken('user_001', event['token']!); +}); +``` + +--- + +## 9. iOS APNs 配置 + +在 Xcode 中开启 Push Notifications 能力(Signing & Capabilities → + Capability → Push Notifications)。 + +`Info.plist` 添加(如需后台推送): + +```xml +UIBackgroundModes + + remote-notification + +``` diff --git a/docs-site/docs/flutter/setup.md b/docs-site/docs/flutter/setup.md index b61340b..bc4d17b 100644 --- a/docs-site/docs/flutter/setup.md +++ b/docs-site/docs/flutter/setup.md @@ -13,7 +13,7 @@ dependencies: xuqm_flutter_sdk: git: url: https://xuqinmin.com/xuqinmin12/XuqmGroup-FlutterSDK.git - ref: v0.2.0 + ref: v0.2.2 ``` > Gitea Package Registry 暂不支持 Dart/Flutter 包格式,因此通过 Git Tag 方式发布。 @@ -28,6 +28,7 @@ dependencies: | `xuqm_flutter_im` | `packages/im` | 单聊、群聊、消息收发、会话、好友、群组 | | `xuqm_flutter_push` | `packages/push` | 设备 Token 注册、厂商检测(Android)/ APNs(iOS)| | `xuqm_flutter_update` | `packages/update` | App 版本检查、商店跳转、APK 下载(Android)| +| `xuqm_flutter_license` | `packages/license` | 设备授权注册与验证(License SDK)| --- @@ -66,3 +67,6 @@ await XuqmImSdk().logout(); ## 下一步 - [Flutter IM 接入 →](./im) +- [Flutter 推送接入 →](./push) +- [Flutter 版本更新 →](./update) +- [Flutter 授权管理 →](./license) diff --git a/docs-site/docs/flutter/update.md b/docs-site/docs/flutter/update.md new file mode 100644 index 0000000..e5b73a1 --- /dev/null +++ b/docs-site/docs/flutter/update.md @@ -0,0 +1,180 @@ +# Flutter 版本更新接入指南 + +**模块**:`xuqm_flutter_update` · **功能**:App 版本检查、商店跳转、APK 直接下载 + +--- + +## 1. 安装 + +在 `pubspec.yaml` 中添加: + +```yaml +dependencies: + xuqm_flutter_update: + git: + url: https://xuqinmin.com/xuqinmin12/XuqmGroup-FlutterSDK.git + ref: v0.2.0 + path: packages/update +``` + +--- + +## 2. 检查版本更新 + +```dart +import 'package:xuqm_flutter_update/xuqm_flutter_update.dart'; + +final sdk = XuqmUpdateSdk(); +final info = await sdk.checkAppUpdate(); + +if (info.needsUpdate) { + print('新版本: ${info.versionName}'); + print('更新说明: ${info.changeLog}'); + print('强制更新: ${info.forceUpdate}'); +} +``` + +`checkAppUpdate()` 会自动读取当前 App 的 versionCode 和 versionName(通过 `package_info_plus`),并与服务端对比。 + +--- + +## 3. 打开商店 + +```dart +// 自动判断平台:iOS → App Store,Android → marketUrl +await sdk.openStore(info); +``` + +--- + +## 4. 直接下载 APK(Android) + +```dart +// 打开 APK 下载链接(使用系统浏览器或下载器) +await sdk.openDownloadUrl(info); +``` + +--- + +## 5. 强制更新处理 + +```dart +import 'package:flutter/material.dart'; +import 'package:xuqm_flutter_update/xuqm_flutter_update.dart'; + +Future checkAndHandleUpdate(BuildContext context) async { + final sdk = XuqmUpdateSdk(); + final info = await sdk.checkAppUpdate(); + + if (!info.needsUpdate) return; + + if (info.forceUpdate) { + // 强制更新:不可关闭的 Dialog + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => AlertDialog( + title: const Text('发现重要更新'), + content: Text('当前版本已不可用,请升级至 ${info.versionName}'), + actions: [ + TextButton( + onPressed: () => sdk.openStore(info), + child: const Text('立即更新'), + ), + ], + ), + ); + } else { + // 可选更新:普通 Dialog + showDialog( + context: context, + builder: (_) => AlertDialog( + title: Text('发现新版本 ${info.versionName}'), + content: Text(info.changeLog ?? ''), + actions: [ + TextButton(onPressed: () => Navigator.pop(context), child: const Text('稍后')), + TextButton(onPressed: () => sdk.openStore(info), child: const Text('立即更新')), + ], + ), + ); + } +} +``` + +--- + +## 6. AppUpdateInfo 字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `needsUpdate` | `bool` | 是否有新版本 | +| `forceUpdate` | `bool` | 是否强制更新 | +| `versionName` | `String?` | 新版本名称(如 `1.2.3`)| +| `versionCode` | `int?` | 新版本号 | +| `changeLog` | `String?` | 更新说明 | +| `downloadUrl` | `String?` | APK 直接下载地址(Android)| +| `appStoreUrl` | `String?` | App Store 链接(iOS)| +| `marketUrl` | `String?` | 各大应用商店链接(Android)| + +--- + +## 7. 开发模式(模拟低版本) + +```dart +// 仅在开发/测试时使用,模拟当前版本为 1,触发更新提示 +XuqmUpdateSdk.devSetAppVersion(1, '0.0.1'); +``` + +--- + +## 8. 完整示例 + +```dart +import 'package:flutter/material.dart'; +import 'package:xuqm_flutter_sdk/xuqm_flutter_sdk.dart'; +import 'package:xuqm_flutter_update/xuqm_flutter_update.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await XuqmSDK.initialize(XuqmInitOptions(appKey: 'your_app_key')); + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) => _checkUpdate()); + } + + Future _checkUpdate() async { + final sdk = XuqmUpdateSdk(); + final info = await sdk.checkAppUpdate(); + if (info.needsUpdate && mounted) { + showDialog( + context: context, + barrierDismissible: !info.forceUpdate, + builder: (_) => AlertDialog( + title: Text('新版本 ${info.versionName}'), + content: Text(info.changeLog ?? ''), + actions: [ + if (!info.forceUpdate) + TextButton(onPressed: () => Navigator.pop(context), child: const Text('稍后')), + TextButton(onPressed: () => sdk.openStore(info), child: const Text('更新')), + ], + ), + ); + } + } + + @override + Widget build(BuildContext context) => const MaterialApp(home: Scaffold()); +} +``` diff --git a/docs-site/docs/ios/index.md b/docs-site/docs/ios/index.md index 4a68e71..2252fca 100644 --- a/docs-site/docs/ios/index.md +++ b/docs-site/docs/ios/index.md @@ -1,6 +1,6 @@ # iOS SDK 概览 -**版本**:0.1.0 · **最低 iOS 版本**:iOS 14 · **语言**:Swift 5.9+ +**版本**:0.2.0 · **最低 iOS 版本**:iOS 16 · **语言**:Swift 5.9+ ## 功能模块 diff --git a/docs-site/docs/ios/license.md b/docs-site/docs/ios/license.md new file mode 100644 index 0000000..9e6a87e --- /dev/null +++ b/docs-site/docs/ios/license.md @@ -0,0 +1,190 @@ +# iOS 授权管理(License SDK) + +**模块**:`LicenseSDK`(包含在 `XuqmSDK` 中)· **最低 iOS 版本**:iOS 16 + +License SDK 用于设备授权注册与验证,Android / iOS / RN / Flutter 共用同一个 AppKey,设备数量统一计算。 + +--- + +## 快速接入 + +### 1. 添加依赖 + +License 模块已内置于 `XuqmSDK`,无需额外安装包: + +```swift +// Package.swift 或 Xcode → File → Add Package Dependencies +// URL: https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK +import XuqmSDK +``` + +### 2. 放置 License 文件 + +从租户平台下载 `.xuqmlicense` 加密文件,放入 App Bundle: + +``` +MyApp/Resources/xuqm/license.xuqm +``` + +在 Xcode 中将该文件添加到 Target → Build Phases → Copy Bundle Resources,确保它出现在 App Bundle 中。SDK 会自动读取 `Bundle.main` 中的 `xuqm/license.xuqm`。 + +### 3. 检查授权 + +SDK **无需手动初始化**,首次调用 `checkLicense()` 时自动读取并解密 License 文件: + +```swift +import XuqmSDK + +Task { + let result = await LicenseSDK.shared.checkLicense() + switch result { + case .success(let reason): + print("授权通过:\(reason)") + case .error(let message): + print("授权失败:\(message)") + } +} +``` + +--- + +## API 说明 + +### checkLicense + +```swift +func checkLicense(userInfo: LicenseUserInfo? = nil) async -> LicenseResult +``` + +**内部逻辑**: +1. 检查本地缓存(默认 10 分钟有效期) +2. 缓存有效 → 直接返回 `.success` +3. 缓存过期或无缓存: + - 有 Token → 调用验证接口 + - 无 Token 或验证失败 → 调用注册接口 +4. 网络异常且缓存曾成功 → 返回 `.success("Offline - cached ok")` +5. 网络异常且无缓存 → 返回 `.error` + +### getStatus + +```swift +func getStatus() -> LicenseStatus // .ok / .denied / .unknown +``` + +同步查询当前状态(不发起网络请求)。 + +### getDeviceId + +```swift +func getDeviceId() -> String? +``` + +### clear + +```swift +func clear() +``` + +清除所有本地授权数据(token、deviceId、状态)。 + +--- + +## 携带用户信息 + +注册时可携带业务用户信息,方便在租户平台进行设备管理: + +```swift +let userInfo = LicenseUserInfo( + userId: "user_001", + name: "张三", + email: "zhangsan@company.com" +) + +let result = await LicenseSDK.shared.checkLicense(userInfo: userInfo) +``` + +--- + +## 设备唯一码 + +| 优先级 | 来源 | 说明 | +|--------|------|------| +| 1 | `UIDevice.identifierForVendor` | 同一 App 卸载重装不变(同 Vendor)| +| 2 | 首次生成 UUID 存入 Keychain | identifierForVendor 不可用时 | + +--- + +## 数据存储 + +| 数据 | 存储方式 | 说明 | +|------|----------|------| +| deviceId | Keychain(Security framework)| 加密持久化 | +| token | Keychain | 加密持久化 | +| 授权状态 | UserDefaults(Suite: `xuqm_license`)| 非敏感 | +| statusTime | UserDefaults | 缓存有效期判断 | + +--- + +## 离线模式 + +- 首次激活需要网络连接 +- 激活成功后,10 分钟缓存内可离线使用 +- 网络异常时,若历史缓存成功,继续返回授权通过 + +--- + +## 手动初始化(高级用法) + +```swift +// 不使用 License 文件,手动指定 AppKey +LicenseSDK.shared.initialize( + appKey: "your_app_key", + baseUrl: "https://auth.dev.xuqinmin.com", + deviceName: "我的 iPhone" +) +``` + +--- + +## 完整示例 + +```swift +import XuqmSDK + +@main +struct MyApp: App { + var body: some Scene { + WindowGroup { + ContentView() + .task { await checkLicense() } + } + } + + func checkLicense() async { + let result = await LicenseSDK.shared.checkLicense( + userInfo: LicenseUserInfo(userId: "user_001") + ) + switch result { + case .success: + break // 授权通过,正常使用 + case .error(let msg): + // 提示用户,限制功能入口 + print("License 验证失败: \(msg)") + } + } +} +``` + +--- + +## 常见问题 + +**Q: checkLicense 返回 error 怎么办?** +检查: +- License 文件是否已添加到 Build Phases → Copy Bundle Resources +- 文件路径是否为 `xuqm/license.xuqm`(大小写敏感) +- 设备是否有网络连接(首次激活需要网络) +- License 是否已过期或被管理员禁用 + +**Q: 不同平台可以用同一个 License 吗?** +可以,所有平台共用同一个 AppKey,设备数量统一计算。 diff --git a/docs-site/docs/ios/setup.md b/docs-site/docs/ios/setup.md index 9c9b1ec..592420c 100644 --- a/docs-site/docs/ios/setup.md +++ b/docs-site/docs/ios/setup.md @@ -1,6 +1,6 @@ # iOS 安装配置 -**版本**:0.1.0 · **最低 iOS 版本**:iOS 16 · **语言**:Swift 5.9+ +**版本**:0.2.0 · **最低 iOS 版本**:iOS 16 · **语言**:Swift 5.9+ --- @@ -22,7 +22,7 @@ let package = Package( name: "MyApp", platforms: [.iOS(.v16)], dependencies: [ - .package(url: "https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK", from: "0.1.0") + .package(url: "https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK", from: "0.2.0") ], targets: [ .target( @@ -91,3 +91,4 @@ XuqmSDK.shared.initialize(config: config) - [iOS IM 接入 →](./im) - [iOS Push 接入 →](./push) - [iOS 版本更新 →](./update) +- [iOS 授权管理 →](./license) diff --git a/docs-site/docs/rn/index.md b/docs-site/docs/rn/index.md index 19faa1d..6e499ad 100644 --- a/docs-site/docs/rn/index.md +++ b/docs-site/docs/rn/index.md @@ -1,6 +1,6 @@ # React Native SDK 接入指南 -**包名**:`@xuqm/rn-sdk` · **版本**:0.3.x(内部基础包,业务方不直接引用) +**包名**:`@xuqm/rn-sdk` · **版本**:0.2.x(内部基础包,业务方不直接引用) > **注意**:`rn-sdk` 作为内部基础包存在,业务方正常接入时使用 `@xuqm/rn-common` 和各业务模块即可。 @@ -14,6 +14,7 @@ | `@xuqm/rn-im` | 单聊、群聊、消息收发、本地 DB(WatermelonDB)| | `@xuqm/rn-push` | 推送设备 Token 上报 | | `@xuqm/rn-update` | App 版本检查、RN Bundle 热更新 | +| `@xuqm/rn-license` | 设备授权注册与验证(License SDK)| | `@xuqm/rn-sdk` | 内部基础包,随 IM / Push / Update 自动安装,不建议业务方直接引用 | --- @@ -23,7 +24,7 @@ 在项目根目录创建 `.npmrc`: ``` -@xuqm:registry=https://nexus.xuqinmin.com/repository/npm/ +@xuqm:registry=https://nexus.xuqinmin.com/repository/npm-hosted/ ``` 只使用基础能力时,直接安装 `rn-common`,不会带入 IM / Push / Update: @@ -42,7 +43,7 @@ yarn add @xuqm/rn-common @xuqm/rn-im --- -## 快速接入(当前 v0.3.x) +## 快速接入(当前 v0.2.x) ### 1. 初始化 @@ -271,8 +272,6 @@ await ImSDK.login(userId, userSig) // dbName 自动由 appKey + userId 派生,无需传入 ``` -UserSig 生成方式见 [安全设计文档](../../design/02-security-design.md)。 - --- ## 常见问题 diff --git a/docs-site/docs/rn/license.md b/docs-site/docs/rn/license.md new file mode 100644 index 0000000..6281c9e --- /dev/null +++ b/docs-site/docs/rn/license.md @@ -0,0 +1,190 @@ +# React Native 授权管理(License SDK) + +**包名**:`@xuqm/rn-license` · **依赖**:`react-native-quick-crypto`(peer dep) + +--- + +## 1. 安装 + +```bash +yarn add @xuqm/rn-license react-native-quick-crypto +``` + +iOS 需要执行 `pod install`: + +```bash +cd ios && pod install +``` + +--- + +## 2. 放置 License 文件 + +从租户平台下载 `.xuqmlicense` 加密文件,通过 `react-native-raw-text` 或 `require()` 将其作为文本资源嵌入 App。 + +**方式一:require 字符串资源(推荐)** + +```ts +// 将 license.xuqm 放入 src/assets/ +const licenseContent = require('./assets/license.xuqm') +``` + +**方式二:手动初始化** + +```ts +import { initialize } from '@xuqm/rn-license' + +initialize('your_app_key', { + baseUrl: 'https://auth.dev.xuqinmin.com', +}) +``` + +--- + +## 3. 检查授权 + +```ts +import { initializeFromFile, checkLicense } from '@xuqm/rn-license' + +// 启动时从加密文件自动初始化 +const licenseContent = require('./assets/license.xuqm') +await initializeFromFile(licenseContent) + +// 检查授权(有缓存时直接返回,否则网络验证) +const result = await checkLicense() +if (result.type === 'success') { + console.log('授权通过:', result.reason) +} else { + console.warn('授权失败:', result.message) +} +``` + +--- + +## 4. 携带用户信息 + +```ts +import type { LicenseUserInfo } from '@xuqm/rn-license' + +const userInfo: LicenseUserInfo = { + userId: 'user_001', + name: '张三', + email: 'zhangsan@company.com', +} + +const result = await checkLicense(userInfo) +``` + +--- + +## 5. API 说明 + +### initialize + +```ts +initialize(appKey: string, options?: { baseUrl?: string; deviceName?: string }): void +``` + +手动初始化,适用于不使用 License 文件的场景。 + +### initializeFromFile + +```ts +initializeFromFile(encryptedContent: string): Promise +``` + +从加密 License 文件内容自动解析 appKey 和 baseUrl 并初始化。 + +### checkLicense + +```ts +checkLicense(userInfo?: LicenseUserInfo): Promise +``` + +返回 `{ type: 'success', reason: string }` 或 `{ type: 'error', message: string }`。 + +**缓存策略**:10 分钟有效期,有效期内直接返回缓存结果(不发起网络请求)。 + +### getStatus + +```ts +getStatus(): Promise // 'ok' | 'denied' | 'unknown' +``` + +### getDeviceId + +```ts +getDeviceId(): Promise +``` + +### clear + +```ts +clear(): Promise +``` + +--- + +## 6. 数据存储 + +| 数据 | 存储方式 | +|------|---------| +| deviceId | AsyncStorage | +| token | AsyncStorage | +| 授权状态 | AsyncStorage | +| statusTime | AsyncStorage | + +--- + +## 7. 离线模式 + +- 首次激活需要网络连接 +- 激活后 10 分钟缓存内可离线使用 +- 网络异常时,若历史缓存成功,继续返回授权通过 + +--- + +## 8. 完整示例 + +```ts +import React, { useEffect, useState } from 'react' +import { View, Text } from 'react-native' +import { initializeFromFile, checkLicense } from '@xuqm/rn-license' + +export default function App() { + const [licensed, setLicensed] = useState(null) + + useEffect(() => { + async function verify() { + const content = require('./assets/license.xuqm') + await initializeFromFile(content) + const result = await checkLicense({ userId: 'user_001' }) + setLicensed(result.type === 'success') + } + verify() + }, []) + + if (licensed === null) return 验证中... + if (!licensed) return 授权验证失败,请联系管理员 + return { /* 主界面 */ } +} +``` + +--- + +## 9. 常见问题 + +**Q: 提示 `react-native-quick-crypto not available`?** +确认已安装 `react-native-quick-crypto` 并执行了 `pod install`(iOS)或重新 build(Android)。 + +**Q: License 文件如何用 require 加载?** +需要在 Metro 配置中将 `.xuqm` 加入 `assetExts`: + +```js +// metro.config.js +const config = { + resolver: { + assetExts: [...defaultConfig.resolver.assetExts, 'xuqm'], + }, +} +``` diff --git a/docs-site/docs/rn/push.md b/docs-site/docs/rn/push.md new file mode 100644 index 0000000..70ac0d0 --- /dev/null +++ b/docs-site/docs/rn/push.md @@ -0,0 +1,138 @@ +# React Native 推送接入指南 + +**包名**:`@xuqm/rn-push` · **支持**:华为、小米、OPPO、vivo、荣耀、APNs(iOS) + +--- + +## 1. 安装 + +```bash +yarn add @xuqm/rn-push +``` + +iOS 需要执行 `pod install`: + +```bash +cd ios && pod install +``` + +--- + +## 2. Android 厂商推送集成 + +各厂商推送 SDK 需在原生 Android 层集成。在 `android/app/build.gradle` 中按需添加: + +```gradle +dependencies { + // 华为 HMS Push + implementation 'com.huawei.hms:push:6.9.0.300' + // 小米 Push + implementation 'com.xiaomi.mipush:sdk:5.0.6' + // OPPO Push + implementation 'com.heytap.msp:push:3.5.0' + // vivo Push + implementation 'com.vivo.push:sdk:3.0.0.4_484' + // 荣耀 Push + implementation 'com.hihonor.mcs:push:7.0.41.301' +} +``` + +--- + +## 3. 请求原生推送权限并注册 + +```ts +import { PushSDK } from '@xuqm/rn-push' + +// 触发原生推送注册(Android 请求厂商 token;iOS 请求 APNs 权限) +await PushSDK.requestNativeRegistration() +``` + +--- + +## 4. 监听推送 Token + +```ts +import { PushSDK } from '@xuqm/rn-push' + +// 在 App 启动时监听 token 回调,并向服务端注册 +const unsubscribe = PushSDK.onPushToken(async (token, vendor) => { + console.log('获取到 Token:', token, '厂商:', vendor) + // 登录后调用注册接口 + await PushSDK.setDeviceToken(token, vendor as PushVendor) +}) + +// 在组件卸载时取消监听 +unsubscribe() +``` + +--- + +## 5. 手动注册 Token + +```ts +import { PushSDK } from '@xuqm/rn-push' +import type { PushVendor } from '@xuqm/rn-push' + +// 登录成功后,将 token 注册到服务端 +await PushSDK.registerToken('user_001', 'device_token_here', 'HUAWEI') +``` + +--- + +## 6. 登出时注销 Token + +```ts +await PushSDK.unregisterToken('user_001') +// 或简写 +await PushSDK.logout('user_001') +``` + +--- + +## 7. 多模块统一登录 + +Push 模块与 IM、Update 模块共享同一套登录态: + +```ts +import { XuqmSDK } from '@xuqm/rn-common' +import { PushSDK } from '@xuqm/rn-push' + +await XuqmSDK.initialize({ appKey: 'your_app_key' }) +await XuqmSDK.login({ userId: 'user_001', userSig: 'your_user_sig' }) +// ↓ 登录后调用 PushSDK.initialize() 完成 token 注册 +await PushSDK.initialize('user_001') +``` + +--- + +## 8. iOS APNs 配置 + +在 `AppDelegate.m` 或 `AppDelegate.swift` 中: + +```objc +// AppDelegate.m +- (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + // 转发 token,由 rn-push 原生模块处理 + [RCTEventEmitter ...] // 通过 Bridge 传至 JS +} +``` + +> 使用 `PushSDK.requestNativeRegistration()` 会自动触发 iOS APNs 注册流程,无需额外原生代码(前提是 RN 0.76+ 自动链接)。 + +--- + +## 9. 厂商渠道自动检测 + +`@xuqm/rn-common` 的 `detectPushVendor` 会根据 `device.brand` 自动识别厂商: + +| 品牌关键字 | 识别厂商 | +|-----------|---------| +| xiaomi / redmi | XIAOMI | +| huawei | HUAWEI | +| honor | HONOR | +| oppo / realme | OPPO | +| vivo / iqoo | VIVO | +| iOS | APNS | +| 其他 | FCM | diff --git a/docs-site/docs/rn/setup.md b/docs-site/docs/rn/setup.md index 4c5457b..4f6c871 100644 --- a/docs-site/docs/rn/setup.md +++ b/docs-site/docs/rn/setup.md @@ -96,7 +96,8 @@ allprojects { ├── @xuqm/rn-common ← 初始化、网络、设备信息 ├── @xuqm/rn-im ← IM 模块(依赖 WatermelonDB) ├── @xuqm/rn-push ← Push 模块 - └── @xuqm/rn-update ← 更新模块 + ├── @xuqm/rn-update ← 更新模块 + └── @xuqm/rn-license ← 授权管理模块(依赖 react-native-quick-crypto) ``` --- @@ -105,4 +106,6 @@ allprojects { - [RN IM 接入 →](./im) - [RN 群聊 →](./group) +- [RN 推送接入 →](./push) - [RN 版本更新 →](./update) +- [RN 授权管理 →](./license) diff --git a/ops-platform/components.d.ts b/ops-platform/components.d.ts index d6e5a5e..6959e75 100644 --- a/ops-platform/components.d.ts +++ b/ops-platform/components.d.ts @@ -12,6 +12,7 @@ declare module 'vue' { ElCard: typeof import('element-plus/es')['ElCard'] ElCol: typeof import('element-plus/es')['ElCol'] ElContainer: typeof import('element-plus/es')['ElContainer'] + ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDialog: typeof import('element-plus/es')['ElDialog'] diff --git a/tenant-platform/src/api/app.ts b/tenant-platform/src/api/app.ts index cae28df..5b59236 100644 --- a/tenant-platform/src/api/app.ts +++ b/tenant-platform/src/api/app.ts @@ -198,4 +198,9 @@ export const appApi = { downloadLicenseFile: (appKey: string) => client.get(`/apps/${appKey}/license-file`, { responseType: 'blob' }), + + requestActivation: (appKey: string, serviceType: 'IM' | 'PUSH' | 'UPDATE' | 'LICENSE', reason: string) => + client.post<{ data: null }>(`/apps/${appKey}/services/request-activation`, null, { + params: { platform: 'ANDROID', serviceType, applyReason: reason }, + }), } diff --git a/tenant-platform/src/views/im/ImManagementView.vue b/tenant-platform/src/views/im/ImManagementView.vue index f0111a2..5a75af7 100644 --- a/tenant-platform/src/views/im/ImManagementView.vue +++ b/tenant-platform/src/views/im/ImManagementView.vue @@ -9,7 +9,27 @@ -