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; // 蓝奏库 using LanzouCloudSolve; using System.Collections.Concurrent; 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 = "suwin-oss"; // 阿里云OSS访问密钥ID private const string OssAccessKeyId = "LTAI5tCwRcL5LUgyHB2j7w82"; // 阿里云OSS访问密钥Secret private const string OssAccessKeySecret = "7ClQns3wz6psmIp9T2OfuEn3tpzrCK"; // 123盘配置 // 注意:如需修改123盘路径,只需修改 OneDrivePath 常量即可,主备域名会自动使用新路径 // 123盘鉴权密钥 private const string OneDriveAuthKey = "ZhwG3LxOtGJwM3ym"; // 123盘UID private const string OneDriveUid = "1850250683"; // 123盘路径(不包含域名)- 修改此处即可同时生效于主备域名 private const string OneDrivePath = "/1850250683/SuWin"; // 123盘主域名 private const string OneDriveMainDomain = "vip.123pan.cn"; // 123盘备用域名 private const string OneDriveBackupDomain = "vip.123yx.com"; // 123盘鉴权有效时间(秒) private const int OneDriveAuthTimeout = 600; // 10分钟 // 网络优化: 静态HttpClient实例,避免套接字耗尽 private static readonly HttpClient _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(20) }; // OSS客户端:仅初始化一次,避免频繁创建导致内存占用过高 private static readonly OssClient _ossClient = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret); // 网络优化: 备用DNS服务列表,提高解析成功率 private static readonly List _dnsServers = new List { "223.5.5.5", "119.29.29.29" }; // 用于存储下载的文件数据 private Dictionary _downloadedFiles = new Dictionary(); // 已完成的下载数量 private int _completedCount = 0; // 总下载数量 private int _totalCount = 0; // 临时文件夹路径 private string _tempDirectory; /// /// 初始化窗体 /// public Update() { InitializeComponent(); ConfigureProgressBar(); InitializeTempDirectory(); // 注册应用程序退出时清理临时文件 Application.ApplicationExit += (s, e) => CleanupTempDirectory(); } /// /// 配置进度条的基本属性 /// 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(); try { await UpdateFile(); } finally { // 确保在更新完成后清理临时文件 CleanupTempDirectory(); } } /// /// 将窗体定位到屏幕右下角 /// 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 { CleanupNewFiles(); UpdateStatus("下载在线MD5文件并读取..."); string tempFilePath = Path.Combine(_tempDirectory, Md5File); // 使用新的带123盘的下载方法 if (!await DownloadMd5FileWithFallback(Md5File, tempFilePath)) { UpdateStatus("下载在线MD5文件失败"); await Task.Delay(3000); this.Close(); return; } var onlineData = ReadOnlineMd5File(tempFilePath); 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(); } } /// /// 清理旧的更新文件(.new后缀文件) /// private void CleanupNewFiles() { try { string startupPath = Application.StartupPath; var newFiles = Directory.GetFiles(startupPath, "*.new", SearchOption.AllDirectories); if (newFiles.Length > 0) { UpdateStatus("正在清理旧的更新文件..."); foreach (var file in newFiles) { try { File.Delete(file); } catch (Exception ex) { Debug.WriteLine($"无法删除旧的更新文件: {file} - {ex.Message}"); } } } } catch (Exception ex) { Debug.WriteLine($"清理旧的更新文件时出错: {ex.Message}"); } } /// /// 读取在线MD5文件并解析JSON内容 /// /// MD5文件路径 /// 版本号、MD5值和数据对象 private (string Version, string Md5, JObject Data) ReadOnlineMd5File(string filePath) { try { using (var reader = new StreamReader(filePath)) { 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); } } /// /// 验证在线数据的有效性 /// /// 版本号 /// MD5值 /// 数据对象 /// 数据是否有效 private bool ValidateOnlineData(string version, string md5, JObject data) { if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null) { UpdateStatus("在线MD5文件无效"); return false; } return true; } /// /// 比较本地和在线MD5数据,找出需要更新的文件 /// /// 在线数据 /// 当前路径 /// 需要更新的文件列表 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; // 始终使用 Path.Combine 来构造路径,确保跨平台和正确性 string relativePath = string.IsNullOrEmpty(currentPath) ? key : Path.Combine(currentPath, key); string localFullPath = Path.Combine(Application.StartupPath, relativePath); if (onlineValue.Type == JTokenType.String) { string expectedMd5 = onlineValue.ToString(); if (!File.Exists(localFullPath)) { if (!differences.ContainsKey(relativePath)) { differences[relativePath] = expectedMd5; } } else { string localMd5 = CalculateMD5(File.ReadAllBytes(localFullPath)); if (localMd5.Equals(expectedMd5, StringComparison.OrdinalIgnoreCase)) { // MD5一致,文件无需更新 } else { if (!differences.ContainsKey(relativePath)) { differences[relativePath] = expectedMd5; } } } } else if (onlineValue.Type == JTokenType.Object) { var subDifferences = CompareMd5Data((JObject)onlineValue, relativePath); foreach (var diff in subDifferences) { if (!differences.ContainsKey(diff.Key)) { differences[diff.Key] = diff.Value; } } } } return differences; } /// /// 下载并验证文件列表 /// /// 需要下载的文件列表 private async Task DownloadAndVerifyFiles(Dictionary fileList) { _totalCount = fileList.Count; _completedCount = 0; _downloadedFiles.Clear(); var failedFiles = new ConcurrentDictionary(); await PerformDownloads(fileList, failedFiles); if (!failedFiles.IsEmpty) { UpdateStatus($"有 {failedFiles.Count} 个文件下载失败,开始重试..."); var stillFailing = await RetryFailedFilesAsync(new Dictionary(failedFiles)); if (stillFailing.Any()) { // 即使重试后仍然有失败的,也继续处理已成功的 UpdateStatus($"重试后仍有 {stillFailing.Count} 个文件下载失败。"); } } if (_completedCount == 0 && fileList.Count > 0) { throw new Exception("所有文件下载失败。"); } VerifyAndSaveAllFiles(); } /// /// 执行并发下载任务 /// /// 要下载的文件列表 /// 下载失败的文件集合 private async Task PerformDownloads(IDictionary filesToDownload, ConcurrentDictionary failedDownloads) { var semaphore = new SemaphoreSlim(4); // 限制4个并发下载 var downloadTasks = filesToDownload.Select(async (file) => { await semaphore.WaitAsync(); try { bool success = await AttemptDownloadAsync(file.Key, file.Value); if (success) { lock (_downloadedFiles) { _downloadedFiles[file.Key] = (null, file.Value); } Interlocked.Increment(ref _completedCount); // 实时更新进度(0-95%用于下载) int progress = (int)((double)_completedCount / _totalCount * 95); UpdateProgressValue(progress); // 更新下载完成状态:文件名称 当前进度/总进度 UpdateStatus($"{Path.GetFileName(file.Key)} {_completedCount}/{_totalCount}"); } else { failedDownloads.TryAdd(file.Key, file.Value); } } finally { semaphore.Release(); } }); await Task.WhenAll(downloadTasks); } /// /// 尝试下载单个文件(123盘主要,OSS备用) /// /// 文件路径 /// 期望的MD5值 /// 下载是否成功 private async Task AttemptDownloadAsync(string filePath, string expectedMd5) { string tempFilePath = Path.Combine(_tempDirectory, filePath); string fileName = Path.GetFileName(filePath); try { // 1. 首先尝试从123盘下载 UpdateStatus($"{fileName} {_completedCount + 1}/{_totalCount}"); if (await DownloadFileFromOneDrive(filePath, expectedMd5, tempFilePath)) { return true; } // 2. 如果123盘下载失败,使用阿里云OSS作为备用 UpdateStatus($"{fileName} {_completedCount + 1}/{_totalCount}"); string ossKey = $"File/{expectedMd5}"; var obj = _ossClient.GetObject(OssBucketName, ossKey); string tempDir = Path.GetDirectoryName(tempFilePath); if (!Directory.Exists(tempDir)) { Directory.CreateDirectory(tempDir); } using (var fileStream = File.Create(tempFilePath)) { await obj.Content.CopyToAsync(fileStream); } return true; } catch (Exception ex) when (ex is OssException || ex is WebException) { UpdateStatus($"{fileName} {_completedCount + 1}/{_totalCount}"); string ossKey = $"File/{expectedMd5}"; return await DownloadFileWithFallback(ossKey, tempFilePath); } catch (Exception ex) { UpdateStatus($"下载异常: {fileName} - {ex.Message}"); return false; } } /// /// 从123盘下载在线MD5文件(主域名+备用域名) /// /// 123盘基础链接地址 /// 要下载的文件名 /// 本地保存路径 /// 下载是否成功 private async Task DownloadFromOneDrive(string baseUrl, string fileName, string localPath) { // 1. 首先尝试主域名 try { UpdateStatus($"正在从123盘下载: {fileName}"); // 生成123盘鉴权URL(主域名) string authUrl = GenerateAuthUrl($"http://{OneDriveMainDomain}{OneDrivePath}", fileName); UpdateStatus($"使用主域名下载文件..."); // 创建HTTP请求 var request = new HttpRequestMessage(HttpMethod.Get, authUrl); request.Headers.Add("User-Agent", "CheckDownload/1.0"); // 发送请求并获取响应 var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); // 读取文件数据 byte[] fileData = await response.Content.ReadAsByteArrayAsync(); // 确保本地目录存在 string localDir = Path.GetDirectoryName(localPath); if (!Directory.Exists(localDir)) { Directory.CreateDirectory(localDir); } // 保存文件到本地 File.WriteAllBytes(localPath, fileData); UpdateStatus($"123盘主域名下载成功: {fileName}"); return true; } catch (Exception ex) { UpdateStatus($"123盘主域名下载失败,尝试备用域名: {fileName}"); Debug.WriteLine($"123盘主域名下载异常: {ex}"); } // 2. 如果主域名失败,尝试备用域名 try { UpdateStatus($"正在从123盘备用域名下载: {fileName}"); // 生成123盘鉴权URL(备用域名) string authUrl = GenerateAuthUrl($"http://{OneDriveBackupDomain}{OneDrivePath}", fileName); UpdateStatus($"使用备用域名下载文件..."); // 创建HTTP请求 var request = new HttpRequestMessage(HttpMethod.Get, authUrl); request.Headers.Add("User-Agent", "CheckDownload/1.0"); // 发送请求并获取响应 var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); // 读取文件数据 byte[] fileData = await response.Content.ReadAsByteArrayAsync(); // 确保本地目录存在 string localDir = Path.GetDirectoryName(localPath); if (!Directory.Exists(localDir)) { Directory.CreateDirectory(localDir); } // 保存文件到本地 File.WriteAllBytes(localPath, fileData); UpdateStatus($"123盘备用域名下载成功: {fileName}"); return true; } catch (Exception ex) { UpdateStatus($"123盘备用域名下载失败: {fileName} - {ex.Message}"); Debug.WriteLine($"123盘备用域名下载异常: {ex}"); return false; } } /// /// 从123盘下载单个文件(主域名+备用域名) /// /// 文件相对路径 /// 期望的MD5值 /// 本地保存路径 /// 下载是否成功 private async Task DownloadFileFromOneDrive(string filePath, string expectedMd5, string localPath) { // 在123盘中,文件是通过MD5值存储的,路径为 /File/{MD5值} string fileName = $"File/{expectedMd5}"; // 1. 首先尝试主域名 try { // 生成123盘鉴权URL(主域名) string authUrl = GenerateAuthUrl($"http://{OneDriveMainDomain}{OneDrivePath}", fileName); // 创建HTTP请求 var request = new HttpRequestMessage(HttpMethod.Get, authUrl); request.Headers.Add("User-Agent", "CheckDownload/1.0"); // 发送请求并获取响应 var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); // 读取文件数据 byte[] fileData = await response.Content.ReadAsByteArrayAsync(); // 确保本地目录存在 string localDir = Path.GetDirectoryName(localPath); if (!Directory.Exists(localDir)) { Directory.CreateDirectory(localDir); } // 保存文件到本地 File.WriteAllBytes(localPath, fileData); return true; } catch (Exception ex) { Debug.WriteLine($"123盘主域名下载文件失败: {filePath} - {ex.Message}"); } // 2. 如果主域名失败,尝试备用域名 try { // 生成123盘鉴权URL(备用域名) string authUrl = GenerateAuthUrl($"http://{OneDriveBackupDomain}{OneDrivePath}", fileName); // 创建HTTP请求 var request = new HttpRequestMessage(HttpMethod.Get, authUrl); request.Headers.Add("User-Agent", "CheckDownload/1.0"); // 发送请求并获取响应 var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); // 读取文件数据 byte[] fileData = await response.Content.ReadAsByteArrayAsync(); // 确保本地目录存在 string localDir = Path.GetDirectoryName(localPath); if (!Directory.Exists(localDir)) { Directory.CreateDirectory(localDir); } // 保存文件到本地 File.WriteAllBytes(localPath, fileData); return true; } catch (Exception ex) { Debug.WriteLine($"123盘备用域名下载文件失败: {filePath} - {ex.Message}"); return false; } } /// /// 重试下载失败的文件 /// /// 失败的文件列表 /// 仍然失败的文件列表 private async Task> RetryFailedFilesAsync(Dictionary failedFiles) { const int maxRetries = 2; var filesToRetry = new Dictionary(failedFiles); for (int i = 0; i < maxRetries && filesToRetry.Any(); i++) { UpdateStatus($"第 {i + 1} 次重试,剩余 {filesToRetry.Count} 个文件..."); var failedThisRound = new ConcurrentDictionary(); // BUG修复: 此处不应清空重试队列 await PerformDownloads(filesToRetry, failedThisRound); filesToRetry = new Dictionary(failedThisRound); if (filesToRetry.Any() && i < maxRetries - 1) { UpdateStatus($"等待 3 秒后进行下一次重试..."); await Task.Delay(3000); } } return filesToRetry; } /// /// 通过DNS服务器解析域名获取IP地址列表 /// /// 要解析的域名 /// IP地址列表 private async Task> GetIpAddressesForDomain(string domain) { foreach (var dnsServer in _dnsServers) { try { var requestUri = $"https://{dnsServer}/resolve?name={domain}&type=1&short=1"; var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("User-Agent", "CheckDownload/1.0"); request.Headers.Host = dnsServer; var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); var ips = JsonConvert.DeserializeObject>(responseBody); if (ips != null && ips.Any()) { UpdateStatus($"通过 {dnsServer} 成功解析域名 {domain}"); return ips; } } catch (Exception ex) { UpdateStatus($"通过 {dnsServer} 解析域名失败,尝试下一个..."); await Task.Delay(500); // 短暂延迟后重试 } } UpdateStatus($"所有DNS服务器均无法解析域名: {domain}"); return new List(); } /// /// 验证并保存所有下载的文件 /// private void VerifyAndSaveAllFiles() { UpdateStatus("正在校验文件..."); var failedFiles = new List(); var filesForScripting = new List<(string original, string newFile)>(); int processedCount = 0; int totalFiles = _downloadedFiles.Count; foreach (var item in _downloadedFiles) { string relativePath = item.Key; string expectedMd5 = item.Value.ExpectedMd5; string tempFilePath = Path.Combine(_tempDirectory, relativePath); try { processedCount++; UpdateStatus($"正在校验和保存文件 ({processedCount}/{totalFiles}): {relativePath}"); // 计算临时文件的MD5 string actualMd5 = CalculateMD5(File.ReadAllBytes(tempFilePath)); if (actualMd5 != expectedMd5.ToLower()) { throw new Exception($"MD5校验失败 (期望: {expectedMd5}, 实际: {actualMd5})"); } string localPath = Path.Combine(Application.StartupPath, relativePath); string localDir = Path.GetDirectoryName(localPath); // 确保目标目录存在 if (!Directory.Exists(localDir)) { Directory.CreateDirectory(localDir); } // 尝试移动文件,如果文件被占用则尝试解锁 if (!TryMoveFile(tempFilePath, localPath)) { // 如果无法移动文件,则使用备用文件名 string backupPath = localPath + ".new"; File.Move(tempFilePath, backupPath); filesForScripting.Add((localPath, backupPath)); UpdateStatus($"文件 {relativePath} 正在被占用,将在程序重启后更新"); } else { 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); } } if (filesForScripting.Any()) { CreateReplaceScriptForAll(filesForScripting); } foreach (var failedFile in failedFiles) { _downloadedFiles.Remove(failedFile); } if (failedFiles.Count > 0) { throw new Exception($"{failedFiles.Count}个文件校验失败"); } else { UpdateStatus("所有文件校验和保存成功"); UpdateProgressValue(100); } } /// /// 尝试移动文件,如果文件被占用则尝试解锁 /// /// 源文件路径 /// 目标文件路径 /// 移动是否成功 private bool TryMoveFile(string sourcePath, string targetPath) { try { // 直接尝试移动文件 File.Move(sourcePath, targetPath); return true; } catch (IOException) { // 文件被占用,尝试解锁 UpdateStatus($"文件被占用,尝试解锁..."); try { // 等待一段时间,让文件可能被释放 Thread.Sleep(1000); // 再次尝试移动文件 File.Move(sourcePath, targetPath); UpdateStatus($"文件解锁成功并已更新"); return true; } catch { // 如果仍然无法移动,返回失败 UpdateStatus($"文件仍被占用,无法移动"); return false; } } catch (Exception ex) { UpdateStatus($"移动文件时发生错误: {ex.Message}"); return false; } } /// /// 为所有需要替换的文件创建批处理脚本 /// /// 需要替换的文件列表 private void CreateReplaceScriptForAll(List<(string original, string newFile)> files) { if (!files.Any()) return; try { string batchFilePath = Path.Combine(Application.StartupPath, "update_files.bat"); string processId = Process.GetCurrentProcess().Id.ToString(); var batchContent = new StringBuilder(); batchContent.AppendLine("@echo off"); batchContent.AppendLine("chcp 65001 > nul"); // 确保正确处理UTF-8路径 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"); foreach (var file in files) { batchContent.AppendLine($"del \"{file.original}\" /f /q"); batchContent.AppendLine($"move \"{file.newFile}\" \"{file.original}\""); } batchContent.AppendLine("del \"%~f0\" /f /q"); File.WriteAllText(batchFilePath, batchContent.ToString(), new UTF8Encoding(false)); var 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 void InitializeTempDirectory() { try { // 获取用户临时目录 string userTempPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Temp", "CheckDownload" ); // 创建临时目录 _tempDirectory = CreateTempDirectory(userTempPath); } catch (Exception ex) { UpdateStatus($"初始化临时目录失败: {ex.Message}"); } } /// /// 创建带时间戳的临时目录 /// /// 基础路径 /// 临时目录路径 private string CreateTempDirectory(string basePath) { try { // 确保基础目录存在 if (!Directory.Exists(basePath)) { Directory.CreateDirectory(basePath); } // 创建带时间戳的临时目录 string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string tempDir = Path.Combine(basePath, $"temp_{timestamp}"); if (!Directory.Exists(tempDir)) { Directory.CreateDirectory(tempDir); } return tempDir; } catch (Exception ex) { UpdateStatus($"创建临时目录失败: {ex.Message}"); throw; } } /// /// 清理临时目录和历史遗留文件 /// private void CleanupTempDirectory() { try { // 只删除当前实例创建的临时目录 if (!string.IsNullOrEmpty(_tempDirectory) && Directory.Exists(_tempDirectory)) { Directory.Delete(_tempDirectory, true); Debug.WriteLine($"已清理临时目录: {_tempDirectory}"); } // 同时清理历史遗留的空目录或非常旧的目录 string baseTempPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Temp", "CheckDownload" ); if (!Directory.Exists(baseTempPath)) return; foreach (var dir in Directory.GetDirectories(baseTempPath)) { try { // 删除超过7天的旧临时文件夹 if ((DateTime.UtcNow - new DirectoryInfo(dir).CreationTimeUtc).TotalDays > 7) { Directory.Delete(dir, true); Debug.WriteLine($"已清理旧的临时目录: {dir}"); } } catch (Exception ex) { // 忽略删除失败的,可能是其他实例正在使用 Debug.WriteLine($"删除旧临时目录失败: {dir} - {ex.Message}"); } } } catch (Exception ex) { Debug.WriteLine($"清理临时目录时发生错误: {ex.Message}"); } } /// /// 计算字符串的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 CalculateMD5(byte[] data) { using (var md5 = MD5.Create()) { byte[] hashBytes = md5.ComputeHash(data); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } /// /// 通用的带备用方案的OSS文件下载方法 /// /// OSS对象键 /// 本地保存路径 /// 下载是否成功 private async Task DownloadFileWithFallback(string ossKey, string localPath) { // 1. 先尝试用 OSS SDK 下载 try { var obj = _ossClient.GetObject(OssBucketName, ossKey); string localDir = Path.GetDirectoryName(localPath); if (!Directory.Exists(localDir)) { Directory.CreateDirectory(localDir); } using (var fileStream = File.Create(localPath)) { obj.Content.CopyTo(fileStream); } return true; } catch (Exception ex) when (ex is OssException || ex is WebException || ex is IOException) { UpdateStatus($"主下载失败,尝试备用方案..."); } // 2. 备用方案:用 IP 下载(带签名URL) var domain = new Uri("https://" + OssEndpoint).Host; List ips = await GetIpAddressesForDomain(domain); if (ips == null || ips.Count == 0) { UpdateStatus($"无法获取IP地址"); return false; } // 用 SDK 生成带签名的 URL var req = new GeneratePresignedUriRequest(OssBucketName, ossKey, SignHttpMethod.Get) { Expiration = DateTime.Now.AddMinutes(10) }; var signedUrl = _ossClient.GeneratePresignedUri(req).ToString(); var signedUri = new Uri(signedUrl); foreach (var ip in ips) { try { // 替换URL中的host为IP var ipUrl = signedUrl.Replace(signedUri.Host, ip); // 网络优化: 使用静态HttpClient实例 var request = new HttpRequestMessage(HttpMethod.Get, ipUrl); request.Headers.Host = signedUri.Host; // Host头保持原域名 var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); byte[] fileData = await response.Content.ReadAsByteArrayAsync(); string localDir = Path.GetDirectoryName(localPath); if (!Directory.Exists(localDir)) { Directory.CreateDirectory(localDir); } File.WriteAllBytes(localPath, fileData); return true; } catch (Exception) { UpdateStatus($"备用方案用IP {ip} 下载失败"); } } return false; } /// /// 以系统时间为准根据传入的数字生成一个时间戳(秒) /// /// 要增加的秒数 /// Unix时间戳 private long GenerateTimestamp(int number) { return (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds + number; } /// /// 根据传入的数字生成一个UUID,不能包含中划线,且长度为32位 /// /// 用于生成UUID的数字 /// 32位UUID字符串 private string GenerateUUID(int number) { // 使用数字作为种子生成确定性UUID var random = new Random(number); var guid = new byte[16]; random.NextBytes(guid); // 设置版本号(4)和变体位 guid[7] = (byte)((guid[7] & 0x0F) | 0x40); // 版本4 guid[8] = (byte)((guid[8] & 0x3F) | 0x80); // 变体位 var resultGuid = new Guid(guid); return resultGuid.ToString("N").Replace("-", ""); } /// /// 通过MD5算法计算字符串的哈希值,由数字0~9和小写英文字母a~z组成,长度为32位 /// /// 输入字符串 /// 32位MD5哈希值 private string GenerateMD5(string input) { using (var md5 = MD5.Create()) { byte[] inputBytes = Encoding.UTF8.GetBytes(input); byte[] hashBytes = md5.ComputeHash(inputBytes); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } } /// /// 生成123云盘鉴权后的URL /// /// 原始URL /// 要下载的文件名 /// 鉴权后的URL private string GenerateAuthUrl(string url, string file) { try { // 使用配置的超时时间生成时间戳 long timestamp = GenerateTimestamp(OneDriveAuthTimeout); // 生成随机UUID string rand = GenerateUUID(16); // 通用URL解析:提取域名后的路径部分 string pathPart = ExtractPathFromUrl(url); // 构造完整的下载URL string fullUrl = $"{url}/{file}"; // 构造签名字符串:/路径部分/文件名-时间戳-随机数-UID-密钥 string signString = $"/{pathPart}/{file}-{timestamp}-{rand}-{OneDriveUid}-{OneDriveAuthKey}"; // 计算MD5签名 string signature = GenerateMD5(signString); // 构造鉴权后的URL:完整URL?auth_key=时间戳-随机数-UID-签名 string authUrl = $"{fullUrl}?auth_key={timestamp}-{rand}-{OneDriveUid}-{signature}"; // 调试信息(可选) Debug.WriteLine($"鉴权URL生成: {authUrl}"); Debug.WriteLine($"签名字符串: {signString}"); return authUrl; } catch (Exception ex) { UpdateStatus($"生成鉴权URL失败: {ex.Message}"); return $"{url}/{file}"; // 如果生成失败,返回原始URL } } /// /// 从URL中提取路径部分(域名后的部分) /// /// 完整URL /// 路径部分 private string ExtractPathFromUrl(string url) { string pathPart = ""; try { var uri = new Uri(url); // 获取路径部分,去掉开头的斜杠 pathPart = uri.AbsolutePath.TrimStart('/'); // 如果路径为空,尝试从完整URL中提取 if (string.IsNullOrEmpty(pathPart)) { string basePattern = $"{uri.Scheme}://{uri.Host}"; if (url.StartsWith(basePattern)) { pathPart = url.Substring(basePattern.Length).TrimStart('/'); } } } catch { // 如果URI解析失败,使用简单的字符串处理 int protocolIndex = url.IndexOf("://"); if (protocolIndex > 0) { int domainEndIndex = url.IndexOf('/', protocolIndex + 3); if (domainEndIndex > 0) { pathPart = url.Substring(domainEndIndex + 1); } } } return pathPart; } /// /// 带备用方案的在线MD5文件下载方法(123盘主要,OSS备用) /// /// 要下载的文件名 /// 本地保存路径 /// 下载是否成功 private async Task DownloadMd5FileWithFallback(string fileName, string localPath) { try { // 1. 首先尝试从123盘下载 UpdateStatus("尝试从123盘下载MD5文件..."); if (await DownloadFromOneDrive($"http://{OneDriveMainDomain}{OneDrivePath}", fileName, localPath)) { return true; } // 2. 如果123盘下载失败,使用阿里云OSS作为备用 UpdateStatus("123盘下载失败,尝试阿里云OSS备用方案..."); return await DownloadFileWithFallback(fileName, localPath); } catch (Exception ex) { UpdateStatus($"所有下载方式都失败: {ex.Message}"); Debug.WriteLine($"下载MD5文件异常: {ex}"); return false; } } } }