From 873601013fe32928f7e59397ac6e1af39fc58f26 Mon Sep 17 00:00:00 2001
From: dong <1278815766@qq.com>
Date: Thu, 26 Jun 2025 18:41:30 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=86=85=E5=AD=98=E5=8D=A0?=
=?UTF-8?q?=E7=94=A8=E8=BF=87=E5=A4=9A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CheckDownload.csproj | 4 +-
Form1.cs | 535 ++++++++++++++++++-------------------------
packages.config | 2 +-
3 files changed, 221 insertions(+), 320 deletions(-)
diff --git a/CheckDownload.csproj b/CheckDownload.csproj
index da5ff88..ec7963b 100644
--- a/CheckDownload.csproj
+++ b/CheckDownload.csproj
@@ -180,8 +180,8 @@
-
+
-
+
\ No newline at end of file
diff --git a/Form1.cs b/Form1.cs
index 2292008..cbc1095 100644
--- a/Form1.cs
+++ b/Form1.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Globalization;
@@ -20,9 +19,9 @@ using Aliyun.OSS;
using Aliyun.OSS.Common;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-// 蓝奏库
-using LanzouCloudSolve;
+
using System.Collections.Concurrent;
+using System.Diagnostics;
namespace CheckDownload
{
@@ -38,21 +37,18 @@ namespace CheckDownload
private const string OssAccessKeyId = "LTAI5tCwRcL5LUgyHB2j7w82";
// 阿里云OSS访问密钥Secret
private const string OssAccessKeySecret = "7ClQns3wz6psmIp9T2OfuEn3tpzrCK";
-
- // 123盘配置
- // 注意:如需修改123盘路径,只需修改 OneDrivePath 常量即可,主备域名会自动使用新路径
// 123盘鉴权密钥
- private const string OneDriveAuthKey = "ZhwG3LxOtGJwM3ym";
+ private const string OneDriveAuthKey = "6SwdpWdSJuJRSh";
// 123盘UID
- private const string OneDriveUid = "1850250683";
+ private const string OneDriveUid = "1826795402";
// 123盘路径(不包含域名)- 修改此处即可同时生效于主备域名
- private const string OneDrivePath = "/1850250683/SuWin";
+ private const string OneDrivePath = "/1826795402/KeyAuth";
// 123盘主域名
private const string OneDriveMainDomain = "vip.123pan.cn";
// 123盘备用域名
private const string OneDriveBackupDomain = "vip.123yx.com";
// 123盘鉴权有效时间(秒)
- private const int OneDriveAuthTimeout = 600; // 10分钟
+ private const int OneDriveAuthTimeout = 600;
// 网络优化: 静态HttpClient实例,避免套接字耗尽
private static readonly HttpClient _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(20) };
@@ -60,9 +56,10 @@ namespace CheckDownload
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 static readonly int MaxConcurrentDownloads = 2;
// 用于存储下载的文件数据
- private Dictionary _downloadedFiles = new Dictionary();
+ private Dictionary _downloadedFiles = new Dictionary();
// 已完成的下载数量
private int _completedCount = 0;
// 总下载数量
@@ -226,7 +223,7 @@ namespace CheckDownload
}
///
- /// 清理旧的更新文件(.new后缀文件)
+ /// 清理旧的更新文件,删除所有.new后缀的临时文件
///
private void CleanupNewFiles()
{
@@ -242,24 +239,22 @@ namespace CheckDownload
{
File.Delete(file);
}
- catch (Exception ex)
+ catch
{
- Debug.WriteLine($"无法删除旧的更新文件: {file} - {ex.Message}");
}
}
}
}
- catch (Exception ex)
+ catch
{
- Debug.WriteLine($"清理旧的更新文件时出错: {ex.Message}");
}
}
///
- /// 读取在线MD5文件并解析JSON内容
+ /// 读取在线MD5文件并解析JSON内容,提取版本信息和文件清单数据
///
- /// MD5文件路径
- /// 版本号、MD5值和数据对象
+ /// MD5文件的本地路径
+ /// 包含版本号、MD5值和文件数据的元组
private (string Version, string Md5, JObject Data) ReadOnlineMd5File(string filePath)
{
try
@@ -282,12 +277,12 @@ namespace CheckDownload
}
///
- /// 验证在线数据的有效性
+ /// 验证在线MD5数据的完整性和有效性
///
- /// 版本号
- /// MD5值
- /// 数据对象
- /// 数据是否有效
+ /// 版本号字符串
+ /// 文件的MD5哈希值
+ /// 包含文件信息的JSON对象
+ /// 如果所有必需字段都有效则返回true,否则返回false
private bool ValidateOnlineData(string version, string md5, JObject data)
{
if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null)
@@ -299,11 +294,11 @@ namespace CheckDownload
}
///
- /// 比较本地和在线MD5数据,找出需要更新的文件
+ /// 递归比较本地文件和在线MD5数据,识别需要更新的文件
///
- /// 在线数据
- /// 当前路径
- /// 需要更新的文件列表
+ /// 在线文件清单的JSON数据
+ /// 当前递归路径,用于构建完整的文件路径
+ /// 包含需要更新的文件路径和对应MD5值的字典
private Dictionary CompareMd5Data(JObject onlineData, string currentPath = "")
{
var differences = new Dictionary();
@@ -313,7 +308,6 @@ namespace CheckDownload
string key = onlineProperty.Name;
JToken onlineValue = onlineProperty.Value;
- // 始终使用 Path.Combine 来构造路径,确保跨平台和正确性
string relativePath = string.IsNullOrEmpty(currentPath) ? key : Path.Combine(currentPath, key);
string localFullPath = Path.Combine(_baseDirectory, relativePath);
@@ -329,12 +323,8 @@ namespace CheckDownload
}
else
{
- string localMd5 = CalculateMD5(File.ReadAllBytes(localFullPath));
- if (localMd5.Equals(expectedMd5, StringComparison.OrdinalIgnoreCase))
- {
- // MD5一致,文件无需更新
- }
- else
+ string localMd5 = CalculateMD5FromFile(localFullPath);
+ if (!localMd5.Equals(expectedMd5, StringComparison.OrdinalIgnoreCase))
{
if (!differences.ContainsKey(relativePath))
{
@@ -359,9 +349,9 @@ namespace CheckDownload
}
///
- /// 下载并验证文件列表
+ /// 批量下载文件列表并进行MD5验证,支持重试机制
///
- /// 需要下载的文件列表
+ /// 包含文件路径和期望MD5值的字典
private async Task DownloadAndVerifyFiles(Dictionary fileList)
{
_totalCount = fileList.Count;
@@ -377,7 +367,6 @@ namespace CheckDownload
var stillFailing = await RetryFailedFilesAsync(new Dictionary(failedFiles));
if (stillFailing.Any())
{
- // 即使重试后仍然有失败的,也继续处理已成功的
UpdateStatus($"重试后仍有 {stillFailing.Count} 个文件下载失败。");
}
}
@@ -387,17 +376,17 @@ namespace CheckDownload
throw new Exception("所有文件下载失败。");
}
- VerifyAndSaveAllFiles();
+ await VerifyAndSaveAllFiles();
}
///
- /// 执行并发下载任务
+ /// 使用信号量控制并发数量,执行多个文件的并发下载任务
///
- /// 要下载的文件列表
- /// 下载失败的文件集合
+ /// 包含文件路径和MD5值的下载任务字典
+ /// 用于收集下载失败文件的线程安全集合
private async Task PerformDownloads(IDictionary filesToDownload, ConcurrentDictionary failedDownloads)
{
- var semaphore = new SemaphoreSlim(4); // 限制4个并发下载
+ var semaphore = new SemaphoreSlim(MaxConcurrentDownloads);
var downloadTasks = filesToDownload.Select(async (file) =>
{
await semaphore.WaitAsync();
@@ -408,15 +397,13 @@ namespace CheckDownload
{
lock (_downloadedFiles)
{
- _downloadedFiles[file.Key] = (null, file.Value);
+ _downloadedFiles[file.Key] = 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
@@ -433,11 +420,11 @@ namespace CheckDownload
}
///
- /// 尝试下载单个文件(123盘主要,OSS备用),支持断点续传
+ /// 尝试从多个数据源下载单个文件,优先使用123盘,OSS作为备用
///
- /// 文件路径
- /// 期望的MD5值
- /// 下载是否成功
+ /// 文件的相对路径
+ /// 文件的期望MD5值,用于验证完整性
+ /// 下载成功返回true,否则返回false
private async Task AttemptDownloadAsync(string filePath, string expectedMd5)
{
string tempFilePath = Path.Combine(_tempDirectory, filePath);
@@ -445,20 +432,17 @@ namespace CheckDownload
try
{
- // 检查临时文件是否已存在且完整
if (await CheckExistingTempFile(tempFilePath, expectedMd5, fileName))
{
- return true; // 文件已存在且完整,跳过下载
+ return true;
}
- // 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}";
@@ -490,38 +474,35 @@ namespace CheckDownload
}
///
- /// 检查现有临时文件是否存在且完整
+ /// 检查临时目录中是否已存在完整的文件,通过MD5验证文件完整性
///
- /// 临时文件路径
- /// 期望的MD5值
- /// 文件名(用于显示)
- /// 文件是否存在且完整
+ /// 临时文件的完整路径
+ /// 文件的期望MD5哈希值
+ /// 文件名,用于状态显示
+ /// 如果文件存在且MD5匹配返回true,否则返回false
private async Task CheckExistingTempFile(string tempFilePath, string expectedMd5, string fileName)
{
try
{
- // 检查文件是否存在
if (!File.Exists(tempFilePath))
{
- return false; // 文件不存在,需要下载
+ return false;
}
UpdateStatus($"检查已存在的临时文件: {fileName}");
- // 异步计算文件MD5
- string actualMd5 = await Task.Run(() => CalculateMD5(File.ReadAllBytes(tempFilePath)));
+ string actualMd5 = await Task.Run(() => CalculateMD5FromFile(tempFilePath));
if (actualMd5.Equals(expectedMd5, StringComparison.OrdinalIgnoreCase))
{
UpdateStatus($"临时文件完整,跳过下载: {fileName}");
- return true; // 文件完整,跳过下载
+ return true;
}
else
{
UpdateStatus($"临时文件不完整,重新下载: {fileName}");
- // 删除不完整的文件
File.Delete(tempFilePath);
- return false; // 文件不完整,需要重新下载
+ return false;
}
}
catch (Exception ex)
@@ -529,7 +510,6 @@ namespace CheckDownload
UpdateStatus($"检查临时文件时出错,将重新下载: {fileName} - {ex.Message}");
try
{
- // 如果出错,尝试删除可能损坏的文件
if (File.Exists(tempFilePath))
{
File.Delete(tempFilePath);
@@ -537,190 +517,168 @@ namespace CheckDownload
}
catch
{
- // 忽略删除失败的错误
}
- return false; // 出错时重新下载
+ return false;
}
}
///
- /// 从123盘下载在线MD5文件(主域名+备用域名)
+ /// 从123盘下载文件,先尝试主域名,失败后自动切换到备用域名
///
- /// 123盘基础链接地址
+ /// 123盘的基础URL地址
/// 要下载的文件名
- /// 本地保存路径
- /// 下载是否成功
+ /// 文件保存的本地路径
+ /// 下载成功返回true,两个域名都失败则返回false
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))
+ using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{
- Directory.CreateDirectory(localDir);
+ response.EnsureSuccessStatusCode();
+ string localDir = Path.GetDirectoryName(localPath);
+ if (!Directory.Exists(localDir))
+ {
+ Directory.CreateDirectory(localDir);
+ }
+
+ using (var remote = await response.Content.ReadAsStreamAsync())
+ using (var localFile = File.Create(localPath))
+ {
+ await remote.CopyToAsync(localFile);
+ }
+
+ UpdateStatus($"123盘主域名下载成功: {fileName}");
+ return true;
}
-
- // 保存文件到本地
- 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))
+ using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{
- Directory.CreateDirectory(localDir);
+ response.EnsureSuccessStatusCode();
+ string localDir = Path.GetDirectoryName(localPath);
+ if (!Directory.Exists(localDir))
+ {
+ Directory.CreateDirectory(localDir);
+ }
+
+ using (var remote = await response.Content.ReadAsStreamAsync())
+ using (var localFile = File.Create(localPath))
+ {
+ await remote.CopyToAsync(localFile);
+ }
+
+ UpdateStatus($"123盘备用域名下载成功: {fileName}");
+ return true;
}
-
- // 保存文件到本地
- 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从123盘下载文件,使用主备域名双重保障
///
- /// 文件相对路径
- /// 期望的MD5值
- /// 本地保存路径
- /// 下载是否成功
+ /// 文件的相对路径(仅用于日志显示)
+ /// 文件的MD5值,用于构建123盘存储路径
+ /// 文件保存的本地完整路径
+ /// 下载成功返回true,两个域名都失败则返回false
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))
+ using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{
- Directory.CreateDirectory(localDir);
+ response.EnsureSuccessStatusCode();
+ string localDir = Path.GetDirectoryName(localPath);
+ if (!Directory.Exists(localDir))
+ {
+ Directory.CreateDirectory(localDir);
+ }
+
+ using (var remote = await response.Content.ReadAsStreamAsync())
+ using (var localFile = File.Create(localPath))
+ {
+ await remote.CopyToAsync(localFile);
+ }
+
+ return true;
}
-
- // 保存文件到本地
- 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))
+ using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{
- Directory.CreateDirectory(localDir);
+ response.EnsureSuccessStatusCode();
+ string localDir = Path.GetDirectoryName(localPath);
+ if (!Directory.Exists(localDir))
+ {
+ Directory.CreateDirectory(localDir);
+ }
+
+ using (var remote = await response.Content.ReadAsStreamAsync())
+ using (var localFile = File.Create(localPath))
+ {
+ await remote.CopyToAsync(localFile);
+ }
+
+ return true;
}
-
- // 保存文件到本地
- File.WriteAllBytes(localPath, fileData);
-
- return true;
}
catch (Exception ex)
{
- Debug.WriteLine($"123盘备用域名下载文件失败: {filePath} - {ex.Message}");
return false;
}
}
///
- /// 重试下载失败的文件
+ /// 对下载失败的文件进行重试,最多重试指定次数
///
- /// 失败的文件列表
- /// 仍然失败的文件列表
+ /// 包含失败文件路径和MD5值的字典
+ /// 重试后仍然失败的文件字典
private async Task> RetryFailedFilesAsync(Dictionary failedFiles)
{
const int maxRetries = 2;
@@ -731,7 +689,6 @@ namespace CheckDownload
UpdateStatus($"第 {i + 1} 次重试,剩余 {filesToRetry.Count} 个文件...");
var failedThisRound = new ConcurrentDictionary();
- // BUG修复: 此处不应清空重试队列
await PerformDownloads(filesToRetry, failedThisRound);
filesToRetry = new Dictionary(failedThisRound);
@@ -747,17 +704,17 @@ namespace CheckDownload
}
///
- /// 通过DNS服务器解析域名获取IP地址列表
+ /// 使用多个DNS服务器解析域名获取IP地址列表,提高解析成功率
///
- /// 要解析的域名
- /// 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 requestUri = $"http://{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;
@@ -776,7 +733,7 @@ namespace CheckDownload
catch (Exception ex)
{
UpdateStatus($"通过 {dnsServer} 解析域名失败,尝试下一个...");
- await Task.Delay(500); // 短暂延迟后重试
+ await Task.Delay(500);
}
}
@@ -785,9 +742,9 @@ namespace CheckDownload
}
///
- /// 验证并保存所有下载的文件
+ /// 验证所有下载文件的MD5完整性并保存到目标位置,处理文件占用情况
///
- private void VerifyAndSaveAllFiles()
+ private async Task VerifyAndSaveAllFiles()
{
UpdateStatus("正在校验文件...");
var failedFiles = new List();
@@ -798,7 +755,7 @@ namespace CheckDownload
foreach (var item in _downloadedFiles)
{
string relativePath = item.Key;
- string expectedMd5 = item.Value.ExpectedMd5;
+ string expectedMd5 = item.Value;
string tempFilePath = Path.Combine(_tempDirectory, relativePath);
try
@@ -806,8 +763,7 @@ namespace CheckDownload
processedCount++;
UpdateStatus($"正在校验和保存文件 ({processedCount}/{totalFiles}): {relativePath}");
- // 计算临时文件的MD5
- string actualMd5 = CalculateMD5(File.ReadAllBytes(tempFilePath));
+ string actualMd5 = CalculateMD5FromFile(tempFilePath);
if (actualMd5 != expectedMd5.ToLower())
{
throw new Exception($"MD5校验失败 (期望: {expectedMd5}, 实际: {actualMd5})");
@@ -816,16 +772,13 @@ namespace CheckDownload
string localPath = Path.Combine(_baseDirectory, relativePath);
string localDir = Path.GetDirectoryName(localPath);
- // 确保目标目录存在
if (!Directory.Exists(localDir))
{
Directory.CreateDirectory(localDir);
}
- // 尝试移动文件,如果文件被占用则尝试解锁
- if (!TryMoveFile(tempFilePath, localPath))
+ if (!await TryMoveFileAsync(tempFilePath, localPath))
{
- // 如果无法移动文件,则使用备用文件名
string backupPath = localPath + ".new";
File.Move(tempFilePath, backupPath);
filesForScripting.Add((localPath, backupPath));
@@ -868,37 +821,32 @@ namespace CheckDownload
}
///
- /// 尝试移动文件,如果文件被占用则尝试解锁
+ /// 尝试将文件从临时位置移动到目标位置(异步),若文件被占用则等待一秒后重试一次
///
- /// 源文件路径
- /// 目标文件路径
- /// 移动是否成功
- private bool TryMoveFile(string sourcePath, string targetPath)
+ /// 源文件的完整路径
+ /// 目标文件的完整路径
+ /// 移动成功返回true,失败返回false
+ private async Task TryMoveFileAsync(string sourcePath, string targetPath)
{
try
{
- // 直接尝试移动文件
File.Move(sourcePath, targetPath);
return true;
}
catch (IOException)
{
- // 文件被占用,尝试解锁
UpdateStatus($"文件被占用,尝试解锁...");
-
+
try
{
- // 等待一段时间,让文件可能被释放
- Thread.Sleep(1000);
-
- // 再次尝试移动文件
+ await Task.Delay(1000);
+
File.Move(sourcePath, targetPath);
UpdateStatus($"文件解锁成功并已更新");
return true;
}
catch
{
- // 如果仍然无法移动,返回失败
UpdateStatus($"文件仍被占用,无法移动");
return false;
}
@@ -911,9 +859,9 @@ namespace CheckDownload
}
///
- /// 为所有需要替换的文件创建批处理脚本
+ /// 为被占用文件创建批处理脚本,在程序退出后自动完成文件替换
///
- /// 需要替换的文件列表
+ /// 包含原始文件路径和新文件路径的元组列表
private void CreateReplaceScriptForAll(List<(string original, string newFile)> files)
{
if (!files.Any()) return;
@@ -925,7 +873,7 @@ namespace CheckDownload
var batchContent = new StringBuilder();
batchContent.AppendLine("@echo off");
- batchContent.AppendLine("chcp 65001 > nul"); // 确保正确处理UTF-8路径
+ batchContent.AppendLine("chcp 65001 > nul");
batchContent.AppendLine(":check_process");
batchContent.AppendLine($"tasklist /FI \"PID eq {processId}\" 2>NUL | find /I \"{processId}\" >NUL");
batchContent.AppendLine("if %ERRORLEVEL% == 0 (");
@@ -961,20 +909,18 @@ namespace CheckDownload
}
///
- /// 初始化临时目录
+ /// 初始化程序使用的临时目录,用于存储下载过程中的临时文件
///
private void InitializeTempDirectory()
{
try
{
- // 直接使用 %LocalAppData%\Temp\CheckDownload 作为临时目录
_tempDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Temp",
"CheckDownload"
);
- // 确保临时目录存在
if (!Directory.Exists(_tempDirectory))
{
Directory.CreateDirectory(_tempDirectory);
@@ -987,10 +933,10 @@ namespace CheckDownload
}
///
- /// 计算字符串的MD5值
+ /// 计算字符串的MD5哈希值,返回32位小写十六进制字符串
///
- /// 输入字符串
- /// MD5哈希值
+ /// 要计算哈希值的字符串
+ /// 32位小写十六进制MD5哈希值
private string CalculateMD5(string input)
{
using (var md5 = MD5.Create())
@@ -1002,10 +948,10 @@ namespace CheckDownload
}
///
- /// 计算字节数组的MD5值
+ /// 计算字节数组的MD5哈希值,返回32位小写十六进制字符串
///
- /// 字节数组
- /// MD5哈希值
+ /// 要计算哈希值的字节数组
+ /// 32位小写十六进制MD5哈希值
private string CalculateMD5(byte[] data)
{
using (var md5 = MD5.Create())
@@ -1016,14 +962,13 @@ namespace CheckDownload
}
///
- /// 通用的带备用方案的OSS文件下载方法
+ /// 从阿里云OSS下载文件,包含SDK直连和IP直连两种备用方案
///
- /// OSS对象键
- /// 本地保存路径
- /// 下载是否成功
+ /// OSS对象的存储键值
+ /// 文件保存的本地路径
+ /// 下载成功返回true,所有方案都失败返回false
private async Task DownloadFileWithFallback(string ossKey, string localPath)
{
- // 1. 先尝试用 OSS SDK 下载
try
{
var obj = _ossClient.GetObject(OssBucketName, ossKey);
@@ -1043,7 +988,6 @@ namespace CheckDownload
UpdateStatus($"主下载失败,尝试备用方案...");
}
- // 2. 备用方案:用 IP 下载(带签名URL)
var domain = new Uri("https://" + OssEndpoint).Host;
List ips = await GetIpAddressesForDomain(domain);
if (ips == null || ips.Count == 0)
@@ -1052,7 +996,6 @@ namespace CheckDownload
return false;
}
- // 用 SDK 生成带签名的 URL
var req = new GeneratePresignedUriRequest(OssBucketName, ossKey, SignHttpMethod.Get)
{
Expiration = DateTime.Now.AddMinutes(10)
@@ -1064,21 +1007,25 @@ namespace CheckDownload
{
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))
+ request.Headers.Host = signedUri.Host;
+ using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{
- Directory.CreateDirectory(localDir);
+ response.EnsureSuccessStatusCode();
+ string localDir = Path.GetDirectoryName(localPath);
+ if (!Directory.Exists(localDir))
+ {
+ Directory.CreateDirectory(localDir);
+ }
+
+ using (var remote = await response.Content.ReadAsStreamAsync())
+ using (var localFile = File.Create(localPath))
+ {
+ await remote.CopyToAsync(localFile);
+ }
}
- File.WriteAllBytes(localPath, fileData);
return true;
}
catch (Exception)
@@ -1090,40 +1037,38 @@ namespace CheckDownload
}
///
- /// 以系统时间为准根据传入的数字生成一个时间戳(秒)
+ /// 生成基于当前UTC时间的Unix时间戳,并添加指定的秒数偏移
///
- /// 要增加的秒数
- /// Unix时间戳
+ /// 要在当前时间戳基础上增加的秒数
+ /// Unix时间戳(秒)
private long GenerateTimestamp(int number)
{
return (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds + number;
}
///
- /// 根据传入的数字生成一个UUID,不能包含中划线,且长度为32位
+ /// 使用指定数字作为随机种子生成确定性的32位UUID字符串
///
- /// 用于生成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); // 变体位
+ guid[7] = (byte)((guid[7] & 0x0F) | 0x40);
+ guid[8] = (byte)((guid[8] & 0x3F) | 0x80);
var resultGuid = new Guid(guid);
return resultGuid.ToString("N").Replace("-", "");
}
///
- /// 通过MD5算法计算字符串的哈希值,由数字0~9和小写英文字母a~z组成,长度为32位
+ /// 计算字符串的MD5哈希值,用于123盘鉴权签名生成
///
- /// 输入字符串
- /// 32位MD5哈希值
+ /// 要计算哈希值的输入字符串
+ /// 32位小写十六进制MD5哈希值
private string GenerateMD5(string input)
{
using (var md5 = MD5.Create())
@@ -1135,64 +1080,51 @@ namespace CheckDownload
}
///
- /// 生成123云盘鉴权后的URL
+ /// 为123盘文件访问生成带有时间戳和签名验证的鉴权URL
///
- /// 原始URL
- /// 要下载的文件名
- /// 鉴权后的URL
+ /// 123盘的基础访问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
+ return $"{url}/{file}";
}
}
///
- /// 从URL中提取路径部分(域名后的部分)
+ /// 从完整URL中解析并提取域名后的路径部分,用于生成123盘签名
///
- /// 完整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}";
@@ -1204,7 +1136,6 @@ namespace CheckDownload
}
catch
{
- // 如果URI解析失败,使用简单的字符串处理
int protocolIndex = url.IndexOf("://");
if (protocolIndex > 0)
{
@@ -1219,42 +1150,38 @@ namespace CheckDownload
}
///
- /// 带备用方案的在线MD5文件下载方法(123盘主要,OSS备用)
+ /// 下载MD5配置文件,优先使用123盘,失败后切换到阿里云OSS备用方案
///
- /// 要下载的文件名
- /// 本地保存路径
- /// 下载是否成功
+ /// MD5配置文件名
+ /// 文件保存的本地路径
+ /// 下载成功返回true,所有方案都失败返回false
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;
}
}
///
- /// 清理临时文件夹,直接删除整个CheckDownload文件夹
+ /// 清理程序使用的临时目录,删除所有下载过程中产生的临时文件
///
private void CleanupTempDirectory()
{
try
{
- // 临时目录路径
string tempPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Temp",
@@ -1265,85 +1192,62 @@ namespace CheckDownload
{
UpdateStatus("清理临时文件...");
Directory.Delete(tempPath, true);
- Debug.WriteLine($"已清理临时目录: {tempPath}");
}
}
catch (Exception ex)
{
- Debug.WriteLine($"清理临时目录时发生错误: {ex.Message}");
- // 清理失败时不影响程序运行,只记录日志
}
}
///
- /// 基于md5.json内容智能检测基准目录
+ /// 通过分析md5.json文件内容智能确定项目基准目录,用于正确定位文件更新路径
///
- /// md5.json的数据对象
+ /// md5.json文件中的数据对象
private void InitializeBaseDirectoryFromMd5Data(JObject data)
{
try
{
- // 获取当前程序的名称(不包含路径)
string currentProgramName = Path.GetFileName(Application.ExecutablePath);
- Debug.WriteLine($"当前程序名称: {currentProgramName}");
- // 在md5.json中查找当前程序文件
string programPathInMd5 = FindFileInMd5Data(data, currentProgramName);
if (!string.IsNullOrEmpty(programPathInMd5))
{
- Debug.WriteLine($"程序在MD5中的路径: {programPathInMd5}");
-
- // 获取程序在md5.json中所在的目录
string programDirInMd5 = Path.GetDirectoryName(programPathInMd5);
if (string.IsNullOrEmpty(programDirInMd5))
{
- // 程序在根目录
programDirInMd5 = "";
}
- Debug.WriteLine($"程序在MD5中的目录: '{programDirInMd5}'");
-
- // 获取当前程序实际运行的目录
string currentProgramDir = Application.StartupPath;
- Debug.WriteLine($"程序实际运行目录: {currentProgramDir}");
- // 检查当前程序是否运行在md5.json指定的目录中
if (string.IsNullOrEmpty(programDirInMd5))
{
- // 程序应该在根目录,使用当前目录作为基准目录
_baseDirectory = currentProgramDir;
}
else
{
- // 程序应该在某个子目录中,需要向上查找基准目录
_baseDirectory = FindProjectBaseDirectory(currentProgramDir, programDirInMd5);
}
}
else
{
- // 在md5.json中没有找到当前程序,使用当前目录作为基准目录
_baseDirectory = Application.StartupPath;
- Debug.WriteLine($"MD5文件中未找到程序 '{currentProgramName}',使用当前目录作为基准目录");
}
-
- Debug.WriteLine($"最终确定的基准目录: {_baseDirectory}");
}
catch (Exception ex)
{
- // 如果出错,使用当前目录作为fallback
_baseDirectory = Application.StartupPath;
- Debug.WriteLine($"基准目录检测失败,使用默认: {_baseDirectory} - {ex.Message}");
}
}
///
- /// 在md5.json数据中递归查找指定文件名
+ /// 在md5.json的数据结构中递归搜索指定的文件名
///
- /// md5数据对象
- /// 要查找的文件名
- /// 当前路径
- /// 文件在md5.json中的完整路径,如果未找到返回null
+ /// md5.json解析后的JSON对象
+ /// 要搜索的目标文件名
+ /// 当前递归的路径,用于构建完整路径
+ /// 找到的文件在md5.json中的完整相对路径,未找到返回null
private string FindFileInMd5Data(JObject data, string fileName, string currentPath = "")
{
foreach (var property in data.Properties())
@@ -1351,12 +1255,10 @@ namespace CheckDownload
string key = property.Name;
JToken value = property.Value;
- // 构造当前文件/目录的完整路径
string fullPath = string.IsNullOrEmpty(currentPath) ? key : Path.Combine(currentPath, key);
if (value.Type == JTokenType.String)
{
- // 这是一个文件,检查文件名是否匹配
if (string.Equals(key, fileName, StringComparison.OrdinalIgnoreCase))
{
return fullPath;
@@ -1364,7 +1266,6 @@ namespace CheckDownload
}
else if (value.Type == JTokenType.Object)
{
- // 这是一个目录,递归查找
string result = FindFileInMd5Data((JObject)value, fileName, fullPath);
if (!string.IsNullOrEmpty(result))
{
@@ -1376,66 +1277,66 @@ namespace CheckDownload
}
///
- /// 根据程序在md5.json中的路径,查找项目的基准目录
+ /// 通过分析程序在md5.json中的相对路径,向上递归查找项目根目录
///
- /// 当前程序所在目录
+ /// 当前程序运行的目录
/// 程序在md5.json中的相对路径
- /// 项目基准目录
+ /// 项目的基准目录路径
private string FindProjectBaseDirectory(string currentDir, string expectedRelativePath)
{
try
{
- // 将路径分解为目录层级
string[] expectedDirs = expectedRelativePath.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar },
StringSplitOptions.RemoveEmptyEntries);
- Debug.WriteLine($"期望的相对路径层级: {string.Join(" -> ", expectedDirs)}");
-
string checkDir = currentDir;
- // 向上查找,直到找到匹配的基准目录
for (int i = 0; i < expectedDirs.Length; i++)
{
string parentDir = Directory.GetParent(checkDir)?.FullName;
if (string.IsNullOrEmpty(parentDir))
{
- // 已经到达根目录,无法继续向上
break;
}
- // 检查当前目录名是否匹配期望路径中的最后一个目录
string currentDirName = Path.GetFileName(checkDir);
string expectedDirName = expectedDirs[expectedDirs.Length - 1 - i];
- Debug.WriteLine($"检查: 当前目录名 '{currentDirName}' vs 期望目录名 '{expectedDirName}'");
-
if (string.Equals(currentDirName, expectedDirName, StringComparison.OrdinalIgnoreCase))
{
- // 如果这是期望路径的第一个目录,说明找到了基准目录
if (i == expectedDirs.Length - 1)
{
- Debug.WriteLine($"找到基准目录: {parentDir}");
return parentDir;
}
- // 继续向上查找
checkDir = parentDir;
}
else
{
- // 目录名不匹配,说明程序不在期望的路径中
break;
}
}
- // 如果没有找到匹配的路径结构,使用当前目录
- Debug.WriteLine("未找到匹配的路径结构,使用当前目录");
return currentDir;
}
catch (Exception ex)
{
- Debug.WriteLine($"查找基准目录时出错: {ex.Message}");
return currentDir;
}
}
+
+ ///
+ /// 使用文件流计算指定文件的 MD5 哈希值(32 位小写十六进制)。
+ ///
+ /// 要计算 MD5 的文件完整路径
+ /// 32 位小写十六进制 MD5 字符串
+ private string CalculateMD5FromFile(string filePath)
+ {
+ using (var fs = File.OpenRead(filePath))
+ using (var md5 = MD5.Create())
+ {
+ var hashBytes = md5.ComputeHash(fs);
+ return BitConverter.ToString(hashBytes).Replace("-", string.Empty).ToLowerInvariant();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/packages.config b/packages.config
index 8ca4bff..2c4649d 100644
--- a/packages.config
+++ b/packages.config
@@ -3,7 +3,7 @@
-
+