feat: 集成阿里云OSS SDK实现文件下载功能
- 添加阿里云OSS SDK依赖,用于从OSS下载文件 - 重构文件下载逻辑,使用OSS SDK替代原有HTTP下载方式 - 优化MD5文件比较和文件校验流程,提高下载效率
This commit is contained in:
parent
52ce8400ba
commit
a92d6a4070
@ -48,6 +48,9 @@
|
|||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="Aliyun.OSS, Version=2.14.1.0, Culture=neutral, PublicKeyToken=0ad4175f0dac0b9b, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\Aliyun.OSS.SDK.2.14.1\lib\net461\Aliyun.OSS.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="DnsClient, Version=1.8.0.0, Culture=neutral, PublicKeyToken=4574bb5573c51424, processorArchitecture=MSIL">
|
<Reference Include="DnsClient, Version=1.8.0.0, Culture=neutral, PublicKeyToken=4574bb5573c51424, processorArchitecture=MSIL">
|
||||||
<HintPath>packages\DnsClient.1.8.0\lib\net472\DnsClient.dll</HintPath>
|
<HintPath>packages\DnsClient.1.8.0\lib\net472\DnsClient.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
786
Form1.cs
786
Form1.cs
@ -2,7 +2,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@ -12,533 +14,275 @@ using System.Text;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using Aliyun.OSS;
|
||||||
|
using Aliyun.OSS.Common;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace CheckDownload
|
namespace CheckDownload
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 自动更新窗体类,负责检查和下载软件更新
|
|
||||||
/// </summary>
|
|
||||||
public partial class Update : Form
|
public partial class Update : Form
|
||||||
{
|
{
|
||||||
/// <summary>本地MD5文件名</summary>
|
// MD5文件名称
|
||||||
private const string LocalMd5File = "md5.json";
|
private const string Md5File = "md5.json";
|
||||||
/// <summary>DNS查询域名,用于获取更新信息</summary>
|
// 阿里云OSS访问地址
|
||||||
private const string DnsQueryDomain = "test.file.ipoi.cn";
|
private const string OssEndpoint = "oss-cn-hongkong.aliyuncs.com";
|
||||||
/// <summary>基础下载URL</summary>
|
// 阿里云OSS存储空间名称
|
||||||
private const string BaseDownloadUrl = "http://localhost:60006/";
|
private const string OssBucketName = "sxho-hk";
|
||||||
/// <summary>最大并发下载数</summary>
|
// 阿里云OSS访问密钥ID
|
||||||
private const int MaxConcurrentDownloads = 5;
|
private const string OssAccessKeyId = "LTAI5tBc8BgooVrHrzfEeg1q";
|
||||||
|
// 阿里云OSS访问密钥Secret
|
||||||
/// <summary>在线MD5文件名,从DNS查询获取</summary>
|
private const string OssAccessKeySecret = "nMvSp0UqzodTzKImuAMK1a1bkSks5O";
|
||||||
private string _onlineMd5File = "";
|
// 用于存储下载的文件数据
|
||||||
|
|
||||||
// 类级别变量,用于存储下载的文件数据
|
|
||||||
private Dictionary<string, (byte[] Data, string ExpectedMd5)> _downloadedFiles = new Dictionary<string, (byte[], string)>();
|
private Dictionary<string, (byte[] Data, string ExpectedMd5)> _downloadedFiles = new Dictionary<string, (byte[], string)>();
|
||||||
/// <summary>已完成下载的文件数</summary>
|
// 已完成的下载数量
|
||||||
private int _completedFiles = 0;
|
private int _completedCount = 0;
|
||||||
/// <summary>需要下载的总文件数</summary>
|
// 总下载数量
|
||||||
private int _totalFilesToDownload = 0;
|
private int _totalCount = 0;
|
||||||
|
|
||||||
/// <summary>
|
// 初始化窗体
|
||||||
/// 构造函数,初始化更新窗体
|
|
||||||
/// </summary>
|
|
||||||
public Update()
|
public Update()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
ConfigureProgressBar();
|
ConfigureProgressBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// 配置进度条
|
||||||
/// 配置进度条初始设置
|
|
||||||
/// </summary>
|
|
||||||
private void ConfigureProgressBar()
|
private void ConfigureProgressBar()
|
||||||
{
|
{
|
||||||
Update_Pro.Minimum = 0; // 设置最小值
|
Update_Pro.Minimum = 0;
|
||||||
Update_Pro.Maximum = 100; // 设置最大值
|
Update_Pro.Maximum = 100;
|
||||||
Update_Pro.Value = 0; // 设置初始值
|
Update_Pro.Value = 0;
|
||||||
Update_Pro.Step = 1; // 设置步进值
|
Update_Pro.Step = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新进度条值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="percentage">进度百分比(0-100)</param>
|
|
||||||
private void UpdateProgressValue(int percentage)
|
private void UpdateProgressValue(int percentage)
|
||||||
{
|
{
|
||||||
// 确保百分比在有效范围内
|
|
||||||
if (percentage < 0) percentage = 0;
|
if (percentage < 0) percentage = 0;
|
||||||
if (percentage > 100) percentage = 100;
|
if (percentage > 100) percentage = 100;
|
||||||
|
|
||||||
// 在UI线程上更新进度条值
|
|
||||||
if (this.InvokeRequired)
|
if (this.InvokeRequired)
|
||||||
{
|
{
|
||||||
this.Invoke((MethodInvoker)delegate
|
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);
|
Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum);
|
||||||
}
|
|
||||||
// 添加Application.DoEvents()调用,确保消息队列被处理
|
|
||||||
Application.DoEvents();
|
Application.DoEvents();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
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();
|
Application.DoEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void UpdateStatus(string message)
|
||||||
/// 窗体加载事件处理方法,启动更新流程
|
{
|
||||||
/// </summary>
|
if (this.InvokeRequired)
|
||||||
/// <param name="sender">事件源</param>
|
{
|
||||||
/// <param name="e">事件参数</param>
|
this.Invoke((MethodInvoker)delegate { Status_Box.Text = message; });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Status_Box.Text = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async void Update_Load(object sender, EventArgs e)
|
public async void Update_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
// 设置窗体位置到屏幕右下角
|
|
||||||
PositionFormToBottomRight();
|
PositionFormToBottomRight();
|
||||||
|
await UpdateFile();
|
||||||
await UpdateFile(); // 执行更新流程
|
|
||||||
// 注意:窗体关闭逻辑已移至UpdateFile方法中,确保显示完成消息后再关闭
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置窗体位置到屏幕右下角
|
|
||||||
/// </summary>
|
|
||||||
private void PositionFormToBottomRight()
|
private void PositionFormToBottomRight()
|
||||||
{
|
{
|
||||||
// 获取当前屏幕的工作区域(排除任务栏等系统元素)
|
|
||||||
Rectangle workingArea = Screen.GetWorkingArea(this);
|
Rectangle workingArea = Screen.GetWorkingArea(this);
|
||||||
|
this.Location = new Point(workingArea.Right - this.Width, workingArea.Bottom - this.Height);
|
||||||
// 计算窗体在右下角的位置
|
|
||||||
int x = workingArea.Right - this.Width;
|
|
||||||
int y = workingArea.Bottom - this.Height;
|
|
||||||
|
|
||||||
// 设置窗体位置
|
|
||||||
this.Location = new Point(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 主要更新流程,检查并下载更新文件
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>更新任务</returns>
|
|
||||||
private async Task UpdateFile()
|
private async Task UpdateFile()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 定义更新流程的总步骤数和当前步骤
|
const int totalSteps = 4;
|
||||||
const int totalSteps = 6; // 总共6个主要步骤
|
|
||||||
int currentStep = 0;
|
int currentStep = 0;
|
||||||
|
|
||||||
// 更新进度条初始状态
|
UpdateStatus("下载在线MD5文件并读取...");
|
||||||
UpdateProgressValue(0);
|
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);
|
||||||
|
|
||||||
// 步骤1: 读取本地MD5文件信息
|
UpdateStatus("比较本地和在线MD5文件...");
|
||||||
UpdateStatus("读取本地MD5文件...");
|
var compareResult = CompareMd5Data(onlineData.Data);
|
||||||
var localData = await ReadLocalMd5File();
|
if (compareResult.Count == 0)
|
||||||
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%
|
|
||||||
|
|
||||||
// 步骤3: 比较版本,判断是否需要更新
|
|
||||||
UpdateStatus("比较版本信息...");
|
|
||||||
if (!ShouldUpdate(localData.Version, onlineData.Version))
|
|
||||||
{
|
{
|
||||||
UpdateStatus("当前已是最新版本,无需更新");
|
UpdateStatus("所有文件都是最新的,无需更新");
|
||||||
UpdateProgressValue(100); // 完成进度
|
UpdateProgressValue(100);
|
||||||
|
await Task.Delay(2000);
|
||||||
|
this.Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UpdateProgressValue(++currentStep * 100 / totalSteps); // 更新进度为50%
|
UpdateProgressValue(++currentStep * 100 / totalSteps);
|
||||||
|
|
||||||
// 步骤4: 下载在线MD5文件
|
UpdateStatus("下载并验证文件...");
|
||||||
UpdateStatus("下载在线MD5文件...");
|
_totalCount = compareResult.Count;
|
||||||
var onlineFileData = await DownloadOnlineMd5File();
|
await DownloadAndVerifyFiles(compareResult);
|
||||||
if (onlineFileData == null) return;
|
UpdateProgressValue(100);
|
||||||
UpdateProgressValue(++currentStep * 100 / totalSteps); // 更新进度为66%
|
|
||||||
|
|
||||||
// 步骤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("更新完成");
|
UpdateStatus("更新完成");
|
||||||
|
|
||||||
// 等待1秒后关闭窗体
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
this.Close();
|
this.Close();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
UpdateStatus($"发生错误: {ex.Message}");
|
UpdateStatus($"更新失败: {ex.Message}");
|
||||||
|
await Task.Delay(3000);
|
||||||
|
this.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private (string Version, string Md5, JObject Data) ReadOnlineMd5File(OssClient client, string bucket, string key)
|
||||||
/// 验证本地数据的有效性
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="version">版本号</param>
|
|
||||||
/// <param name="md5">MD5校验值</param>
|
|
||||||
/// <param name="data">文件数据对象</param>
|
|
||||||
/// <returns>数据是否有效</returns>
|
|
||||||
private bool ValidateLocalData(string version, string md5, JObject data)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null)
|
|
||||||
{
|
|
||||||
UpdateStatus("本地MD5文件无效");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证在线数据的有效性
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="version">版本号</param>
|
|
||||||
/// <param name="md5">MD5校验值</param>
|
|
||||||
/// <returns>数据是否有效</returns>
|
|
||||||
private bool ValidateOnlineData(string version, string md5)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5))
|
|
||||||
{
|
|
||||||
UpdateStatus("无法获取在线MD5信息");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 读取本地MD5文件并解析内容
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>包含版本号、MD5值和数据对象的元组</returns>
|
|
||||||
private async Task<(string Version, string Md5, JObject Data)> ReadLocalMd5File()
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
UpdateStatus("读取md5.json...");
|
var obj = client.GetObject(bucket, key);
|
||||||
// 异步读取文件内容
|
using (var reader = new StreamReader(obj.Content))
|
||||||
string json = await Task.Run(() => File.ReadAllText(LocalMd5File));
|
{
|
||||||
// 解析JSON对象
|
string json = reader.ReadToEnd();
|
||||||
var obj = JObject.Parse(json);
|
var parsed = JObject.Parse(json);
|
||||||
// 获取版本号
|
string version = parsed["version"]?.ToString();
|
||||||
string version = obj["version"]?.ToString();
|
var data = (JObject)parsed["data"];
|
||||||
// 获取数据对象
|
|
||||||
var data = (JObject)obj["data"];
|
|
||||||
// 计算JSON内容的MD5值
|
|
||||||
string jsonMd5 = CalculateMD5(json);
|
string jsonMd5 = CalculateMD5(json);
|
||||||
return (version, jsonMd5, data);
|
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);
|
return (null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private bool ValidateOnlineData(string version, string md5, JObject data)
|
||||||
/// 获取在线MD5文件信息
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>包含版本号和MD5值的元组</returns>
|
|
||||||
private async Task<(string Version, string Md5)> GetOnlineMd5File()
|
|
||||||
{
|
{
|
||||||
try
|
if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null)
|
||||||
{
|
{
|
||||||
UpdateStatus("解析在线md5.json...");
|
UpdateStatus("在线MD5文件无效");
|
||||||
// 通过DNS查询获取响应数据
|
|
||||||
string responseData = await QueryDnsAsync();
|
|
||||||
// 反序列化JSON字符串
|
|
||||||
string firstUnescaped = JsonConvert.DeserializeObject<string>(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 判断是否需要更新
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="localVersion">本地版本号</param>
|
|
||||||
/// <param name="onlineVersion">在线版本号</param>
|
|
||||||
/// <returns>是否需要更新</returns>
|
|
||||||
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}");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private Dictionary<string, string> CompareMd5Data(JObject onlineData, string currentPath = "")
|
||||||
/// 下载在线MD5文件并解析内容
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>文件数据对象</returns>
|
|
||||||
private async Task<JObject> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 比较本地数据和在线数据的差异
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="localData">本地数据对象</param>
|
|
||||||
/// <param name="onlineData">在线数据对象</param>
|
|
||||||
/// <param name="currentPath">当前路径,用于递归调用</param>
|
|
||||||
/// <returns>需要更新的文件路径和MD5值的字典</returns>
|
|
||||||
private Dictionary<string, string> CompareDataDifferences(JObject localData, JObject onlineData, string currentPath = "")
|
|
||||||
{
|
{
|
||||||
var differences = new Dictionary<string, string>();
|
var differences = new Dictionary<string, string>();
|
||||||
|
|
||||||
foreach (var onlineProperty in onlineData.Properties())
|
foreach (var onlineProperty in onlineData.Properties())
|
||||||
{
|
{
|
||||||
// 获取属性名称
|
|
||||||
string key = onlineProperty.Name;
|
string key = onlineProperty.Name;
|
||||||
// 获取属性值
|
|
||||||
JToken onlineValue = onlineProperty.Value;
|
JToken onlineValue = onlineProperty.Value;
|
||||||
// 构建完整路径
|
|
||||||
string fullPath = string.IsNullOrEmpty(currentPath) ? key : $"{currentPath}/{key}";
|
string fullPath = string.IsNullOrEmpty(currentPath) ? key : $"{currentPath}/{key}";
|
||||||
|
|
||||||
if (onlineValue.Type == JTokenType.String)
|
if (onlineValue.Type == JTokenType.String)
|
||||||
{
|
{
|
||||||
// 如果是字符串类型,表示是文件的MD5值
|
|
||||||
string expectedMd5 = onlineValue.ToString();
|
string expectedMd5 = onlineValue.ToString();
|
||||||
// 判断是否需要下载该文件
|
if (!File.Exists(fullPath))
|
||||||
if (ShouldDownloadFile(localData, key, expectedMd5, fullPath))
|
{
|
||||||
|
if (!differences.ContainsKey(fullPath))
|
||||||
{
|
{
|
||||||
differences[fullPath] = expectedMd5;
|
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)
|
else if (onlineValue.Type == JTokenType.Object)
|
||||||
{
|
{
|
||||||
// 如果是对象类型,表示是目录,递归比较
|
var subDifferences = CompareMd5Data((JObject)onlineValue, fullPath);
|
||||||
JObject localSubData = GetSubData(localData, key);
|
|
||||||
var subDifferences = CompareDataDifferences(localSubData, (JObject)onlineValue, fullPath);
|
|
||||||
foreach (var diff in subDifferences)
|
foreach (var diff in subDifferences)
|
||||||
|
{
|
||||||
|
if (!differences.ContainsKey(diff.Key))
|
||||||
{
|
{
|
||||||
differences[diff.Key] = diff.Value;
|
differences[diff.Key] = diff.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return differences;
|
return differences;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private async Task DownloadAndVerifyFiles(Dictionary<string, string> fileList)
|
||||||
/// 判断是否需要下载文件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="localData">本地数据对象</param>
|
|
||||||
/// <param name="key">文件键名</param>
|
|
||||||
/// <param name="expectedMd5">期望的MD5值</param>
|
|
||||||
/// <param name="fullPath">文件完整路径</param>
|
|
||||||
/// <returns>是否需要下载</returns>
|
|
||||||
private bool ShouldDownloadFile(JObject localData, string key, string expectedMd5, string fullPath)
|
|
||||||
{
|
{
|
||||||
// 本地数据中不存在该键
|
try
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取本地数据中的子数据对象
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="localData">本地数据对象</param>
|
|
||||||
/// <param name="key">子数据键名</param>
|
|
||||||
/// <returns>子数据对象,如果不存在则返回空对象</returns>
|
|
||||||
private JObject GetSubData(JObject localData, string key)
|
|
||||||
{
|
{
|
||||||
// 如果本地数据中存在该键且类型为对象,则返回该对象,否则返回空对象
|
OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
|
||||||
return localData.ContainsKey(key) && localData[key].Type == JTokenType.Object
|
_totalCount = fileList.Count;
|
||||||
? (JObject)localData[key]
|
_completedCount = 0;
|
||||||
: new JObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载需要更新的文件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="differences">需要更新的文件路径和MD5值的字典</param>
|
|
||||||
/// <returns>下载任务</returns>
|
|
||||||
private async Task DownloadUpdatedFiles(Dictionary<string, string> differences)
|
|
||||||
{
|
|
||||||
// 重置下载状态
|
|
||||||
_completedFiles = 0;
|
|
||||||
_downloadedFiles.Clear();
|
_downloadedFiles.Clear();
|
||||||
|
|
||||||
// 设置总文件数
|
var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
|
||||||
_totalFilesToDownload = differences.Count;
|
|
||||||
|
|
||||||
// 更新状态信息
|
await Task.Run(() =>
|
||||||
UpdateStatus($"开始下载更新文件,共{differences.Count}个文件...");
|
|
||||||
|
|
||||||
// 创建信号量控制并发下载数量
|
|
||||||
var semaphore = new SemaphoreSlim(MaxConcurrentDownloads);
|
|
||||||
var downloadTasks = new List<Task>();
|
|
||||||
|
|
||||||
// 为每个需要更新的文件创建下载任务
|
|
||||||
foreach (var file in differences)
|
|
||||||
{
|
{
|
||||||
downloadTasks.Add(DownloadFileWithSemaphore(file, semaphore));
|
Parallel.ForEach(fileList, options, (file, state) =>
|
||||||
}
|
|
||||||
|
|
||||||
// 等待所有下载任务完成
|
|
||||||
await Task.WhenAll(downloadTasks);
|
|
||||||
|
|
||||||
// 所有文件下载完成后统一校验和保存
|
|
||||||
UpdateStatus("所有文件下载完成,开始校验和保存...");
|
|
||||||
VerifyAndSaveAllFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 使用信号量控制并发下载文件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="file">文件路径和MD5值的键值对</param>
|
|
||||||
/// <param name="semaphore">控制并发的信号量</param>
|
|
||||||
/// <returns>下载任务</returns>
|
|
||||||
private async Task DownloadFileWithSemaphore(KeyValuePair<string, string> file, SemaphoreSlim semaphore)
|
|
||||||
{
|
{
|
||||||
await semaphore.WaitAsync();
|
string filePath = file.Key;
|
||||||
|
string expectedMd5 = file.Value;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await DownloadToMemory(file.Key, file.Value);
|
string ossKey = $"File/{expectedMd5}";
|
||||||
}
|
var obj = client.GetObject(OssBucketName, ossKey);
|
||||||
finally
|
using (var memoryStream = new MemoryStream())
|
||||||
{
|
{
|
||||||
semaphore.Release();
|
obj.Content.CopyTo(memoryStream);
|
||||||
}
|
byte[] fileData = memoryStream.ToArray();
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下载文件到内存并暂存
|
|
||||||
/// </summary>
|
|
||||||
private async Task DownloadToMemory(string relativePath, string expectedMd5)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 更新状态显示当前下载进度
|
|
||||||
int current = Interlocked.Increment(ref _completedFiles);
|
|
||||||
|
|
||||||
// 下载文件到内存
|
|
||||||
using (var client = new HttpClient())
|
|
||||||
{
|
|
||||||
var fileUrl = $"{BaseDownloadUrl}File/{expectedMd5}";
|
|
||||||
var response = await client.GetAsync(fileUrl);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
byte[] fileData = await response.Content.ReadAsByteArrayAsync();
|
|
||||||
|
|
||||||
// 存储到内存字典
|
|
||||||
lock (_downloadedFiles)
|
lock (_downloadedFiles)
|
||||||
{
|
{
|
||||||
_downloadedFiles[relativePath] = (fileData, expectedMd5);
|
_downloadedFiles[filePath] = (fileData, expectedMd5);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新进度
|
Interlocked.Increment(ref _completedCount);
|
||||||
UpdateProgress();
|
int progress = (int)((double)_completedCount / _totalCount * 95);
|
||||||
|
UpdateProgressValue(progress);
|
||||||
|
UpdateStatus($"已下载 {_completedCount}/{_totalCount}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
UpdateStatus($"下载失败: {relativePath} - {ex.Message}");
|
UpdateStatus($"下载文件 {filePath} 失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_downloadedFiles.Count != fileList.Count)
|
||||||
|
{
|
||||||
|
throw new Exception($"部分文件下载失败,成功 {_downloadedFiles.Count}/{fileList.Count}");
|
||||||
|
}
|
||||||
|
|
||||||
|
VerifyAndSaveAllFiles();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
UpdateStatus($"下载过程出错: {ex.Message}");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 校验并保存所有已下载的文件
|
|
||||||
/// </summary>
|
|
||||||
private void VerifyAndSaveAllFiles()
|
private void VerifyAndSaveAllFiles()
|
||||||
{
|
{
|
||||||
UpdateStatus("正在校验文件...");
|
UpdateStatus("正在校验文件...");
|
||||||
|
|
||||||
// 创建失败文件列表
|
|
||||||
var failedFiles = new List<string>();
|
var failedFiles = new List<string>();
|
||||||
int processedCount = 0;
|
int processedCount = 0;
|
||||||
int totalFiles = _downloadedFiles.Count;
|
int totalFiles = _downloadedFiles.Count;
|
||||||
@ -551,40 +295,21 @@ namespace CheckDownload
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 更新状态信息
|
|
||||||
processedCount++;
|
processedCount++;
|
||||||
UpdateStatus($"正在校验和保存文件 ({processedCount}/{totalFiles}): {relativePath}");
|
UpdateStatus($"正在校验和保存文件 ({processedCount}/{totalFiles}): {relativePath}");
|
||||||
|
|
||||||
// 计算内存中数据的MD5
|
string actualMd5 = CalculateMD5(data);
|
||||||
string actualMd5;
|
|
||||||
using (var md5 = MD5.Create())
|
|
||||||
{
|
|
||||||
byte[] hashBytes = md5.ComputeHash(data);
|
|
||||||
actualMd5 = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验MD5
|
|
||||||
if (actualMd5 != expectedMd5.ToLower())
|
if (actualMd5 != expectedMd5.ToLower())
|
||||||
{
|
{
|
||||||
throw new Exception($"MD5校验失败 (期望: {expectedMd5}, 实际: {actualMd5})");
|
throw new Exception($"MD5校验失败 (期望: {expectedMd5}, 实际: {actualMd5})");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取本地路径并创建目录
|
string localPath = Path.Combine(Application.StartupPath, relativePath);
|
||||||
string localPath = GetLocalPath(relativePath);
|
Directory.CreateDirectory(Path.GetDirectoryName(localPath));
|
||||||
EnsureDirectoryExists(localPath);
|
|
||||||
|
|
||||||
// 保存文件
|
|
||||||
File.WriteAllBytes(localPath, data);
|
File.WriteAllBytes(localPath, data);
|
||||||
|
|
||||||
// 更新进度条 - 从95%到98%
|
int percentage = 95 + (int)(processedCount * 5.0 / totalFiles);
|
||||||
// 确保即使在totalFiles为0的情况下也不会出现除零错误,并且百分比不会超过98
|
UpdateProgressValue(Math.Min(100, percentage));
|
||||||
int percentage = 95;
|
|
||||||
if (totalFiles > 0)
|
|
||||||
{
|
|
||||||
// 计算进度时确保不会超过98%
|
|
||||||
percentage = 95 + Math.Min(3, (int)(processedCount * 3.0 / totalFiles));
|
|
||||||
}
|
|
||||||
UpdateProgressValue(percentage);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -593,7 +318,6 @@ namespace CheckDownload
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理失败的文件记录
|
|
||||||
foreach (var failedFile in failedFiles)
|
foreach (var failedFile in failedFiles)
|
||||||
{
|
{
|
||||||
_downloadedFiles.Remove(failedFile);
|
_downloadedFiles.Remove(failedFile);
|
||||||
@ -606,257 +330,27 @@ namespace CheckDownload
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
UpdateStatus("所有文件校验和保存成功");
|
UpdateStatus("所有文件校验和保存成功");
|
||||||
|
UpdateProgressValue(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取文件的本地路径
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="relativePath">文件的相对路径</param>
|
|
||||||
/// <returns>本地文件路径</returns>
|
|
||||||
private string GetLocalPath(string relativePath)
|
|
||||||
{
|
|
||||||
// 将相对路径转换为本地路径,替换斜杠为反斜杠
|
|
||||||
return Path.Combine(".", relativePath.Replace('/', '\\'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 确保文件所在目录存在
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
private void EnsureDirectoryExists(string filePath)
|
|
||||||
{
|
|
||||||
// 获取文件所在目录
|
|
||||||
string directory = Path.GetDirectoryName(filePath);
|
|
||||||
// 如果目录不存在,则创建目录
|
|
||||||
if (!Directory.Exists(directory))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(directory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证文件的MD5值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
/// <param name="expectedMd5">期望的MD5值</param>
|
|
||||||
/// <param name="relativePath">文件的相对路径,用于错误提示</param>
|
|
||||||
/// <exception cref="Exception">当MD5校验失败时抛出异常</exception>
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 替换现有文件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tempPath">临时文件路径</param>
|
|
||||||
/// <param name="localPath">目标文件路径</param>
|
|
||||||
private void ReplaceExistingFile(string tempPath, string localPath)
|
|
||||||
{
|
|
||||||
// 如果目标文件已存在,则先删除
|
|
||||||
if (File.Exists(localPath))
|
|
||||||
{
|
|
||||||
File.Delete(localPath);
|
|
||||||
}
|
|
||||||
// 将临时文件移动到目标位置
|
|
||||||
File.Move(tempPath, localPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新文件下载进度
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 替换本地MD5文件为在线下载的新版本
|
|
||||||
/// </summary>
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新状态信息显示
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">要显示的状态信息</param>
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 计算字符串的MD5值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">输入字符串</param>
|
|
||||||
/// <returns>MD5哈希值(小写十六进制字符串)</returns>
|
|
||||||
private string CalculateMD5(string input)
|
private string CalculateMD5(string input)
|
||||||
{
|
{
|
||||||
using (var md5 = MD5.Create())
|
using (var md5 = MD5.Create())
|
||||||
{
|
{
|
||||||
// 将字符串转换为字节数组
|
|
||||||
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
|
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
|
||||||
// 计算哈希值
|
|
||||||
byte[] hashBytes = md5.ComputeHash(inputBytes);
|
byte[] hashBytes = md5.ComputeHash(inputBytes);
|
||||||
// 将字节数组转换为十六进制字符串并返回
|
|
||||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private string CalculateMD5(byte[] data)
|
||||||
/// 计算文件的MD5值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
/// <returns>MD5哈希值(小写十六进制字符串)</returns>
|
|
||||||
private string CalculateFileMD5(string filePath)
|
|
||||||
{
|
{
|
||||||
using (var md5 = MD5.Create())
|
using (var md5 = MD5.Create())
|
||||||
using (var stream = File.OpenRead(filePath))
|
|
||||||
{
|
{
|
||||||
// 计算文件流的哈希值
|
byte[] hashBytes = md5.ComputeHash(data);
|
||||||
byte[] hashBytes = md5.ComputeHash(stream);
|
|
||||||
// 将字节数组转换为十六进制字符串并返回
|
|
||||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 通过多个DNS over HTTPS服务器查询TXT记录
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>查询到的TXT记录内容</returns>
|
|
||||||
private static async Task<string> QueryDnsAsync()
|
|
||||||
{
|
|
||||||
// DNS over HTTPS服务器列表
|
|
||||||
var dohServers = new List<string>
|
|
||||||
{
|
|
||||||
"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<Task<string>>();
|
|
||||||
|
|
||||||
// 并行查询所有DNS服务器
|
|
||||||
foreach (var server in dohServers)
|
|
||||||
{
|
|
||||||
tasks.Add(QueryDnsServer(httpClient, server, cts.Token));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等待任意一个任务完成
|
|
||||||
var completedTask = await Task.WhenAny(tasks);
|
|
||||||
// 取消其他正在进行的任务
|
|
||||||
cts.Cancel();
|
|
||||||
// 返回第一个完成的任务结果
|
|
||||||
return await completedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询指定DNS over HTTPS服务器的TXT记录
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client">HTTP客户端</param>
|
|
||||||
/// <param name="server">DNS服务器URL</param>
|
|
||||||
/// <param name="token">取消令牌</param>
|
|
||||||
/// <returns>查询到的TXT记录内容,失败则返回空字符串</returns>
|
|
||||||
private static async Task<string> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
|
<package id="Aliyun.OSS.SDK" version="2.14.1" targetFramework="net472" />
|
||||||
<package id="DnsClient" version="1.8.0" targetFramework="net472" />
|
<package id="DnsClient" version="1.8.0" targetFramework="net472" />
|
||||||
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net472" />
|
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net472" />
|
||||||
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
|
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user