docs: fix version numbers, add license SDK docs; fix service gating reactivity
- Android: correct version header 0.5.x→0.4.x, add sdk-license to module table, update artifact versions to 0.4.10
- iOS: correct min version iOS 14→16, bump version to 0.2.0, update SPM ref to from: "0.2.0"
- RN: fix version 0.3.x→0.2.x, standardize npm registry URL, add @xuqm/rn-license to module table
- Flutter: update git ref to v0.2.2, add xuqm_flutter_license to module tables
- Add new docs: ios/license, rn/push, rn/license, flutter/push, flutter/update, flutter/license
- tenant-platform: make appKey a computed ref in Push/VersionManagementView to fix service gating reactivity on route change
- tenant-platform: add requestActivation API endpoint
- tenant-platform: add IM service gating UI (checkServiceEnabled + activation dialog)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 02:23:57 +08:00
|
|
|
|
# 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;
|
2026-05-16 02:47:23 +08:00
|
|
|
|
import 'package:xuqm_flutter_license/xuqm_flutter_license.dart';
|
docs: fix version numbers, add license SDK docs; fix service gating reactivity
- Android: correct version header 0.5.x→0.4.x, add sdk-license to module table, update artifact versions to 0.4.10
- iOS: correct min version iOS 14→16, bump version to 0.2.0, update SPM ref to from: "0.2.0"
- RN: fix version 0.3.x→0.2.x, standardize npm registry URL, add @xuqm/rn-license to module table
- Flutter: update git ref to v0.2.2, add xuqm_flutter_license to module tables
- Add new docs: ios/license, rn/push, rn/license, flutter/push, flutter/update, flutter/license
- tenant-platform: make appKey a computed ref in Push/VersionManagementView to fix service gating reactivity on route change
- tenant-platform: add requestActivation API endpoint
- tenant-platform: add IM service gating UI (checkServiceEnabled + activation dialog)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 02:23:57 +08:00
|
|
|
|
|
|
|
|
|
|
Future<void> 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<void> initializeFromFile(String encryptedContent)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
从加密 License 文件内容自动解析 appKey 和 baseUrl 并初始化。
|
|
|
|
|
|
|
|
|
|
|
|
### checkLicense
|
|
|
|
|
|
|
|
|
|
|
|
```dart
|
|
|
|
|
|
Future<LicenseResult> checkLicense({LicenseUserInfo? userInfo})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
返回 `LicenseSuccess(reason)` 或 `LicenseError(message)`(密封类,使用 `switch` 匹配)。
|
|
|
|
|
|
|
|
|
|
|
|
**缓存策略**:10 分钟有效期,有效期内不发起网络请求。
|
|
|
|
|
|
|
|
|
|
|
|
### getStatus
|
|
|
|
|
|
|
|
|
|
|
|
```dart
|
|
|
|
|
|
Future<LicenseStatus> getStatus() // LicenseStatus.ok / .denied / .unknown
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### getDeviceId
|
|
|
|
|
|
|
|
|
|
|
|
```dart
|
|
|
|
|
|
Future<String?> getDeviceId()
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### clear
|
|
|
|
|
|
|
|
|
|
|
|
```dart
|
|
|
|
|
|
Future<void> 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,设备数量统一计算。
|