Files
feature-next-arch/lib/core/update/version_check_service.dart

328 lines
11 KiB
Dart
Raw Normal View History

import 'package:dio/dio.dart';
import 'package:flutter/foundation.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),
validateStatus: (status) => true,
));
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 {
debugPrint('');
debugPrint('========================================');
debugPrint(' UPDATE CHECK START');
debugPrint('========================================');
debugPrint('[1] APK version: $currentVersion ($currentVersionCode)');
// 从文件读取已应用的补丁版本
final patchInfo = await _loadPatchVersionInfo();
final appliedPatchVersion = patchInfo['version'] as String?;
final appliedPatchVersionCode = patchInfo['versionCode'] as int?;
debugPrint('[2] Patch file version: $appliedPatchVersion ($appliedPatchVersionCode)');
// 始终用 APK 基础版本请求服务端,让服务端返回最新版本
// 客户端自己判断本地是否已经是最新
final requestVersion = currentVersion;
final requestVersionCode = currentVersionCode;
debugPrint('[3] Request to server: version=$requestVersion, versionCode=$requestVersionCode');
debugPrint('[4] URL: $apiUrl?version=$requestVersion&versionCode=$requestVersionCode');
final response = await _dio.get(
apiUrl,
queryParameters: {
'version': requestVersion,
'versionCode': requestVersionCode,
},
);
debugPrint('[5] HTTP status: ${response.statusCode}');
debugPrint('[6] Response body: ${response.data}');
final respCode = response.data['code'];
final data = response.data['data'];
final message = response.data['message'] ?? '';
debugPrint('[7] respCode=$respCode, data=$data, message=$message');
if (respCode != 200 || data == null) {
debugPrint('[RESULT] No update - server returned code=$respCode, data=${data == null ? "NULL" : "NOT_NULL"}, message=$message');
debugPrint('========================================');
debugPrint('');
return null;
}
debugPrint('[7.1] data type: ${data.runtimeType}');
debugPrint('[7.2] data keys: ${data is Map ? data.keys.toList() : "NOT A MAP"}');
debugPrint('[7.3] hasUpdate = ${data['hasUpdate']} (type: ${data['hasUpdate']?.runtimeType})');
debugPrint('[7.4] version = ${data['version']}');
debugPrint('[7.5] versionCode = ${data['versionCode']}');
debugPrint('[7.6] updateType = ${data['updateType']}');
debugPrint('[7.7] patch = ${data['patch']}');
debugPrint('[7.8] fullApk = ${data['fullApk']}');
if (data['hasUpdate'] == false) {
debugPrint('[RESULT] No update - hasUpdate=false');
debugPrint('========================================');
debugPrint('');
return null;
}
final versionInfo = AppVersionInfo.fromJson(data);
debugPrint('[8] Server version: ${versionInfo.version} (${versionInfo.versionCode})');
debugPrint('[9] Update type: ${versionInfo.updateType}, force: ${versionInfo.forceUpdate}');
debugPrint('[10] patchUrl: ${versionInfo.patchUrl}');
debugPrint('[11] patchMd5: ${versionInfo.patchMd5}');
debugPrint('[12] apkUrl: ${versionInfo.apkUrl}');
// 只用版本字符串判断是否已更新:服务端 hasUpdate=true 说了算
if (appliedPatchVersion != null && appliedPatchVersion == versionInfo.version) {
debugPrint('[RESULT] No update - already at version ${versionInfo.version}');
debugPrint('========================================');
debugPrint('');
return null;
}
debugPrint('[8.1] Local patch version: ${appliedPatchVersion ?? "none"}');
debugPrint('[8.2] Server version: ${versionInfo.version}');
debugPrint('[8.3] Version different: ${appliedPatchVersion != versionInfo.version}');
debugPrint('[RESULT] NEW VERSION FOUND: ${versionInfo.version} (${versionInfo.versionCode})');
debugPrint('========================================');
debugPrint('');
return versionInfo;
} catch (e) {
debugPrint('[RESULT] ERROR: $e');
debugPrint('========================================');
debugPrint('');
return null;
}
}
/// 下载并应用差量补丁
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};
}
}
}