128 lines
4.8 KiB
Dart
128 lines
4.8 KiB
Dart
|
|
import 'package:flutter/foundation.dart';
|
|||
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|||
|
|
|
|||
|
|
import '../models/app_version.dart';
|
|||
|
|
import '../utils/session_org_scope.dart';
|
|||
|
|
import 'api_client.dart';
|
|||
|
|
|
|||
|
|
/// 版本更新检测结果。
|
|||
|
|
///
|
|||
|
|
/// [hasUpdate] 是否检测到更高版本;[latest] 服务端最新可用版本(无更新时为 null)。
|
|||
|
|
class UpdateCheckResult {
|
|||
|
|
const UpdateCheckResult({required this.hasUpdate, this.latest});
|
|||
|
|
|
|||
|
|
final bool hasUpdate;
|
|||
|
|
final AppVersion? latest;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 版本更新服务:检测当前 App 是否有新版本可更新。
|
|||
|
|
///
|
|||
|
|
/// 对齐 Web 端 `searchAppVersion`:按当前登录用户所属组织查询该组织配置的
|
|||
|
|
/// APP 版本(`GET /device/orgGroupAppVersion/search?orgId=...`),再与本地
|
|||
|
|
/// versionCode 比较。
|
|||
|
|
///
|
|||
|
|
/// 注意:这里不再调用 `/device/orgGroupAppVersion/list`。`/list` 是管理后台
|
|||
|
|
/// 全量枚举接口,普通用户常因权限不足收到「未知异常,请联系管理员」;
|
|||
|
|
/// `/search` 才是「查询某组织当前应使用版本」的语义入口。
|
|||
|
|
class UpdateService {
|
|||
|
|
UpdateService({required this.packageName, ApiClient? client})
|
|||
|
|
: _client = client ?? ApiClient.instance;
|
|||
|
|
|
|||
|
|
/// 当前 App 包名(运行时由 package_info_plus 取得,如 com.tongshi.cloudhome)。
|
|||
|
|
final String packageName;
|
|||
|
|
|
|||
|
|
final ApiClient _client;
|
|||
|
|
|
|||
|
|
/// 查询当前用户所属组织配置的版本,并判定是否有更新。
|
|||
|
|
///
|
|||
|
|
/// - 优先使用传入的 [orgId],未传入时回退到 [SessionOrgScope.instance.currentOrgId];
|
|||
|
|
/// - 若最终没有可用 orgId(管理员/未识别),直接返回「无更新」,避免向后端
|
|||
|
|
/// 发起缺少必要参数的请求;
|
|||
|
|
/// - 调用 `/device/orgGroupAppVersion/search?orgId=xxx` 获取该组织配置的版本;
|
|||
|
|
/// - 兼容返回体为 `null` / `{appVersion: {...}}` / 直接版本对象三种形态;
|
|||
|
|
/// - 仅当返回版本 `status==0` 且 versionCode 大于当前版本时才认为有更新;
|
|||
|
|
/// - 若后端返回了 packageName 且与当前 App 包名不一致,也认为无更新,防止
|
|||
|
|
/// 不同包被错误提示。
|
|||
|
|
///
|
|||
|
|
/// 任一异常均向上抛出,由调用方决定是否静默忽略(启动时检测失败不应阻断使用)。
|
|||
|
|
Future<UpdateCheckResult> checkForUpdate(
|
|||
|
|
int currentVersionCode, {
|
|||
|
|
String? orgId,
|
|||
|
|
}) async {
|
|||
|
|
final effectiveOrgId = (orgId != null && orgId.isNotEmpty)
|
|||
|
|
? orgId
|
|||
|
|
: (SessionOrgScope.instance.enabled
|
|||
|
|
? SessionOrgScope.instance.currentOrgId
|
|||
|
|
: null);
|
|||
|
|
|
|||
|
|
if (effectiveOrgId == null || effectiveOrgId.isEmpty) {
|
|||
|
|
if (kDebugMode) {
|
|||
|
|
debugPrint('[UpdateService] 缺少 orgId,跳过更新检查');
|
|||
|
|
}
|
|||
|
|
return const UpdateCheckResult(hasUpdate: false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (kDebugMode) {
|
|||
|
|
debugPrint(
|
|||
|
|
'[UpdateService] orgId=$effectiveOrgId currentCode=$currentVersionCode',
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final response = await _client.dio.get<dynamic>(
|
|||
|
|
'/device/orgGroupAppVersion/search',
|
|||
|
|
queryParameters: {'orgId': effectiveOrgId},
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
final latest = _parseVersion(response.data);
|
|||
|
|
if (latest == null) {
|
|||
|
|
if (kDebugMode) debugPrint('[UpdateService] 未查询到组织版本配置');
|
|||
|
|
return const UpdateCheckResult(hasUpdate: false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (kDebugMode) {
|
|||
|
|
debugPrint(
|
|||
|
|
'[UpdateService] server versionCode=${latest.versionCode} '
|
|||
|
|
'versionName=${latest.versionName} status=${latest.status} '
|
|||
|
|
'packageName=${latest.packageName}',
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (latest.status != 0) {
|
|||
|
|
return const UpdateCheckResult(hasUpdate: false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 后端配置了 packageName 但与当前 App 不匹配时,不提示更新
|
|||
|
|
//(常见于同一组织下存在多个不同包的场景)。
|
|||
|
|
if (latest.packageName.isNotEmpty && latest.packageName != packageName) {
|
|||
|
|
return const UpdateCheckResult(hasUpdate: false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return UpdateCheckResult(
|
|||
|
|
hasUpdate: latest.versionCode > currentVersionCode,
|
|||
|
|
latest: latest,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 解析 `/search` 返回的三种形态:
|
|||
|
|
/// - `null`:该组织没有配置版本;
|
|||
|
|
/// - `{appVersion: {...}}`:Web 端标准形态;
|
|||
|
|
/// - 直接返回版本对象。
|
|||
|
|
AppVersion? _parseVersion(dynamic data) {
|
|||
|
|
if (data == null) return null;
|
|||
|
|
if (data is! Map<String, dynamic>) return null;
|
|||
|
|
final appVersion = data['appVersion'];
|
|||
|
|
if (appVersion is Map<String, dynamic>) {
|
|||
|
|
return AppVersion.fromJson(appVersion);
|
|||
|
|
}
|
|||
|
|
if (data.containsKey('id') || data.containsKey('versionName')) {
|
|||
|
|
return AppVersion.fromJson(data);
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 读取当前 App 的 [PackageInfo](包名 / versionCode / versionName)。
|
|||
|
|
///
|
|||
|
|
/// 集中封装以便多处复用,并在 [PackageInfo] 未初始化时提供兜底。
|
|||
|
|
Future<PackageInfo> getCurrentPackageInfo() => PackageInfo.fromPlatform();
|