From 15287382a9f1346ea7767e7085d89664c855e152 Mon Sep 17 00:00:00 2001 From: Dong <1278815766@qq.com> Date: Mon, 12 May 2025 01:45:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90=E9=98=BF=E9=87=8C?= =?UTF-8?q?=E4=BA=91OSS=20SDK=E4=BB=A5=E5=AE=9E=E7=8E=B0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此次提交引入了阿里云OSS SDK,用于从阿里云OSS存储桶中下载文件。主要修改包括: 1. 在packages.config和CheckDownload.csproj中添加了阿里云OSS SDK的依赖。 2. 在Form1.cs中重构了文件下载逻辑,使用阿里云OSS SDK替代原有的HTTP下载方式。 3. 移除了原有的DNS查询和本地HTTP服务器依赖,简化了文件获取流程。 4. 优化了文件校验和保存的逻辑,确保下载文件的完整性和正确性。 --- CheckDownload.csproj | 3 + Form1.cs | 834 +++++++++---------------------------------- packages.config | 1 + 3 files changed, 168 insertions(+), 670 deletions(-) diff --git a/CheckDownload.csproj b/CheckDownload.csproj index fc1e1b8..de6e93e 100644 --- a/CheckDownload.csproj +++ b/CheckDownload.csproj @@ -48,6 +48,9 @@ 4 + + packages\Aliyun.OSS.SDK.2.14.1\lib\net461\Aliyun.OSS.dll + packages\DnsClient.1.8.0\lib\net472\DnsClient.dll diff --git a/Form1.cs b/Form1.cs index 4e8ab56..1ffd7ef 100644 --- a/Form1.cs +++ b/Form1.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; +using System.Diagnostics.CodeAnalysis; using System.Drawing; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -12,533 +14,275 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; +using Aliyun.OSS; +using Aliyun.OSS.Common; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace CheckDownload { - /// - /// 自动更新窗体类,负责检查和下载软件更新 - /// public partial class Update : Form { - /// 本地MD5文件名 - private const string LocalMd5File = "md5.json"; - /// DNS查询域名,用于获取更新信息 - private const string DnsQueryDomain = "test.file.ipoi.cn"; - /// 基础下载URL - private const string BaseDownloadUrl = "http://localhost:60006/"; - /// 最大并发下载数 - private const int MaxConcurrentDownloads = 5; - - /// 在线MD5文件名,从DNS查询获取 - private string _onlineMd5File = ""; - - // 类级别变量,用于存储下载的文件数据 + // MD5文件名称 + private const string Md5File = "md5.json"; + // 阿里云OSS访问地址 + private const string OssEndpoint = "oss-cn-hongkong.aliyuncs.com"; + // 阿里云OSS存储空间名称 + private const string OssBucketName = "sxho-hk"; + // 阿里云OSS访问密钥ID + private const string OssAccessKeyId = "LTAI5tBc8BgooVrHrzfEeg1q"; + // 阿里云OSS访问密钥Secret + private const string OssAccessKeySecret = "nMvSp0UqzodTzKImuAMK1a1bkSks5O"; + // 用于存储下载的文件数据 private Dictionary _downloadedFiles = new Dictionary(); - /// 已完成下载的文件数 - private int _completedFiles = 0; - /// 需要下载的总文件数 - private int _totalFilesToDownload = 0; + // 已完成的下载数量 + private int _completedCount = 0; + // 总下载数量 + private int _totalCount = 0; - /// - /// 构造函数,初始化更新窗体 - /// + // 初始化窗体 public Update() { InitializeComponent(); ConfigureProgressBar(); } - /// - /// 配置进度条初始设置 - /// + // 配置进度条 private void ConfigureProgressBar() { - Update_Pro.Minimum = 0; // 设置最小值 - Update_Pro.Maximum = 100; // 设置最大值 - Update_Pro.Value = 0; // 设置初始值 - Update_Pro.Step = 1; // 设置步进值 + Update_Pro.Minimum = 0; + Update_Pro.Maximum = 100; + Update_Pro.Value = 0; + Update_Pro.Step = 1; } - - /// - /// 更新进度条值 - /// - /// 进度百分比(0-100) + private void UpdateProgressValue(int percentage) { - // 确保百分比在有效范围内 if (percentage < 0) percentage = 0; if (percentage > 100) percentage = 100; - - // 在UI线程上更新进度条值 + if (this.InvokeRequired) { this.Invoke((MethodInvoker)delegate { - // 确保进度条值不超出范围 - if (percentage >= Update_Pro.Minimum && percentage <= Update_Pro.Maximum) - { - Update_Pro.Value = percentage; - } - else - { - // 如果超出范围,则设置为最大或最小值 - Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum); - } - // 添加Application.DoEvents()调用,确保消息队列被处理 + Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum); Application.DoEvents(); }); } else { - if (percentage >= Update_Pro.Minimum && percentage <= Update_Pro.Maximum) - { - Update_Pro.Value = percentage; - } - else - { - Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum); - } + Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum); Application.DoEvents(); } } - /// - /// 窗体加载事件处理方法,启动更新流程 - /// - /// 事件源 - /// 事件参数 - public async void Update_Load(object sender, EventArgs e) + private void UpdateStatus(string message) { - // 设置窗体位置到屏幕右下角 - PositionFormToBottomRight(); - - await UpdateFile(); // 执行更新流程 - // 注意:窗体关闭逻辑已移至UpdateFile方法中,确保显示完成消息后再关闭 - } - - /// - /// 设置窗体位置到屏幕右下角 - /// - private void PositionFormToBottomRight() - { - // 获取当前屏幕的工作区域(排除任务栏等系统元素) - Rectangle workingArea = Screen.GetWorkingArea(this); - - // 计算窗体在右下角的位置 - int x = workingArea.Right - this.Width; - int y = workingArea.Bottom - this.Height; - - // 设置窗体位置 - this.Location = new Point(x, y); + if (this.InvokeRequired) + { + this.Invoke((MethodInvoker)delegate { Status_Box.Text = message; }); + } + else + { + Status_Box.Text = message; + } + } + + public async void Update_Load(object sender, EventArgs e) + { + PositionFormToBottomRight(); + await UpdateFile(); + } + + private void PositionFormToBottomRight() + { + Rectangle workingArea = Screen.GetWorkingArea(this); + this.Location = new Point(workingArea.Right - this.Width, workingArea.Bottom - this.Height); } - /// - /// 主要更新流程,检查并下载更新文件 - /// - /// 更新任务 private async Task UpdateFile() { try { - // 定义更新流程的总步骤数和当前步骤 - const int totalSteps = 6; // 总共6个主要步骤 + const int totalSteps = 4; int currentStep = 0; - - // 更新进度条初始状态 - UpdateProgressValue(0); - - // 步骤1: 读取本地MD5文件信息 - UpdateStatus("读取本地MD5文件..."); - var localData = await ReadLocalMd5File(); - if (!ValidateLocalData(localData.Version, localData.Md5, localData.Data)) return; - UpdateProgressValue(++currentStep * 100 / totalSteps); // 更新进度为16% - // 步骤2: 获取在线MD5文件信息 - UpdateStatus("获取在线MD5文件信息..."); - var onlineData = await GetOnlineMd5File(); - if (!ValidateOnlineData(onlineData.Version, onlineData.Md5)) return; - UpdateProgressValue(++currentStep * 100 / totalSteps); // 更新进度为33% + UpdateStatus("下载在线MD5文件并读取..."); + OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret); + var onlineData = ReadOnlineMd5File(client, OssBucketName, Md5File); + if (!ValidateOnlineData(onlineData.Version, onlineData.Md5, onlineData.Data)) return; + UpdateProgressValue(++currentStep * 100 / totalSteps); - // 步骤3: 比较版本,判断是否需要更新 - UpdateStatus("比较版本信息..."); - if (!ShouldUpdate(localData.Version, onlineData.Version)) + UpdateStatus("比较本地和在线MD5文件..."); + var compareResult = CompareMd5Data(onlineData.Data); + if (compareResult.Count == 0) { - UpdateStatus("当前已是最新版本,无需更新"); - UpdateProgressValue(100); // 完成进度 + UpdateStatus("所有文件都是最新的,无需更新"); + UpdateProgressValue(100); + await Task.Delay(2000); + this.Close(); return; } - UpdateProgressValue(++currentStep * 100 / totalSteps); // 更新进度为50% + UpdateProgressValue(++currentStep * 100 / totalSteps); - // 步骤4: 下载在线MD5文件 - UpdateStatus("下载在线MD5文件..."); - var onlineFileData = await DownloadOnlineMd5File(); - if (onlineFileData == null) return; - UpdateProgressValue(++currentStep * 100 / totalSteps); // 更新进度为66% + UpdateStatus("下载并验证文件..."); + _totalCount = compareResult.Count; + await DownloadAndVerifyFiles(compareResult); + UpdateProgressValue(100); - // 步骤5: 比较文件差异 - UpdateStatus("比较文件差异..."); - var differences = CompareDataDifferences(localData.Data, onlineFileData); - UpdateProgressValue(++currentStep * 100 / totalSteps); // 更新进度为83% - - // 步骤6: 下载和更新文件 - if (differences.Count > 0) - { - // 下载需要更新的文件 - await DownloadUpdatedFiles(differences); - } - else - { - UpdateStatus("无文件需要更新"); - } - - // 替换本地MD5文件 - ReplaceLocalMd5File(); - UpdateProgressValue(100); // 完成进度 - - // 显示更新完成消息 UpdateStatus("更新完成"); - - // 等待1秒后关闭窗体 await Task.Delay(1000); this.Close(); } catch (Exception ex) { - UpdateStatus($"发生错误: {ex.Message}"); + UpdateStatus($"更新失败: {ex.Message}"); + await Task.Delay(3000); + this.Close(); } } - /// - /// 验证本地数据的有效性 - /// - /// 版本号 - /// MD5校验值 - /// 文件数据对象 - /// 数据是否有效 - private bool ValidateLocalData(string version, string md5, JObject data) - { - if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null) - { - UpdateStatus("本地MD5文件无效"); - return false; - } - return true; - } - - /// - /// 验证在线数据的有效性 - /// - /// 版本号 - /// MD5校验值 - /// 数据是否有效 - private bool ValidateOnlineData(string version, string md5) - { - if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5)) - { - UpdateStatus("无法获取在线MD5信息"); - return false; - } - return true; - } - - /// - /// 读取本地MD5文件并解析内容 - /// - /// 包含版本号、MD5值和数据对象的元组 - private async Task<(string Version, string Md5, JObject Data)> ReadLocalMd5File() + private (string Version, string Md5, JObject Data) ReadOnlineMd5File(OssClient client, string bucket, string key) { try { - UpdateStatus("读取md5.json..."); - // 异步读取文件内容 - string json = await Task.Run(() => File.ReadAllText(LocalMd5File)); - // 解析JSON对象 - var obj = JObject.Parse(json); - // 获取版本号 - string version = obj["version"]?.ToString(); - // 获取数据对象 - var data = (JObject)obj["data"]; - // 计算JSON内容的MD5值 - string jsonMd5 = CalculateMD5(json); - return (version, jsonMd5, data); + var obj = client.GetObject(bucket, key); + using (var reader = new StreamReader(obj.Content)) + { + string json = reader.ReadToEnd(); + var parsed = JObject.Parse(json); + string version = parsed["version"]?.ToString(); + var data = (JObject)parsed["data"]; + string jsonMd5 = CalculateMD5(json); + return (version, jsonMd5, data); + } } - catch (Exception ex) when (ex is FileNotFoundException || ex is JsonException) + catch (Exception ex) when (ex is OssException || ex is JsonException) { - UpdateStatus($"读取本地MD5文件失败: {ex.Message}"); + UpdateStatus($"读取在线MD5文件失败: {ex.Message}"); return (null, null, null); } } - /// - /// 获取在线MD5文件信息 - /// - /// 包含版本号和MD5值的元组 - private async Task<(string Version, string Md5)> GetOnlineMd5File() + private bool ValidateOnlineData(string version, string md5, JObject data) { - try + if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null) { - UpdateStatus("解析在线md5.json..."); - // 通过DNS查询获取响应数据 - string responseData = await QueryDnsAsync(); - // 反序列化JSON字符串 - string firstUnescaped = JsonConvert.DeserializeObject(responseData); - // 解析JSON对象 - var dataJson = JObject.Parse(firstUnescaped); - - // 获取版本号和MD5值 - string version = dataJson["version"]?.ToString(); - string md5 = dataJson["md5"]?.ToString(); - - // 设置在线MD5文件名 - _onlineMd5File = $"{md5}.json"; - - return (version, md5); - } - catch (Exception ex) - { - UpdateStatus($"获取在线MD5信息失败: {ex.Message}"); - return (null, null); - } - } - - /// - /// 判断是否需要更新 - /// - /// 本地版本号 - /// 在线版本号 - /// 是否需要更新 - private bool ShouldUpdate(string localVersion, string onlineVersion) - { - try - { - UpdateStatus("校验信息..."); - // 解析版本号字符串为Version对象 - var localVer = new Version(localVersion); - var onlineVer = new Version(onlineVersion); - // 比较版本号,如果本地版本小于在线版本,则需要更新 - return localVer.CompareTo(onlineVer) < 0; - } - catch (Exception ex) when (ex is FormatException || ex is ArgumentNullException) - { - UpdateStatus($"版本比较失败: {ex.Message}"); + UpdateStatus("在线MD5文件无效"); return false; } + return true; } - /// - /// 下载在线MD5文件并解析内容 - /// - /// 文件数据对象 - private async Task DownloadOnlineMd5File() - { - try - { - UpdateStatus("下载在线md5.json文件..."); - // 使用WebClient下载文件 - using (var client = new WebClient()) - { - await client.DownloadFileTaskAsync( - new Uri($"{BaseDownloadUrl}{_onlineMd5File}"), - _onlineMd5File); - } - - UpdateStatus("读取在线md5.json文件..."); - // 异步读取文件内容 - string json = await Task.Run(() => File.ReadAllText(_onlineMd5File)); - // 解析JSON对象 - var obj = JObject.Parse(json); - // 返回数据部分 - return (JObject)obj["data"]; - } - catch (Exception ex) - { - UpdateStatus($"下载在线MD5文件失败: {ex.Message}"); - return null; - } - } - - /// - /// 比较本地数据和在线数据的差异 - /// - /// 本地数据对象 - /// 在线数据对象 - /// 当前路径,用于递归调用 - /// 需要更新的文件路径和MD5值的字典 - private Dictionary CompareDataDifferences(JObject localData, JObject onlineData, string currentPath = "") + private Dictionary CompareMd5Data(JObject onlineData, string currentPath = "") { var differences = new Dictionary(); foreach (var onlineProperty in onlineData.Properties()) { - // 获取属性名称 string key = onlineProperty.Name; - // 获取属性值 JToken onlineValue = onlineProperty.Value; - // 构建完整路径 string fullPath = string.IsNullOrEmpty(currentPath) ? key : $"{currentPath}/{key}"; if (onlineValue.Type == JTokenType.String) { - // 如果是字符串类型,表示是文件的MD5值 string expectedMd5 = onlineValue.ToString(); - // 判断是否需要下载该文件 - if (ShouldDownloadFile(localData, key, expectedMd5, fullPath)) + if (!File.Exists(fullPath)) { - differences[fullPath] = expectedMd5; + if (!differences.ContainsKey(fullPath)) + { + differences[fullPath] = expectedMd5; + } + } + else + { + string localMd5 = CalculateMD5(File.ReadAllBytes(fullPath)); + if (localMd5 != expectedMd5) + { + if (!differences.ContainsKey(fullPath)) + { + differences[fullPath] = expectedMd5; + } + } } } else if (onlineValue.Type == JTokenType.Object) { - // 如果是对象类型,表示是目录,递归比较 - JObject localSubData = GetSubData(localData, key); - var subDifferences = CompareDataDifferences(localSubData, (JObject)onlineValue, fullPath); + var subDifferences = CompareMd5Data((JObject)onlineValue, fullPath); foreach (var diff in subDifferences) { - differences[diff.Key] = diff.Value; + if (!differences.ContainsKey(diff.Key)) + { + differences[diff.Key] = diff.Value; + } } } } - return differences; } - /// - /// 判断是否需要下载文件 - /// - /// 本地数据对象 - /// 文件键名 - /// 期望的MD5值 - /// 文件完整路径 - /// 是否需要下载 - private bool ShouldDownloadFile(JObject localData, string key, string expectedMd5, string fullPath) - { - // 本地数据中不存在该键 - bool fileMissing = !localData.ContainsKey(key); - // 本地数据中存在该键,但MD5值不匹配 - bool md5Mismatch = localData.ContainsKey(key) && - (localData[key].Type != JTokenType.String || - localData[key].ToString() != expectedMd5); - // 物理文件不存在 - bool physicalFileMissing = !File.Exists(Path.Combine(".", fullPath.Replace('/', '\\'))); - - // 满足任一条件则需要下载 - return fileMissing || md5Mismatch || physicalFileMissing; - } - - /// - /// 获取本地数据中的子数据对象 - /// - /// 本地数据对象 - /// 子数据键名 - /// 子数据对象,如果不存在则返回空对象 - private JObject GetSubData(JObject localData, string key) - { - // 如果本地数据中存在该键且类型为对象,则返回该对象,否则返回空对象 - return localData.ContainsKey(key) && localData[key].Type == JTokenType.Object - ? (JObject)localData[key] - : new JObject(); - } - - /// - /// 下载需要更新的文件 - /// - /// 需要更新的文件路径和MD5值的字典 - /// 下载任务 - private async Task DownloadUpdatedFiles(Dictionary differences) - { - // 重置下载状态 - _completedFiles = 0; - _downloadedFiles.Clear(); - - // 设置总文件数 - _totalFilesToDownload = differences.Count; - - // 更新状态信息 - UpdateStatus($"开始下载更新文件,共{differences.Count}个文件..."); - - // 创建信号量控制并发下载数量 - var semaphore = new SemaphoreSlim(MaxConcurrentDownloads); - var downloadTasks = new List(); - - // 为每个需要更新的文件创建下载任务 - foreach (var file in differences) - { - downloadTasks.Add(DownloadFileWithSemaphore(file, semaphore)); - } - - // 等待所有下载任务完成 - await Task.WhenAll(downloadTasks); - - // 所有文件下载完成后统一校验和保存 - UpdateStatus("所有文件下载完成,开始校验和保存..."); - VerifyAndSaveAllFiles(); - } - - /// - /// 使用信号量控制并发下载文件 - /// - /// 文件路径和MD5值的键值对 - /// 控制并发的信号量 - /// 下载任务 - private async Task DownloadFileWithSemaphore(KeyValuePair file, SemaphoreSlim semaphore) - { - await semaphore.WaitAsync(); - try - { - await DownloadToMemory(file.Key, file.Value); - } - finally - { - semaphore.Release(); - } - } - - /// - /// 下载文件到内存并暂存 - /// - private async Task DownloadToMemory(string relativePath, string expectedMd5) + private async Task DownloadAndVerifyFiles(Dictionary fileList) { try { - // 更新状态显示当前下载进度 - int current = Interlocked.Increment(ref _completedFiles); - - // 下载文件到内存 - using (var client = new HttpClient()) + OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret); + _totalCount = fileList.Count; + _completedCount = 0; + _downloadedFiles.Clear(); + + var options = new ParallelOptions { MaxDegreeOfParallelism = 4 }; + + await Task.Run(() => { - var fileUrl = $"{BaseDownloadUrl}File/{expectedMd5}"; - var response = await client.GetAsync(fileUrl); - response.EnsureSuccessStatusCode(); - - byte[] fileData = await response.Content.ReadAsByteArrayAsync(); - - // 存储到内存字典 - lock (_downloadedFiles) + Parallel.ForEach(fileList, options, (file, state) => { - _downloadedFiles[relativePath] = (fileData, expectedMd5); - } + string filePath = file.Key; + string expectedMd5 = file.Value; + + try + { + string ossKey = $"File/{expectedMd5}"; + var obj = client.GetObject(OssBucketName, ossKey); + using (var memoryStream = new MemoryStream()) + { + obj.Content.CopyTo(memoryStream); + byte[] fileData = memoryStream.ToArray(); + + lock (_downloadedFiles) + { + _downloadedFiles[filePath] = (fileData, expectedMd5); + } + + Interlocked.Increment(ref _completedCount); + int progress = (int)((double)_completedCount / _totalCount * 95); + UpdateProgressValue(progress); + UpdateStatus($"已下载 {_completedCount}/{_totalCount}"); + } + } + catch (Exception ex) + { + UpdateStatus($"下载文件 {filePath} 失败: {ex.Message}"); + } + }); + }); + + if (_downloadedFiles.Count != fileList.Count) + { + throw new Exception($"部分文件下载失败,成功 {_downloadedFiles.Count}/{fileList.Count}"); } - // 更新进度 - UpdateProgress(); + VerifyAndSaveAllFiles(); } catch (Exception ex) { - UpdateStatus($"下载失败: {relativePath} - {ex.Message}"); + UpdateStatus($"下载过程出错: {ex.Message}"); throw; } } - /// - /// 校验并保存所有已下载的文件 - /// private void VerifyAndSaveAllFiles() { UpdateStatus("正在校验文件..."); - - // 创建失败文件列表 var failedFiles = new List(); int processedCount = 0; int totalFiles = _downloadedFiles.Count; @@ -551,40 +295,21 @@ namespace CheckDownload try { - // 更新状态信息 processedCount++; UpdateStatus($"正在校验和保存文件 ({processedCount}/{totalFiles}): {relativePath}"); - - // 计算内存中数据的MD5 - string actualMd5; - using (var md5 = MD5.Create()) - { - byte[] hashBytes = md5.ComputeHash(data); - actualMd5 = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); - } - // 校验MD5 + string actualMd5 = CalculateMD5(data); if (actualMd5 != expectedMd5.ToLower()) { throw new Exception($"MD5校验失败 (期望: {expectedMd5}, 实际: {actualMd5})"); } - // 获取本地路径并创建目录 - string localPath = GetLocalPath(relativePath); - EnsureDirectoryExists(localPath); - - // 保存文件 + string localPath = Path.Combine(Application.StartupPath, relativePath); + Directory.CreateDirectory(Path.GetDirectoryName(localPath)); File.WriteAllBytes(localPath, data); - - // 更新进度条 - 从95%到98% - // 确保即使在totalFiles为0的情况下也不会出现除零错误,并且百分比不会超过98 - int percentage = 95; - if (totalFiles > 0) - { - // 计算进度时确保不会超过98% - percentage = 95 + Math.Min(3, (int)(processedCount * 3.0 / totalFiles)); - } - UpdateProgressValue(percentage); + + int percentage = 95 + (int)(processedCount * 5.0 / totalFiles); + UpdateProgressValue(Math.Min(100, percentage)); } catch (Exception ex) { @@ -593,7 +318,6 @@ namespace CheckDownload } } - // 清理失败的文件记录 foreach (var failedFile in failedFiles) { _downloadedFiles.Remove(failedFile); @@ -606,257 +330,27 @@ namespace CheckDownload else { UpdateStatus("所有文件校验和保存成功"); + UpdateProgressValue(100); } } - /// - /// 获取文件的本地路径 - /// - /// 文件的相对路径 - /// 本地文件路径 - private string GetLocalPath(string relativePath) - { - // 将相对路径转换为本地路径,替换斜杠为反斜杠 - return Path.Combine(".", relativePath.Replace('/', '\\')); - } - - /// - /// 确保文件所在目录存在 - /// - /// 文件路径 - private void EnsureDirectoryExists(string filePath) - { - // 获取文件所在目录 - string directory = Path.GetDirectoryName(filePath); - // 如果目录不存在,则创建目录 - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - } - - /// - /// 验证文件的MD5值 - /// - /// 文件路径 - /// 期望的MD5值 - /// 文件的相对路径,用于错误提示 - /// 当MD5校验失败时抛出异常 - private void VerifyFileMd5(string filePath, string expectedMd5, string relativePath) - { - // 计算文件的实际MD5值 - string actualMd5 = CalculateFileMD5(filePath); - // 如果MD5值不匹配,删除文件并抛出异常 - if (actualMd5 != expectedMd5) - { - File.Delete(filePath); - throw new Exception($"MD5校验失败: {relativePath}"); - } - } - - /// - /// 替换现有文件 - /// - /// 临时文件路径 - /// 目标文件路径 - private void ReplaceExistingFile(string tempPath, string localPath) - { - // 如果目标文件已存在,则先删除 - if (File.Exists(localPath)) - { - File.Delete(localPath); - } - // 将临时文件移动到目标位置 - File.Move(tempPath, localPath); - } - - /// - /// 更新文件下载进度 - /// - private void UpdateProgress() - { - // 计算下载进度百分比,避免除零错误 - int percentage = 0; - if (_totalFilesToDownload > 0) - { - percentage = (int)(_completedFiles * 100.0 / _totalFilesToDownload); - } - - // 更新状态信息 - UpdateStatus($"正在下载文件 ({_completedFiles}/{_totalFilesToDownload})..."); - - // 在UI线程上更新进度条值 - this.BeginInvoke((MethodInvoker)delegate - { - // 在下载阶段,进度从83%到95%之间变化 - // 确保不会超过95% - int overallPercentage = 83 + Math.Min(12, (int)(percentage * 12.0 / 100)); - // 使用UpdateProgressValue方法确保值在有效范围内 - UpdateProgressValue(overallPercentage); - }); - } - - /// - /// 替换本地MD5文件为在线下载的新版本 - /// - private void ReplaceLocalMd5File() - { - try - { - // 如果本地MD5文件已存在,则先删除 - if (File.Exists(LocalMd5File)) - { - File.Delete(LocalMd5File); - } - // 将下载的在线MD5文件移动到本地MD5文件位置 - File.Move(_onlineMd5File, LocalMd5File); - UpdateStatus("更新完成"); - } - catch (Exception ex) - { - UpdateStatus($"更新本地版本信息失败: {ex.Message}"); - } - } - - /// - /// 更新状态信息显示 - /// - /// 要显示的状态信息 - private void UpdateStatus(string message) - { - // 使用Invoke而不是BeginInvoke,确保UI更新完成 - if (this.InvokeRequired) - { - this.Invoke((MethodInvoker)delegate - { - Status_Box.Text = message; - // 立即刷新控件,确保状态实时显示 - Status_Box.Update(); - // 添加Application.DoEvents()调用,确保消息队列被处理 - Application.DoEvents(); - }); - } - else - { - Status_Box.Text = message; - Status_Box.Update(); - Application.DoEvents(); - } - } - - /// - /// 计算字符串的MD5值 - /// - /// 输入字符串 - /// MD5哈希值(小写十六进制字符串) private string CalculateMD5(string input) { using (var md5 = MD5.Create()) { - // 将字符串转换为字节数组 byte[] inputBytes = Encoding.UTF8.GetBytes(input); - // 计算哈希值 byte[] hashBytes = md5.ComputeHash(inputBytes); - // 将字节数组转换为十六进制字符串并返回 return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } - /// - /// 计算文件的MD5值 - /// - /// 文件路径 - /// MD5哈希值(小写十六进制字符串) - private string CalculateFileMD5(string filePath) + private string CalculateMD5(byte[] data) { using (var md5 = MD5.Create()) - using (var stream = File.OpenRead(filePath)) { - // 计算文件流的哈希值 - byte[] hashBytes = md5.ComputeHash(stream); - // 将字节数组转换为十六进制字符串并返回 + byte[] hashBytes = md5.ComputeHash(data); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } - - /// - /// 通过多个DNS over HTTPS服务器查询TXT记录 - /// - /// 查询到的TXT记录内容 - private static async Task QueryDnsAsync() - { - // DNS over HTTPS服务器列表 - var dohServers = new List - { - "https://cloudflare-dns.com/dns-query", - "https://dns.cloudflare.com/dns-query", - "https://1.1.1.1/dns-query", - "https://1.0.0.1/dns-query", - "https://dns.google/resolve", - "https://sm2.doh.pub/dns-query", - "https://doh.pub/dns-query", - "https://dns.alidns.com/resolve", - "https://223.5.5.5/resolve", - "https://223.6.6.6/resolve", - "https://doh.360.cn/resolve" - }; - - using (var httpClient = new HttpClient()) - { - // 创建取消令牌源,用于取消其他任务 - var cts = new CancellationTokenSource(); - var tasks = new List>(); - - // 并行查询所有DNS服务器 - foreach (var server in dohServers) - { - tasks.Add(QueryDnsServer(httpClient, server, cts.Token)); - } - - // 等待任意一个任务完成 - var completedTask = await Task.WhenAny(tasks); - // 取消其他正在进行的任务 - cts.Cancel(); - // 返回第一个完成的任务结果 - return await completedTask; - } - } - - /// - /// 查询指定DNS over HTTPS服务器的TXT记录 - /// - /// HTTP客户端 - /// DNS服务器URL - /// 取消令牌 - /// 查询到的TXT记录内容,失败则返回空字符串 - private static async Task QueryDnsServer(HttpClient client, string server, CancellationToken token) - { - try - { - // 构建DNS查询URL - var url = $"{server}?name={DnsQueryDomain}&type=TXT"; - // 发送HTTP请求获取响应 - var response = await client.GetStringAsync(url); - // 解析JSON响应 - var jsonResponse = JObject.Parse(response); - - // 遍历Answer部分查找TXT记录 - foreach (var record in jsonResponse["Answer"]) - { - string txtRecord = record["data"]?.ToString(); - // 如果找到有效的TXT记录,则返回 - if (!string.IsNullOrEmpty(txtRecord)) - { - return txtRecord; - } - } - return string.Empty; - } - catch - { - // 查询失败时返回空字符串 - return string.Empty; - } - } } } \ No newline at end of file diff --git a/packages.config b/packages.config index 1763c0c..86450b3 100644 --- a/packages.config +++ b/packages.config @@ -1,5 +1,6 @@  +