Files
CloudHome-Ops/lib/services/update_service.dart

128 lines
4.8 KiB
Dart
Raw Normal View History

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();