完成集成新的登录接口的更换使用 完成集成用户的场站列表接口 完成开发设计选择场站项为全局属性 完成我的页面的个人信息的集成和详情页面的开发 完成设备页面的中无人机机场的列表接口对接入和个项页面的布局的优化 完成设备页面的中无人机机场的详情接口的对接和使用和页面的更新布局
294 lines
9.5 KiB
Dart
294 lines
9.5 KiB
Dart
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)');
|
||
|
||
final response = await _dio.get(
|
||
apiUrl,
|
||
queryParameters: {
|
||
'version': requestVersion,
|
||
'versionCode': requestVersionCode,
|
||
},
|
||
);
|
||
|
||
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};
|
||
}
|
||
}
|
||
}
|