diff --git a/AutoUpdate/AutoUpdate.sln b/AutoUpdate/AutoUpdate.sln index e58e391..5d58408 100644 --- a/AutoUpdate/AutoUpdate.sln +++ b/AutoUpdate/AutoUpdate.sln @@ -4,6 +4,8 @@ VisualStudioVersion = 17.13.35731.43 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdate", "AutoUpdate\AutoUpdate.csproj", "{7851792C-7CFE-4748-A712-974AE110E2D3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdateServer", "AutoUpdateServer\AutoUpdateServer.csproj", "{1BDC940C-E1EC-4D8F-A374-CC360C52EEBB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -14,6 +16,10 @@ Global {7851792C-7CFE-4748-A712-974AE110E2D3}.Debug|Any CPU.Build.0 = Debug|Any CPU {7851792C-7CFE-4748-A712-974AE110E2D3}.Release|Any CPU.ActiveCfg = Release|Any CPU {7851792C-7CFE-4748-A712-974AE110E2D3}.Release|Any CPU.Build.0 = Release|Any CPU + {1BDC940C-E1EC-4D8F-A374-CC360C52EEBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BDC940C-E1EC-4D8F-A374-CC360C52EEBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BDC940C-E1EC-4D8F-A374-CC360C52EEBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BDC940C-E1EC-4D8F-A374-CC360C52EEBB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AutoUpdate/AutoUpdate/App.xaml b/AutoUpdate/AutoUpdate/App.xaml index 91d23de..e1a6071 100644 --- a/AutoUpdate/AutoUpdate/App.xaml +++ b/AutoUpdate/AutoUpdate/App.xaml @@ -2,8 +2,73 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:AutoUpdate" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" StartupUri="MainWindow.xaml"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pack://application:,,,/Lmes;component/字体/MiSans-Normal.ttf#misans + + + + + + + + + + + + diff --git a/AutoUpdate/AutoUpdate/AssemblyInfo.cs b/AutoUpdate/AutoUpdate/AssemblyInfo.cs index cc29e7f..ac816f6 100644 --- a/AutoUpdate/AutoUpdate/AssemblyInfo.cs +++ b/AutoUpdate/AutoUpdate/AssemblyInfo.cs @@ -1,10 +1,11 @@ +using System.Reflection; using System.Windows; -[assembly:ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] \ No newline at end of file diff --git a/AutoUpdate/AutoUpdate/AutoUpdate.csproj b/AutoUpdate/AutoUpdate/AutoUpdate.csproj index defb4d1..1a858a6 100644 --- a/AutoUpdate/AutoUpdate/AutoUpdate.csproj +++ b/AutoUpdate/AutoUpdate/AutoUpdate.csproj @@ -8,4 +8,11 @@ true + + + + + + + diff --git a/AutoUpdate/AutoUpdate/LmesApi.cs b/AutoUpdate/AutoUpdate/LmesApi.cs new file mode 100644 index 0000000..8b040f9 --- /dev/null +++ b/AutoUpdate/AutoUpdate/LmesApi.cs @@ -0,0 +1,102 @@ +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using Path = System.IO.Path; + +namespace AutoUpdate +{ + public class LmesApi + { + public readonly HttpClient httpClient = new(); + public LmesApi(string 基地址) + { + httpClient.BaseAddress = new Uri(基地址); + } + + #region 更新程序 + public async Task 更新客户端程序() + { + try + { + // 获取当前版本信息(从程序集或配置文件中读取) + var versionManager = new VersionManager(); + var currentVersion = versionManager.GetCurrentVersion(); + + // 检查最新版本 + var latestVersion = await 获取最新版本(); + if (latestVersion == null || latestVersion.Version == currentVersion) + { + return currentVersion; // 无需更新 + } + + // 下载新版本 + var downloadPath = await 下载新版本(latestVersion); + if (string.IsNullOrEmpty(downloadPath)) + { + throw new Exception("下载更新包失败"); + } + + // 验证下载文件的CRC + if (!VerifyCRC(downloadPath, latestVersion.CRC)) + { + throw new Exception("文件校验失败,可能已损坏"); + } + + // 使用新的更新管理器 + var updater = new UpdateManager(); + await updater.ExtractAndUpdate(downloadPath, latestVersion.Version); + return latestVersion.Version; + } + catch (Exception ex) + { + // 记录错误日志 + //日志写入.写入($"更新失败: {ex.Message}"); + throw; + } + } + + private async Task 获取最新版本() + { + var response = await httpClient.GetAsync("api/自动更新/获取最新版本信息"); + if (!response.IsSuccessStatusCode) + { + return null; + } + return await response.Content.ReadFromJsonAsync(); + } + + private async Task 下载新版本(VersionInfo versionInfo) + { + var downloadPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Updates", versionInfo.FileName); + + using var response = await httpClient.GetAsync($"api/自动更新/下载指定版本/{versionInfo.FileName}"); + if (!response.IsSuccessStatusCode) + { + return null; + } + + using var fileStream = new FileStream(downloadPath, FileMode.Create); + await response.Content.CopyToAsync(fileStream); + + return downloadPath; + } + + private bool VerifyCRC(string filePath, string expectedCRC) + { + using var fs = System.IO.File.OpenRead(filePath); + using var sha = System.Security.Cryptography.SHA256.Create(); + var hash = sha.ComputeHash(fs); + var actualCRC = BitConverter.ToString(hash).Replace("-", ""); + + return string.Equals(actualCRC, expectedCRC, StringComparison.OrdinalIgnoreCase); + } + public class VersionInfo + { + public string Version { get; set; } + public string CRC { get; set; } + public string FileName { get; set; } + } + #endregion + } +} diff --git a/AutoUpdate/AutoUpdate/MainWindow.xaml b/AutoUpdate/AutoUpdate/MainWindow.xaml index 379fb3a..2250ef9 100644 --- a/AutoUpdate/AutoUpdate/MainWindow.xaml +++ b/AutoUpdate/AutoUpdate/MainWindow.xaml @@ -1,12 +1,13 @@ - + Title="MainWindow" Height="450" Width="800" Loaded="MetroWindow_Loaded"> - - + diff --git a/AutoUpdate/AutoUpdate/MainWindow.xaml.cs b/AutoUpdate/AutoUpdate/MainWindow.xaml.cs index 8e86fe9..1e05afb 100644 --- a/AutoUpdate/AutoUpdate/MainWindow.xaml.cs +++ b/AutoUpdate/AutoUpdate/MainWindow.xaml.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.IO; +using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -8,16 +9,47 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using MahApps.Metro.Controls; +using Path = System.IO.Path; namespace AutoUpdate; /// /// Interaction logic for MainWindow.xaml /// -public partial class MainWindow : Window +public partial class MainWindow : MetroWindow { - public MainWindow() - { - InitializeComponent(); - } -} \ No newline at end of file + private LmesApi lmesApi; + public MainWindow() + { + InitializeComponent(); + lmesApi = new LmesApi(系统参数.设置.Lmes连接参数.LMES地址); + } + + private void MetroWindow_Loaded(object sender, RoutedEventArgs e) + { + + _ = 自动更新程序(); + } + + private async Task 自动更新程序() + { + var 本地目录 = AppDomain.CurrentDomain.BaseDirectory; + var 临时更新目录 = Path.Combine(本地目录, "Updates"); + + // 确保临时更新目录存在 + if (!Directory.Exists(临时更新目录)) + { + Directory.CreateDirectory(临时更新目录); + } + try + { + Title = "当前版本信息:" + await lmesApi.更新客户端程序(); + } + catch (Exception ex) + { + // 更新失败,但允许程序继续运行 + //日志写入.写入($"检查更新失败: {ex.Message}"); + } + } +} diff --git a/AutoUpdate/AutoUpdate/UpdateManager.cs b/AutoUpdate/AutoUpdate/UpdateManager.cs new file mode 100644 index 0000000..5d13004 --- /dev/null +++ b/AutoUpdate/AutoUpdate/UpdateManager.cs @@ -0,0 +1,130 @@ +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Windows; + +namespace AutoUpdate +{ + public class UpdateManager + { + private readonly string _programPath; + private readonly string _updatePath; + private readonly string _backupPath; + private readonly VersionManager _versionManager; + + public UpdateManager() + { + // 程序主目录 + _programPath = AppDomain.CurrentDomain.BaseDirectory; + // 更新文件夹 + _updatePath = Path.Combine(_programPath, "Update"); + // 备份文件夹 + _backupPath = Path.Combine(_programPath, "Backup"); + _versionManager = new VersionManager(); + } + + public async Task ExtractAndUpdate(string zipPath, string newVersion) + { + try + { + // 准备更新目录 + if (Directory.Exists(_updatePath)) + Directory.Delete(_updatePath, true); + Directory.CreateDirectory(_updatePath); + + // 解压新版本到更新目录 + await Task.Run(() => ZipFile.ExtractToDirectory(zipPath, _updatePath)); + + + + + // 创建更新批处理文件 + string batchContent = @" +@echo off +:: 等待原程序退出 +timeout /t 1 /nobreak + +:: 复制新文件 +xcopy /Y /E ""%~dp0Update\*.*"" ""%~dp0."" + +:: 删除更新文件夹 +rd /S /Q ""%~dp0Update"" + +:: 清理 Updates 文件夹 +if exist ""%~dp0Updates"" ( + rd /S /Q ""%~dp0Updates"" +) + +:: 启动程序 +start """" ""%~dp0LMES.exe"" + +:: 删除自身 +del ""%~f0"" +"; + + string batchPath = Path.Combine(_programPath, "update.bat"); + await File.WriteAllTextAsync(batchPath, batchContent); + + // 启动更新批处理并退出程序 + ProcessStartInfo startInfo = new ProcessStartInfo + { + FileName = batchPath, + UseShellExecute = true, + CreateNoWindow = true, + Verb = "runas", // 请求管理员权限 + }; + Process.Start(startInfo); + // 更新版本文件 + _versionManager.UpdateVersion(newVersion); + // 退出应用程序 + Application.Current.Shutdown(); + } + catch (Exception ex) + { + throw new Exception($"更新失败: {ex.Message}"); + } + } + } + + public class VersionManager + { + private readonly string _versionFilePath; + + public VersionManager() + { + _versionFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "version.txt"); + } + + public string GetCurrentVersion() + { + // First try to read from version file + if (File.Exists(_versionFilePath)) + { + try + { + return File.ReadAllText(_versionFilePath).Trim(); + } + catch (Exception ex) + { + //日志写入.写入($"读取版本文件失败: {ex.Message}"); + } + } + + // Fall back to assembly version if file doesn't exist + return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); + } + + public void UpdateVersion(string newVersion) + { + try + { + File.WriteAllText(_versionFilePath, newVersion); + } + catch (Exception ex) + { + //日志写入.写入($"写入版本文件失败: {ex.Message}"); + throw; + } + } + } +} diff --git a/AutoUpdate/AutoUpdate/系统参数.cs b/AutoUpdate/AutoUpdate/系统参数.cs new file mode 100644 index 0000000..10ebfa3 --- /dev/null +++ b/AutoUpdate/AutoUpdate/系统参数.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoUpdate +{ + public class 系统参数 + { + public static 设置类 设置 { get; set; } = new(); + } + + public class 设置类 + { + public Lmes连接参数类 Lmes连接参数 { get; set; } = new(); + } + + public class Lmes连接参数类 + { + public string LMES地址 { get; set; } = @"http://127.0.0.1:5000/"; + } +} diff --git a/AutoUpdate/AutoUpdateServer/AutoUpdateServer.csproj b/AutoUpdate/AutoUpdateServer/AutoUpdateServer.csproj new file mode 100644 index 0000000..f011944 --- /dev/null +++ b/AutoUpdate/AutoUpdateServer/AutoUpdateServer.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + + + + + + + + diff --git a/AutoUpdate/AutoUpdateServer/AutoUpdateServer.http b/AutoUpdate/AutoUpdateServer/AutoUpdateServer.http new file mode 100644 index 0000000..e008039 --- /dev/null +++ b/AutoUpdate/AutoUpdateServer/AutoUpdateServer.http @@ -0,0 +1,6 @@ +@AutoUpdateServer_HostAddress = http://localhost:5062 + +GET {{AutoUpdateServer_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/AutoUpdate/AutoUpdateServer/Controllers/自动更新Controller.cs b/AutoUpdate/AutoUpdateServer/Controllers/自动更新Controller.cs new file mode 100644 index 0000000..b43423c --- /dev/null +++ b/AutoUpdate/AutoUpdateServer/Controllers/自动更新Controller.cs @@ -0,0 +1,133 @@ +using System.ComponentModel; +using System.IO.Compression; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Lmes服务端.Controllers +{ + // 版本信息模型 + public class VersionInfo + { + public string Version { get; set; } + public string CRC { get; set; } + public string FileName { get; set; } + } + [EnableCors("AllowAll")] + [Route("api/[controller]")] + [ApiController] + public class 自动更新Controller : ControllerBase + { + private string 存储路径 { get; set; } + + public 自动更新Controller() + { + // 设置版本文件存储路径 + 存储路径 = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "Lmes_1.0", + "VersionInfo" + ); + + // 确保目录存在 + if (!Directory.Exists(存储路径)) + { + Directory.CreateDirectory(存储路径); + } + } + + // 获取最新版本信息 + [HttpGet("获取最新版本信息")] + public ActionResult GetLatestVersion() + { + try + { + var files = Directory.GetFiles(存储路径, "*.zip"); + if (!files.Any()) + { + return NotFound("未找到任何版本文件"); + } + + // 获取最新的版本文件 + var latestFile = files.OrderByDescending(f => f).First(); + var fileName = Path.GetFileNameWithoutExtension(latestFile); + + // 计算文件的CRC值 + string crc; + using (var fs = System.IO.File.OpenRead(latestFile)) + { + using var sha = System.Security.Cryptography.SHA256.Create(); + var hash = sha.ComputeHash(fs); + crc = BitConverter.ToString(hash).Replace("-", ""); + } + + return new VersionInfo + { + Version = fileName.Split("--")[0], + CRC = crc, + FileName = Path.GetFileName(latestFile) + }; + } + catch (Exception ex) + { + return StatusCode(500, $"获取版本信息失败: {ex.Message}"); + } + } + + // 上传新版本 + [HttpPost("上传新版本")] + public async Task UploadVersion(IFormFile file) + { + try + { + var fileName = file.FileName; + var filePath = Path.Combine(存储路径, fileName); + + using (var stream = new FileStream(filePath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + + // 验证上传的文件是否为有效的ZIP文件 + try + { + using (var archive = ZipFile.OpenRead(filePath)) + { + // 检查是否包含必要的文件 + if (!archive.Entries.Any(e => e.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))) + { + System.IO.File.Delete(filePath); + return BadRequest("ZIP文件中未找到可执行文件"); + } + } + } + catch + { + System.IO.File.Delete(filePath); + return BadRequest("上传的文件不是有效的ZIP文件"); + } + + return Ok("文件上传成功"); + } + catch (Exception ex) + { + return StatusCode(500, $"文件上传失败: {ex.Message}"); + } + } + + // 下载指定版本 + [HttpGet("下载指定版本/{fileName}")] + public IActionResult DownloadVersion(string fileName) + { + var filePath = Path.Combine(存储路径, fileName); + + if (!System.IO.File.Exists(filePath)) + { + return NotFound("指定的版本文件不存在"); + } + + var fileStream = System.IO.File.OpenRead(filePath); + return File(fileStream, "application/zip", fileName); + } + } +} diff --git a/AutoUpdate/AutoUpdateServer/Program.cs b/AutoUpdate/AutoUpdateServer/Program.cs new file mode 100644 index 0000000..83b48e4 --- /dev/null +++ b/AutoUpdate/AutoUpdateServer/Program.cs @@ -0,0 +1,53 @@ + +using Microsoft.AspNetCore.Server.Kestrel.Core; + +namespace AutoUpdateServer +{ + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + builder.Services.AddCors(options => + { + options.AddPolicy("AllowAll", builder => + { + builder.AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + }); + }); + builder.Services.AddControllers(); + // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + builder.Services.AddLogging(); + builder.Services.Configure(options => + { + options.Limits.MaxRequestBodySize = 1024 * 1024 * 1024; // 100 MB + }); + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseCors("AllowAll"); + + app.UseSwagger(); + app.UseSwaggerUI(); + + app.MapControllers(); + + app.Run(); + } + } +} diff --git a/AutoUpdate/AutoUpdateServer/Properties/launchSettings.json b/AutoUpdate/AutoUpdateServer/Properties/launchSettings.json new file mode 100644 index 0000000..135ac87 --- /dev/null +++ b/AutoUpdate/AutoUpdateServer/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5062", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7116;http://localhost:5062", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/AutoUpdate/AutoUpdateServer/appsettings.Development.json b/AutoUpdate/AutoUpdateServer/appsettings.Development.json new file mode 100644 index 0000000..0268711 --- /dev/null +++ b/AutoUpdate/AutoUpdateServer/appsettings.Development.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://*:5000" + } + } + } +} diff --git a/AutoUpdate/AutoUpdateServer/appsettings.json b/AutoUpdate/AutoUpdateServer/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/AutoUpdate/AutoUpdateServer/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}