You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

420 lines
14 KiB

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:logger/logger.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../models/channel_app_model.dart';
import 'network/api.dart';
import 'dart:math' as math;
/// 版本更新服务类
class VersionUpdateService extends GetxService {
static VersionUpdateService get to => Get.find();
// 静态应用信息(内存缓存)
static String? _appVersion = ""; //应用版本号
static String? _buildNumber = ""; //构建号
static String? _platform = ""; //平台类型
static String? _deviceBrand = ""; //设备品牌
static UserChannel? _channel; //渠道
static String? _latestVersion = ""; //最新版本号+构建号
static bool? _hasUpdate = false; //是否有更新
static bool? _isForceUpdate = false; //是否强制更新
static String? _updateUrl = ""; //更新链接
static AppInfo? _appInfo; //应用信息
static bool? _isShowTourists = false; //是否显示游客登录
static DateTime? _lastCheckTime; //上次检查时间
static const int _cacheIntervalMinutes = 10; // 可配置化
static final String? _countryCode = Get.deviceLocale?.countryCode; //国家地区编码
static final String? _languageCode = Get.deviceLocale?.languageCode; //语言编码
// ignore: non_constant_identifier_names
static final String? _SERVER_URL = (_countryCode == 'CN')
? dotenv.env['SERVER_URL']
: dotenv.env['HW_SERVER_URL']; //服务器地址
// 添加 getter 方法来访问缓存的静态数据
/// 获取缓存的应用版本
String get cachedAppVersion => _appVersion ?? '';
/// 获取缓存的构建号
String get cachedBuildNumber => _buildNumber ?? '';
/// 获取缓存的平台信息
String get cachedPlatform => _platform ?? '';
/// 获取缓存的设备品牌
String get cachedDeviceBrand => _deviceBrand ?? '';
/// 获取缓存的渠道信息
UserChannel get cachedChannel => _channel ?? UserChannel.UNKNOWN;
/// 获取缓存的最新版本
String get cachedLatestVersion => _latestVersion ?? '';
/// 获取缓存的是否有更新
bool get cachedHasUpdate => _hasUpdate ?? false;
/// 获取缓存的是否强制更新
bool get cachedIsForceUpdate => _isForceUpdate ?? false;
/// 获取缓存的更新地址
String get cachedUpdateUrl => _updateUrl ?? '';
/// 获取缓存的应用信息
AppInfo? get cachedAppInfo => _appInfo;
/// 获取缓存的游客登录状态
bool get cachedShowTourists => _isShowTourists ?? false;
/// 获取上次检查时间
DateTime? get lastCheckTime => _lastCheckTime;
/// 获取缓存的国家地区编码
String get cachedCountryCode => _countryCode ?? '';
/// 获取缓存的语言编码
String get cachedLanguageCode => _languageCode ?? '';
/// 获取缓存的服务器地址
String get cachedServerUrl => _SERVER_URL ?? '';
final Logger _logger = Logger();
// @override
// void onInit() {
// getCurrentVersion();
// getCurrentPlatformInfo();
// super.onInit();
// }
/// 获取当前应用版本信息
Future<String> getCurrentVersion() async {
try {
final packageInfo = await PackageInfo.fromPlatform();
_appVersion = packageInfo.version;
_buildNumber = packageInfo.buildNumber;
return '${packageInfo.version}+${packageInfo.buildNumber}';
} catch (e) {
_logger.e('获取当前版本信息失败: $e');
return '1.0.0';
}
}
/// 获取当前平台信息
Future<PlatformInfo> getCurrentPlatformInfo() async {
if (Platform.isIOS) {
return const PlatformInfo(
platform: 'ios',
storeType: AppStoreType.appStore,
channel: UserChannel.GLOBAL_APPLE,
);
}
if (Platform.isAndroid) {
// 根据设备品牌判断应用商店类型
return await getAndroidStoreInfoByBrand();
}
return const PlatformInfo(
platform: 'unknown',
storeType: AppStoreType.unknown,
channel: UserChannel.UNKNOWN,
);
}
/// 根据设备品牌获取应用商店信息
///
/// 该方法根据设备品牌判断应用商店类型,并返回对应的 [PlatformInfo] 对象。
/// 如果品牌不在预设列表中,将使用环境变量 [Channel] 或默认值 [UserChannel.GLOBAL_GOOGLE]。
Future<PlatformInfo> getAndroidStoreInfoByBrand() async {
try {
final deviceInfo = DeviceInfoPlugin();
final androidInfo = await deviceInfo.androidInfo;
final brand = androidInfo.brand.toLowerCase();
_deviceBrand = brand;
String channelFromEnv = dotenv.env['Channel'] ?? 'OFFICIAL';
_channel = UserChannel.fromName(channelFromEnv);
UserChannel brandChannel; //品牌渠道,只用于请求版本更新信息
switch (brand) {
case 'huawei':
brandChannel = UserChannel.CHINA_HUAWEI;
break;
case 'xiaomi':
case 'redmi':
brandChannel = UserChannel.CHINA_XIAOMI;
break;
case 'oppo':
case 'oneplus':
brandChannel = UserChannel.CHINA_OPPO;
break;
case 'vivo':
case 'iqoo':
brandChannel = UserChannel.CHINA_VIVO;
break;
case 'honor':
brandChannel = UserChannel.CHINA_HONOR;
break;
default:
brandChannel = _channel ?? UserChannel.GLOBAL_GOOGLE;
break;
}
return PlatformInfo(
platform: 'android',
storeType: AppStoreType.official,
channel: brandChannel,
);
} catch (e) {
_logger.e('获取Android设备信息失败: $e');
return const PlatformInfo(
platform: 'android',
storeType: AppStoreType.googlePlay,
channel: UserChannel.UNKNOWN,
);
}
}
/// 检查版本更新
Future<VersionUpdateInfo> checkForUpdate() async {
if (_lastCheckTime != null &&
DateTime.now().difference(_lastCheckTime!).inMinutes <
_cacheIntervalMinutes) {
// [1]没有超过10分钟,返回缓存数据
_logger.d('没有超过10分钟,返回缓存数据');
return VersionUpdateInfo(
hasUpdate: _hasUpdate ?? false,
currentVersion: _appVersion ?? '1.0.0',
latestVersion: (_latestVersion ?? '1.0.0').split('+')[0],
appInfo: _appInfo,
updateType: _hasUpdate!
? (_isForceUpdate! ? UpdateType.force : UpdateType.optional)
: UpdateType.none,
);
}
try {
final currentVersion = await getCurrentVersion();
final platformInfo = await getCurrentPlatformInfo();
_platform = platformInfo.platform;
//_channel = platformInfo.channel;
_lastCheckTime = DateTime.now();
// 调用后端接口获取最新版本信息
final response =
await Api.getChannelApp({'channel': platformInfo.channel.value});
_logger.d('lxm--获取渠道包信息:$response');
if (response == null || !response.containsKey('app')) {
//[2]获取渠道包信息失败: 响应为空或不包含app字段
_logger.w('获取渠道包信息失败: 响应为空或不包含app字段');
return VersionUpdateInfo(
hasUpdate: false,
currentVersion: currentVersion,
latestVersion: currentVersion,
updateType: UpdateType.none,
);
}
//final channelAppResponse = ChannelAppResponse.fromJson(response);
// 直接从response中获取app信息
final appInfo = AppInfo.fromJson(response['app']);
_appInfo = appInfo;
_logger.d('获取到的最新版本信息: ${appInfo.toJson()}');
_latestVersion = appInfo.version;
_updateUrl = appInfo.address;
// 设置游客登录显示状态(先用当前渠道结果作为回退,再用.env渠道覆盖以保持一致)
_isShowTourists = appInfo.tourists;
if (_channel != platformInfo.channel) {
//[4]当前渠道与.env渠道不同,需要从.env渠道获取游客登录配置
await _refreshTouristsFromEnv();
}
_logger.d('从服务器获取游客登录配置(最终): ${_isShowTourists}');
// if (channelAppResponse.code != 0 || channelAppResponse.data == null) {
// _logger.w('获取版本信息失败: ${channelAppResponse.msg}');
// return VersionUpdateInfo(
// hasUpdate: false,
// currentVersion: currentVersion,
// latestVersion: currentVersion,
// updateType: UpdateType.none,
// );
// }
// final appInfo = channelAppResponse.data!.app;
_hasUpdate = _compareVersions(currentVersion, appInfo.version);
// 判断更新类型:如果主版本号不同则强制更新
UpdateType updateType = UpdateType.none;
if (_hasUpdate!) {
_isForceUpdate = _shouldForceUpdate(currentVersion, appInfo.version);
updateType = _isForceUpdate! ? UpdateType.force : UpdateType.optional;
}
//[3]返回版本更新信息
return VersionUpdateInfo(
hasUpdate: _hasUpdate!,
currentVersion: currentVersion.split('+')[0],
latestVersion: appInfo.version.split('+')[0],
appInfo: appInfo,
updateType: updateType,
);
} catch (e) {
_logger.e('检查版本更新失败: $e');
// 发生错误时,保持默认值 false
_isShowTourists = false;
final currentVersion = await getCurrentVersion();
return VersionUpdateInfo(
hasUpdate: false,
currentVersion: currentVersion,
latestVersion: currentVersion,
updateType: UpdateType.none,
);
}
}
/// 从.env文件刷新游客登录配置
Future<void> _refreshTouristsFromEnv() async {
try {
final envChannelName = dotenv.env['Channel'];
if (envChannelName == null || envChannelName.isEmpty) {
_logger.w('未配置.env Channel,沿用当前渠道的游客配置: ${_isShowTourists}');
return;
}
final envChannel = UserChannel.fromName(envChannelName);
final resp = await Api.getChannelApp({'channel': envChannel.value});
if (resp != null && resp.containsKey('app')) {
final envAppInfo = AppInfo.fromJson(resp['app']);
_isShowTourists = envAppInfo.tourists;
_logger.d(
'游客登录配置已按.env渠道同步: ${envAppInfo.tourists} (channel=$envChannelName)');
} else {
_logger.w('按.env渠道获取游客配置失败:响应为空或不含app字段');
}
} catch (e) {
_logger.w('按.env渠道同步游客配置失败: $e');
}
}
/// 判断是否需要强制更新
/// 当主版本号(第一位数字)不同时返回true
bool _shouldForceUpdate(String current, String latest) {
try {
// 拆分版本号与构建号
final currentParts = current.split('+');
final latestParts = latest.split('+');
// 解析版本号 (major.minor.patch)
final currentVersions =
currentParts[0].split('.').map(int.parse).toList();
final latestVersions = latestParts[0].split('.').map(int.parse).toList();
// 比较主版本号(第一位)
if (currentVersions.isNotEmpty && latestVersions.isNotEmpty) {
if (currentVersions[0] != latestVersions[0]) {
return true;
}
}
// 检查是否有两个或更多的 + 号,如果有则强制更新
if (latest.split('+').length > 2) {
return true;
}
return false;
} catch (e) {
_logger.e('判断强制更新失败: $e');
return false;
}
}
/// 比较版本号
/// 返回true表示需要更新(远程版本更高)
bool _compareVersions(String current, String latest) {
// 拆分版本号与构建号
final currentParts = current.split('+')[0];
final latestParts = latest.split('+')[0];
// 解析版本号 (major.minor.patch)
final currentVersions = currentParts.split('.').map(int.parse).toList();
final latestVersions = latestParts.split('.').map(int.parse).toList();
// 使用较小的长度进行比较,避免索引越界
final minLength = math.min(currentVersions.length, latestVersions.length);
// 逐级比较版本号
for (var i = 0; i < minLength; i++) {
if (latestVersions[i] > currentVersions[i]) return true;
if (latestVersions[i] < currentVersions[i]) return false;
}
// 如果前面的版本号都相同,比较长度
// 更长的版本号被认为是更新的版本(如 1.0.1 > 1.0)
return latestVersions.length > currentVersions.length;
}
/// 解析版本号字符串为数字数组
List<int> _parseVersion(String version) {
final parts = version.split('.');
final result = <int>[];
for (int i = 0; i < 3; i++) {
if (i < parts.length) {
result.add(int.tryParse(parts[i]) ?? 0);
} else {
result.add(0);
}
}
return result;
}
/// 执行更新操作
Future<bool> performUpdate(VersionUpdateInfo updateInfo) async {
//print('lxm---updateInfo详情:');
//print('lxm---hasUpdate: ${updateInfo.hasUpdate}');
//print('lxm---currentVersion: ${updateInfo.currentVersion}');
//print('lxm---latestVersion: ${updateInfo.latestVersion}');
//print('lxm---updateType: ${updateInfo.updateType}');
//print('lxm---appInfo: ${updateInfo.appInfo?.toJson()}');
if (!updateInfo.hasUpdate || updateInfo.appInfo == null) {
return false;
}
try {
_logger.d('lxm---执行更新操作,下载地址: ${updateInfo.appInfo!.address}');
return await _openDownloadUrl(updateInfo.appInfo!.address!);
} catch (e) {
_logger.e('执行更新失败: $e');
return false;
}
}
/// 打开下载链接
Future<bool> _openDownloadUrl(String downloadUrl) async {
return await _launchUrl(downloadUrl);
}
/// 启动URL
Future<bool> _launchUrl(String url) async {
try {
final uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
return await launchUrl(uri, mode: LaunchMode.externalApplication);
}
return false;
} catch (e) {
_logger.e('启动URL失败: $e');
return false;
}
}
}