Files
feature-tenant/lib/core/update/version_check_service.dart
Songzex 71aea48d9f 集成接口 开始执行任务的领域层和数据层和UI层的开发啊(待测试)
集成功能 暂停 取消功能 恢复功能的接口的领域层和数据层的开发 下一步待集成到页面上
优化功能,优化了接口异常和未知异常对页面渲染的影响对用户的体验的不好情况。具体通过弹窗友好提示!
优化更新了tcp指示灯点击出现机器状态中添加选中设备的编号 方便用户使用的明白明了!。
优化更新关闭了tcp重连操作内链条中的获取用户设备和切换设备操作项。
调整了获取无人机状态信息的更新频率 为14秒一次
2026-06-16 17:34:00 +08:00

309 lines
10 KiB
Dart
Raw 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:dio/dio.dart';
import 'package:flutter_patcher/flutter_patcher.dart';
import 'package:logger/logger.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
/// 应用版本信息模型
class AppVersionInfo {
final String version;
final int versionCode;
final String updateType;
final bool forceUpdate;
final String updateDesc;
final String? patchUrl;
final String? patchMd5;
final String? apkUrl;
final String? apkMd5;
AppVersionInfo({
required this.version,
required this.versionCode,
required this.updateType,
required this.forceUpdate,
required this.updateDesc,
this.patchUrl,
this.patchMd5,
this.apkUrl,
this.apkMd5,
});
factory AppVersionInfo.fromJson(Map<String, dynamic> json) {
final patchData = json['patch'] as Map<String, dynamic>?;
final fullApkData = json['fullApk'] as Map<String, dynamic>?;
return AppVersionInfo(
version: json['version'] ?? '',
versionCode: json['versionCode'] ?? 0,
updateType: json['updateType'] ?? 'full',
forceUpdate: json['forceUpdate'] ?? false,
updateDesc: json['updateDesc'] ?? '',
patchUrl: patchData?['patchUrl'] as String?,
patchMd5: patchData?['md5'] as String?,
apkUrl: fullApkData?['apkUrl'] as String?,
apkMd5: fullApkData?['md5'] as String?,
);
}
}
/// 版本检查服务
class VersionCheckService {
static final VersionCheckService _instance = VersionCheckService._internal();
factory VersionCheckService() => _instance;
VersionCheckService._internal();
final Logger _logger = Logger();
final Dio _dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
));
static const String apiUrl = 'http://8.159.134.0:8012/api/update/check';
static const String _patchVersionFileName = '.patch_version';
/// 检查版本更新
Future<AppVersionInfo?> checkUpdate({
required String currentVersion,
required int currentVersionCode,
}) async {
try {
_logger.i('🔍 检查版本更新');
_logger.i('📱 APK 自带版本: $currentVersion ($currentVersionCode)');
// 从文件读取已应用的补丁版本
final patchInfo = await _loadPatchVersionInfo();
final appliedPatchVersion = patchInfo['version'] as String?;
final appliedPatchVersionCode = patchInfo['versionCode'] as int?;
_logger.i('📂 读取到的补丁版本: $appliedPatchVersion ($appliedPatchVersionCode)');
// 如果打过补丁,使用补丁的版本;否则使用 APK 自带版本
final requestVersion = appliedPatchVersion ?? currentVersion;
final requestVersionCode = appliedPatchVersionCode ?? currentVersionCode;
_logger.i('🚀 请求后端版本: $requestVersion ($requestVersionCode)');
// 🔥 打印完整的请求参数
_logger.i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
_logger.i('📤 App 发送的请求参数:');
_logger.i(' URL: $apiUrl');
_logger.i(' version: $requestVersion');
_logger.i(' versionCode: $requestVersionCode');
_logger.i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
final response = await _dio.get(
apiUrl,
queryParameters: {
'version': requestVersion,
'versionCode': requestVersionCode,
},
);
// 🔥 打印完整的后端返回数据
_logger.i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
_logger.i('📥 后端返回的完整数据:');
_logger.i(' statusCode: ${response.statusCode}');
_logger.i(' 原始响应: ${response.data}');
_logger.i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
if (response.statusCode == 200) {
final data = response.data['data'];
if (data['hasUpdate'] == false) {
_logger.i('✅ 已是最新版本');
return null;
}
final versionInfo = AppVersionInfo.fromJson(data);
// 🔥 关键修复:无论更新类型是什么,只要本地已应用此版本,就跳过
if (appliedPatchVersion != null && appliedPatchVersion == versionInfo.version) {
_logger.i('✅ 本地已应用版本 ${versionInfo.version},跳过更新(updateType: ${versionInfo.updateType})');
return null;
}
// 🔥 额外检查:如果本地补丁版本code >= 服务端返回的versionCode,也跳过
if (appliedPatchVersionCode != null && appliedPatchVersionCode >= versionInfo.versionCode) {
_logger.i('✅ 本地补丁版本code ($appliedPatchVersionCode) >= 服务端versionCode (${versionInfo.versionCode}),跳过更新');
return null;
}
_logger.i('✅ 发现新版本: ${versionInfo.version}');
_logger.i('🔄 更新类型: ${versionInfo.updateType}');
_logger.i('⚠️ 强制更新: ${versionInfo.forceUpdate}');
return versionInfo;
}
return null;
} catch (e, stack) {
_logger.e('❌ 版本检查失败: $e');
_logger.e('❌ 堆栈信息: $stack');
throw Exception('网络请求失败: $e');
}
}
/// 下载并应用差量补丁
Future<bool> applyPatch({
required String patchUrl,
required String version,
required int targetVersionCode,
String? md5,
Function(double)? onProgress,
}) async {
try {
_logger.i('⬇️ 开始下载补丁: $patchUrl');
_logger.i('🎯 目标版本: $version ($targetVersionCode)');
final tempDir = await getTemporaryDirectory();
final patchPath = '${tempDir.path}/update_patch.so';
await _dio.download(
patchUrl,
patchPath,
onReceiveProgress: (received, total) {
if (total != -1 && onProgress != null) {
onProgress(received / total);
}
},
);
_logger.i('✅ 补丁下载完成,开始应用...');
final result = await FlutterPatcher.applyPatch(
PatchInfo(
version: version,
patchUrl: 'file://$patchPath',
targetVersionCode: targetVersionCode,
md5: md5 ?? '',
),
);
if (result.ok) {
_logger.i('✅ 补丁应用成功');
// 保存补丁版本号到文件
await _savePatchVersionInfo(version, targetVersionCode);
// 验证保存
final verifyInfo = await _loadPatchVersionInfo();
_logger.i('💾 已保存并验证: ${verifyInfo['version']} (${verifyInfo['versionCode']})');
} else {
_logger.e('❌ 补丁应用失败: ${result.error}');
}
return result.ok;
} catch (e) {
_logger.e('❌ 应用补丁异常: $e');
return false;
}
}
/// 下载完整 APK
Future<String?> downloadApk(String apkUrl, Function(double) onProgress) async {
try {
_logger.i('⬇️ 开始下载 APK: $apkUrl');
final cacheDir = await getApplicationCacheDirectory();
final apkPath = '${cacheDir.path}/app-update.apk';
_logger.i('📁 APK 下载路径: $apkPath');
await _dio.download(
apkUrl,
apkPath,
onReceiveProgress: (received, total) {
if (total != -1) {
onProgress(received / total);
}
},
);
_logger.i('✅ APK 下载完成: $apkPath');
return apkPath;
} catch (e) {
_logger.e('❌ 下载 APK 失败: $e');
return null;
}
}
/// 回滚到内置版本
Future<void> rollback() async {
try {
_logger.i('🔄 执行回滚操作');
await FlutterPatcher.rollback();
_logger.i('✅ 回滚成功');
} catch (e) {
_logger.e('❌ 回滚失败: $e');
}
}
/// 清除补丁版本信息(整包更新后调用)
Future<void> clearPatchVersionInfo() async {
try {
final directory = await getApplicationSupportDirectory();
final file = File('${directory.path}/$_patchVersionFileName');
if (await file.exists()) {
await file.delete();
_logger.i('🗑️ 已清除补丁版本记录');
}
} catch (e) {
_logger.e('❌ 清除补丁版本信息失败: $e');
}
}
/// 保存补丁版本信息到文件
Future<void> _savePatchVersionInfo(String version, int versionCode) async {
try {
final directory = await getApplicationSupportDirectory();
final file = File('${directory.path}/$_patchVersionFileName');
final content = '$version|$versionCode';
await file.writeAsString(content);
// 🔥 立即验证写入
final verifyContent = await file.readAsString();
_logger.i('💾 补丁版本已保存到: ${file.path}');
_logger.i('💾 写入内容: $content');
_logger.i('💾 验证读取: $verifyContent');
_logger.i('💾 文件存在: ${await file.exists()}');
} catch (e) {
_logger.e('❌ 保存补丁版本信息失败: $e');
}
}
/// 从文件加载补丁版本信息
Future<Map<String, dynamic>> _loadPatchVersionInfo() async {
try {
final directory = await getApplicationSupportDirectory();
final file = File('${directory.path}/$_patchVersionFileName');
_logger.i('📂 尝试加载补丁版本文件: ${file.path}');
_logger.i('📂 文件存在: ${await file.exists()}');
if (await file.exists()) {
final content = await file.readAsString();
_logger.i('📂 文件内容: $content');
final parts = content.split('|');
if (parts.length == 2) {
final version = parts[0];
final versionCode = int.tryParse(parts[1]);
if (versionCode != null) {
_logger.i('📂 解析成功: version=$version, versionCode=$versionCode');
return {'version': version, 'versionCode': versionCode};
} else {
_logger.e('📂 versionCode 解析失败: ${parts[1]}');
}
} else {
_logger.e('📂 文件格式错误,parts.length=${parts.length}');
}
}
_logger.i('📂 未找到有效的补丁版本信息');
return {'version': null, 'versionCode': null};
} catch (e) {
_logger.e('❌ 加载补丁版本信息失败: $e');
return {'version': null, 'versionCode': null};
}
}
}