Files
windows-application/lib/services/update_service.dart
2026-01-22 15:14:27 +08:00

155 lines
4.8 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
});
}