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

128 lines
4.8 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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