using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.InteropServices; using System.Security.Cryptography; 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 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 _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; } private void UpdateProgressValue(int percentage) { if (percentage < 0) percentage = 0; if (percentage > 100) percentage = 100; if (this.InvokeRequired) { this.Invoke((MethodInvoker)delegate { Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum); Application.DoEvents(); }); } else { Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum); Application.DoEvents(); } } private void UpdateStatus(string message) { 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 = 4; int currentStep = 0; UpdateStatus("下载在线MD5文件并读取..."); OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret); var onlineData = ReadOnlineMd5File(client, OssBucketName, Md5File); if (!ValidateOnlineData(onlineData.Version, onlineData.Md5, onlineData.Data)) { UpdateStatus("在线MD5文件无效"); await Task.Delay(3000); this.Close(); return; } UpdateStatus("比较本地和在线MD5文件..."); var compareResult = CompareMd5Data(onlineData.Data); if (compareResult.Count == 0) { UpdateStatus("所有文件都是最新的,无需更新"); UpdateProgressValue(100); await Task.Delay(2000); this.Close(); return; } UpdateStatus("下载并验证文件..."); _totalCount = compareResult.Count; await DownloadAndVerifyFiles(compareResult); UpdateProgressValue(100); UpdateStatus("更新完成"); await Task.Delay(1000); this.Close(); } catch (Exception ex) { UpdateStatus($"更新失败: {ex.Message}"); await Task.Delay(3000); this.Close(); } } private (string Version, string Md5, JObject Data) ReadOnlineMd5File(OssClient client, string bucket, string key) { try { 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 OssException || ex is JsonException) { UpdateStatus($"读取在线MD5文件失败: {ex.Message}"); return (null, null, null); } } private bool ValidateOnlineData(string version, string md5, JObject data) { if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null) { UpdateStatus("在线MD5文件无效"); return false; } return true; } 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) { string expectedMd5 = onlineValue.ToString(); if (!File.Exists(fullPath)) { 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) { var subDifferences = CompareMd5Data((JObject)onlineValue, fullPath); foreach (var diff in subDifferences) { if (!differences.ContainsKey(diff.Key)) { differences[diff.Key] = diff.Value; } } } } return differences; } private async Task DownloadAndVerifyFiles(Dictionary fileList) { try { OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret); _totalCount = fileList.Count; _completedCount = 0; _downloadedFiles.Clear(); var options = new ParallelOptions { MaxDegreeOfParallelism = 4 }; await Task.Run(() => { Parallel.ForEach(fileList, options, (file, state) => { 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}"); } VerifyAndSaveAllFiles(); } catch (Exception ex) { UpdateStatus($"下载过程出错: {ex.Message}"); throw; } } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetProcessHandleCount(IntPtr hProcess, out uint pdwHandleCount); private void VerifyAndSaveAllFiles() { UpdateStatus("正在校验文件..."); var failedFiles = new List(); int processedCount = 0; int totalFiles = _downloadedFiles.Count; foreach (var item in _downloadedFiles) { string relativePath = item.Key; byte[] data = item.Value.Data; string expectedMd5 = item.Value.ExpectedMd5; try { processedCount++; UpdateStatus($"正在校验和保存文件 ({processedCount}/{totalFiles}): {relativePath}"); string actualMd5 = CalculateMD5(data); if (actualMd5 != expectedMd5.ToLower()) { throw new Exception($"MD5校验失败 (期望: {expectedMd5}, 实际: {actualMd5})"); } string localPath = Path.Combine(Application.StartupPath, relativePath); Directory.CreateDirectory(Path.GetDirectoryName(localPath)); // 尝试保存文件,如果文件被占用则尝试解锁 if (!TrySaveFile(localPath, data)) { // 如果无法解锁文件,则使用备用文件名保存 string backupPath = localPath + ".new"; File.WriteAllBytes(backupPath, data); // 创建一个批处理文件,在应用程序退出后替换文件 CreateReplaceScript(localPath, backupPath); UpdateStatus($"文件 {relativePath} 正在被占用,将在程序重启后更新"); } int percentage = 95 + (int)(processedCount * 5.0 / totalFiles); UpdateProgressValue(Math.Min(100, percentage)); } catch (Exception ex) { UpdateStatus($"文件校验失败: {relativePath} - {ex.Message}"); failedFiles.Add(relativePath); } } foreach (var failedFile in failedFiles) { _downloadedFiles.Remove(failedFile); } if (failedFiles.Count > 0) { throw new Exception($"{failedFiles.Count}个文件校验失败"); } else { UpdateStatus("所有文件校验和保存成功"); UpdateProgressValue(100); } } private bool TrySaveFile(string filePath, byte[] data) { try { // 直接尝试写入文件 File.WriteAllBytes(filePath, data); return true; } catch (IOException) { // 文件被占用,尝试解锁 UpdateStatus($"文件 {Path.GetFileName(filePath)} 被占用,尝试解锁..."); try { // 尝试找出占用文件的进程 var processes = Process.GetProcesses(); foreach (var process in processes) { try { // 跳过当前进程 if (process.Id == Process.GetCurrentProcess().Id) continue; // 检查进程是否可能占用了文件 string processName = process.ProcessName.ToLower(); if (processName.Contains("explorer") || processName.Contains("chrome") || processName.Contains("edge") || processName.Contains("firefox") || processName.Contains("iexplore") || processName.Contains("winword") || processName.Contains("excel") || processName.Contains("powerpnt") || processName.Contains("acrobat") || processName.Contains("reader")) { // 跳过常见的系统进程 continue; } // 尝试获取进程句柄数量,如果句柄数量较多,可能正在使用文件 uint handleCount = 0; if (GetProcessHandleCount(process.Handle, out handleCount) && handleCount > 0) { // 尝试关闭进程 UpdateStatus($"尝试关闭可能占用文件的进程: {process.ProcessName}"); process.CloseMainWindow(); // 等待进程关闭 if (!process.WaitForExit(1000)) { // 如果进程没有响应,尝试强制结束 process.Kill(); } } } catch { // 忽略处理单个进程时的错误 } finally { process.Dispose(); } } // 再次尝试写入文件 Thread.Sleep(500); // 等待一段时间确保文件已释放 File.WriteAllBytes(filePath, data); UpdateStatus($"文件 {Path.GetFileName(filePath)} 解锁成功并已更新"); return true; } catch { // 如果仍然无法写入,返回失败 return false; } } catch (Exception ex) { UpdateStatus($"保存文件时发生错误: {ex.Message}"); return false; } } private void CreateReplaceScript(string originalFile, string newFile) { try { string batchFilePath = Path.Combine(Application.StartupPath, "update_files.bat"); string processId = Process.GetCurrentProcess().Id.ToString(); string escapedOriginalFile = originalFile.Replace("\\", "\\\\"); string escapedNewFile = newFile.Replace("\\", "\\\\"); // 创建批处理文件内容 StringBuilder batchContent = new StringBuilder(); batchContent.AppendLine("@echo off"); batchContent.AppendLine(":check_process"); batchContent.AppendLine($"tasklist /FI \"PID eq {processId}\" 2>NUL | find /I \"{ processId }\" >NUL"); batchContent.AppendLine("if %ERRORLEVEL% == 0 ("); batchContent.AppendLine(" timeout /t 1 /nobreak > NUL"); batchContent.AppendLine(" goto check_process"); batchContent.AppendLine(")"); batchContent.AppendLine("timeout /t 2 /nobreak > NUL"); batchContent.AppendLine($"del \"{escapedOriginalFile}\" /f /q"); batchContent.AppendLine($"move \"{escapedNewFile}\" \"{escapedOriginalFile}\""); batchContent.AppendLine("del \"%~f0\" /f /q"); // 写入批处理文件 bool fileExists = File.Exists(batchFilePath); using (StreamWriter writer = new StreamWriter(batchFilePath, fileExists)) { if (!fileExists) { writer.Write(batchContent.ToString()); } else { // 如果文件已存在,只添加移动文件的命令 writer.WriteLine($"del \"{escapedOriginalFile}\" /f /q"); writer.WriteLine($"move \"{escapedNewFile}\" \"{escapedOriginalFile}\""); } } // 如果是第一次创建批处理文件,启动它 if (!fileExists) { ProcessStartInfo startInfo = new ProcessStartInfo { FileName = "cmd.exe", Arguments = $"/c start \"\" /min \"{batchFilePath}\"", CreateNoWindow = true, UseShellExecute = true, WindowStyle = ProcessWindowStyle.Hidden }; Process.Start(startInfo); } } catch (Exception ex) { UpdateStatus($"创建替换脚本时出错: {ex.Message}"); } } 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(); } } private string CalculateMD5(byte[] data) { using (var md5 = MD5.Create()) { byte[] hashBytes = md5.ComputeHash(data); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } } }