import 'dart:io'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:http/http.dart' as http; /// 更新服务 - 处理版本检查和客户端更新 class UpdateService { static final UpdateService _instance = UpdateService._internal(); factory UpdateService() => _instance; UpdateService._internal(); final Logger _logger = Logger('UpdateService'); /// 检查远程版本信息 Future checkVersion(String updateUrl) async { try { _logger.info('检查远程版本: $updateUrl'); // 构建版本检查URL: update_url/user/getClientVersion final versionUrl = updateUrl.endsWith('/') ? '${updateUrl}user/getClientVersion' : '$updateUrl/user/getClientVersion'; final response = await http.get(Uri.parse(versionUrl)).timeout( const Duration(seconds: 10), ); if (response.statusCode == 200) { // 返回的是纯文本版本号,不是JSON final remoteVersion = response.body.trim(); if (remoteVersion.isEmpty) { _logger.warning('远程版本号为空'); return null; } _logger.info('获取到远程版本号: $remoteVersion'); // 注意:这里只返回版本号,下载URL需要从其他地方获取 // 可以根据需要扩展 RemoteVersionInfo 或使用其他方式获取下载URL return RemoteVersionInfo( version: remoteVersion, downloadUrl: '', // 需要从其他接口获取或使用默认URL releaseNotes: null, ); } else { _logger.warning('获取版本信息失败: HTTP ${response.statusCode}'); } } catch (e) { _logger.warning('检查版本失败: $e'); } return null; } /// 比较版本号(简单字符串比较,可以改进为语义化版本比较) bool isNewerVersion(String remoteVersion, String localVersion) { // 简单的字符串比较,如果不同则认为远程版本更新 // 可以改进为语义化版本比较(如 1.2.3 vs 1.2.4) return remoteVersion != localVersion && remoteVersion.isNotEmpty; } /// 下载并更新客户端 Future downloadAndUpdate(String downloadUrl) async { try { _logger.info('开始下载新版本: $downloadUrl'); // 获取当前可执行文件路径 final currentExe = Platform.resolvedExecutable; final exeDir = p.dirname(currentExe); final exeName = p.basename(currentExe); // 下载文件保存为临时文件 final tempFile = p.join(exeDir, '${exeName}.new'); final backupFile = p.join(exeDir, '${exeName}.backup'); // 下载文件 final response = await http.get(Uri.parse(downloadUrl)).timeout( const Duration(minutes: 10), ); if (response.statusCode != 200) { _logger.severe('下载失败: HTTP ${response.statusCode}'); return false; } // 保存到临时文件 final file = File(tempFile); await file.writeAsBytes(response.bodyBytes); _logger.info('文件下载完成: $tempFile'); // 备份当前文件 final currentFile = File(currentExe); if (await currentFile.exists()) { await currentFile.copy(backupFile); _logger.info('已备份当前文件: $backupFile'); } // 替换文件(需要关闭当前进程) // 注意:在 Windows 上,不能直接覆盖正在运行的可执行文件 // 需要使用批处理脚本在下次启动时替换 await _createUpdateScript(exeDir, exeName, tempFile, backupFile); _logger.info('更新脚本已创建,将在下次启动时应用更新'); return true; } catch (e) { _logger.severe('下载更新失败: $e'); return false; } } /// 创建更新脚本(Windows批处理文件) Future _createUpdateScript( String exeDir, String exeName, String tempFile, String backupFile, ) async { final scriptPath = p.join(exeDir, 'update.bat'); final script = ''' @echo off timeout /t 2 /nobreak >nul copy /Y "$tempFile" "$exeDir\\$exeName" if %ERRORLEVEL% EQU 0 ( del "$tempFile" echo 更新成功 ) else ( copy /Y "$backupFile" "$exeDir\\$exeName" echo 更新失败,已恢复备份 del "$tempFile" ) del "$backupFile" del "%~f0" '''; final scriptFile = File(scriptPath); await scriptFile.writeAsString(script); // 执行脚本(延迟执行,以便当前进程退出) Process.start('cmd', ['/c', 'start', '/min', scriptPath], runInShell: true); } } /// 远程版本信息 class RemoteVersionInfo { final String version; final String downloadUrl; final String? releaseNotes; RemoteVersionInfo({ required this.version, required this.downloadUrl, this.releaseNotes, }); }