Files
windows-application/lib/services/update_service.dart

155 lines
4.8 KiB
Dart
Raw Normal View History

2026-01-22 15:14:27 +08:00
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<RemoteVersionInfo?> 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<bool> 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<void> _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,
});
}