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 json) { final patchData = json['patch'] as Map?; final fullApkData = json['fullApk'] as Map?; 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 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 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 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 rollback() async { try { _logger.i('🔄 执行回滚操作'); await FlutterPatcher.rollback(); _logger.i('✅ 回滚成功'); } catch (e) { _logger.e('❌ 回滚失败: $e'); } } /// 清除补丁版本信息(整包更新后调用) Future 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 _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> _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}; } } }