Files
CheckDownload/Form1.cs
2025-06-18 20:41:17 +08:00

966 lines
38 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
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";
// 用于存储下载的文件数据
private Dictionary<string, (byte[] Data, string ExpectedMd5)> _downloadedFiles = new Dictionary<string, (byte[], string)>();
// 已完成的下载数量
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
{
UpdateStatus("下载在线MD5文件并读取...");
string tempFilePath = Path.Combine(_tempDirectory, Md5File);
// 使用带备用方案的下载方法
if (!await DownloadFileWithFallback(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();
}
}
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);
}
}
private bool ValidateOnlineData(string version, string md5, JObject data)
{
if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null)
{
UpdateStatus("在线MD5文件无效");
return false;
}
return true;
}
private Dictionary<string, string> CompareMd5Data(JObject onlineData, string currentPath = "")
{
var differences = new Dictionary<string, string>();
foreach (var onlineProperty in onlineData.Properties())
{
string key = onlineProperty.Name;
JToken onlineValue = onlineProperty.Value;
string fullPath = string.IsNullOrEmpty(currentPath) ? key : $"{currentPath}/{key}";
if (onlineValue.Type == JTokenType.String)
{
string expectedMd5 = onlineValue.ToString();
if (!File.Exists(fullPath))
{
if (!differences.ContainsKey(fullPath))
{
differences[fullPath] = expectedMd5;
}
}
else
{
string localMd5 = CalculateMD5(File.ReadAllBytes(fullPath));
if (localMd5 != expectedMd5)
{
if (!differences.ContainsKey(fullPath))
{
differences[fullPath] = expectedMd5;
}
}
}
}
else if (onlineValue.Type == JTokenType.Object)
{
var subDifferences = CompareMd5Data((JObject)onlineValue, fullPath);
foreach (var diff in subDifferences)
{
if (!differences.ContainsKey(diff.Key))
{
differences[diff.Key] = diff.Value;
}
}
}
}
return differences;
}
private async Task DownloadAndVerifyFiles(Dictionary<string, string> fileList)
{
try
{
OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
_totalCount = fileList.Count;
_completedCount = 0;
_downloadedFiles.Clear();
var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
var failedFiles = new Dictionary<string, string>(); // 记录下载失败的文件
await Task.Run(() =>
{
Parallel.ForEach(fileList, options, (file, state) =>
{
string filePath = file.Key;
string expectedMd5 = file.Value;
try
{
string ossKey = $"File/{expectedMd5}";
var obj = client.GetObject(OssBucketName, ossKey);
// 在临时目录中创建对应的目录结构
string tempFilePath = Path.Combine(_tempDirectory, filePath);
string tempDir = Path.GetDirectoryName(tempFilePath);
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
// 将文件保存到临时目录
using (var fileStream = File.Create(tempFilePath))
{
obj.Content.CopyTo(fileStream);
}
// 记录文件信息
lock (_downloadedFiles)
{
_downloadedFiles[filePath] = (null, expectedMd5);
}
Interlocked.Increment(ref _completedCount);
int progress = (int)((double)_completedCount / _totalCount * 95);
UpdateProgressValue(progress);
UpdateStatus($"已下载 {_completedCount}/{_totalCount}");
}
catch (Exception ex) when (ex is OssException || ex is WebException)
{
UpdateStatus($"使用备用方案尝试...");
string ossKey = $"File/{expectedMd5}";
string tempFilePath = Path.Combine(_tempDirectory, filePath);
bool fallbackResult = DownloadWithFallback(ossKey, tempFilePath);
if (fallbackResult)
{
Interlocked.Increment(ref _completedCount);
int progress = (int)((double)_completedCount / _totalCount * 95);
UpdateProgressValue(progress);
UpdateStatus($"已下载 {_completedCount}/{_totalCount}");
// 记录文件信息
lock (_downloadedFiles)
{
_downloadedFiles[filePath] = (null, expectedMd5);
}
}
else
{
UpdateStatus($"备用方案已执行");
// 记录失败的文件,稍后重试
lock (failedFiles)
{
failedFiles[filePath] = expectedMd5;
}
}
}
catch (Exception ex)
{
UpdateStatus($"下载文件 {filePath} 失败: {ex.Message}");
// 记录失败的文件,稍后重试
lock (failedFiles)
{
failedFiles[filePath] = expectedMd5;
}
}
});
});
// 如果有失败的文件,进行重试
if (failedFiles.Count > 0)
{
UpdateStatus($"开始重试 {failedFiles.Count} 个失败的文件...");
await RetryFailedFiles(failedFiles);
}
if (_downloadedFiles.Count != fileList.Count)
{
throw new Exception($"部分文件下载失败,成功 {_downloadedFiles.Count}/{fileList.Count}");
}
VerifyAndSaveAllFiles();
}
catch (Exception ex)
{
UpdateStatus($"下载过程出错: {ex.Message}");
throw;
}
}
/// <summary>
/// 重试下载失败的文件
/// </summary>
/// <param name="failedFiles">失败的文件字典</param>
private async Task RetryFailedFiles(Dictionary<string, string> failedFiles)
{
const int maxRetries = 2; // 最大重试次数
var retryFiles = new Dictionary<string, string>(failedFiles);
for (int retryCount = 1; retryCount <= maxRetries; retryCount++)
{
if (retryFiles.Count == 0) break;
UpdateStatus($"第 {retryCount} 次重试,剩余 {retryFiles.Count} 个文件...");
var currentRetryFiles = new Dictionary<string, string>(retryFiles);
retryFiles.Clear();
foreach (var file in currentRetryFiles)
{
string filePath = file.Key;
string expectedMd5 = file.Value;
try
{
UpdateStatus($"重试下载: {filePath}");
// 先尝试主下载
bool success = false;
try
{
OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
string ossKey = $"File/{expectedMd5}";
var obj = client.GetObject(OssBucketName, ossKey);
string tempFilePath = Path.Combine(_tempDirectory, filePath);
string tempDir = Path.GetDirectoryName(tempFilePath);
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
using (var fileStream = File.Create(tempFilePath))
{
obj.Content.CopyTo(fileStream);
}
success = true;
}
catch (Exception ex) when (ex is OssException || ex is WebException)
{
// 主下载失败,尝试备用方案
string ossKey = $"File/{expectedMd5}";
string tempFilePath = Path.Combine(_tempDirectory, filePath);
success = DownloadWithFallback(ossKey, tempFilePath);
}
if (success)
{
lock (_downloadedFiles)
{
_downloadedFiles[filePath] = (null, expectedMd5);
}
Interlocked.Increment(ref _completedCount);
int progress = (int)((double)_completedCount / _totalCount * 95);
UpdateProgressValue(progress);
UpdateStatus($"重试成功: {filePath}");
}
else
{
retryFiles[filePath] = expectedMd5;
UpdateStatus($"重试失败: {filePath}");
}
}
catch (Exception ex)
{
retryFiles[filePath] = expectedMd5;
UpdateStatus($"重试异常: {filePath} - {ex.Message}");
}
// 重试间隔,避免过于频繁
await Task.Delay(1000);
}
if (retryFiles.Count > 0 && retryCount < maxRetries)
{
UpdateStatus($"等待 3 秒后进行第 {retryCount + 1} 次重试...");
await Task.Delay(3000);
}
}
if (retryFiles.Count > 0)
{
UpdateStatus($"重试完成,仍有 {retryFiles.Count} 个文件下载失败");
}
else
{
UpdateStatus("所有文件重试成功!");
}
}
private async Task<List<string>> GetIpAddressesForDomain(string domain)
{
try
{
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, $"https://223.5.5.5/resolve?name={domain}&type=1&short=1");
request.Headers.Add("User-Agent", "Apifox/1.0.0 (https://apifox.com)");
request.Headers.Add("Accept", "*/*");
request.Headers.Host = "223.5.5.5";
request.Headers.Add("Connection", "keep-alive");
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<string>>(responseBody);
}
}
catch (Exception ex)
{
UpdateStatus($"获取域名IP失败: {domain} - {ex.Message}");
return new List<string>();
}
}
private bool DownloadWithFallback(string ossKey, string localPath)
{
try
{
UpdateStatus($"备用方案开始下载...");
// 1. 用 SDK 生成带签名的 URL
OssClient fallbackClient = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
var req = new GeneratePresignedUriRequest(OssBucketName, ossKey, SignHttpMethod.Get)
{
Expiration = DateTime.Now.AddMinutes(10)
};
var signedUrl = fallbackClient.GeneratePresignedUri(req).ToString();
var signedUri = new Uri(signedUrl);
var domain = signedUri.Host;
List<string> ips = GetIpAddressesForDomain(domain).GetAwaiter().GetResult();
if (ips == null || ips.Count == 0)
{
UpdateStatus($"备用方案无法获取IP地址");
return false;
}
foreach (var ip in ips)
{
try
{
var ipUrl = signedUrl.Replace(signedUri.Host, ip);
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, ipUrl);
request.Headers.Host = signedUri.Host;
var response = httpClient.SendAsync(request).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
byte[] fileData = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
string localDir = Path.GetDirectoryName(localPath);
if (!Directory.Exists(localDir))
{
Directory.CreateDirectory(localDir);
}
File.WriteAllBytes(localPath, fileData);
UpdateStatus($"备用方案下载成功");
return true;
}
}
catch (Exception ex)
{
UpdateStatus($"备用方案用IP下载失败");
}
}
UpdateStatus($"备用方案所有IP均尝试失败");
return false;
}
catch (Exception ex)
{
UpdateStatus($"备用方案下载失败");
return false;
}
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetProcessHandleCount(IntPtr hProcess, out uint pdwHandleCount);
private void VerifyAndSaveAllFiles()
{
UpdateStatus("正在校验文件...");
var failedFiles = new List<string>();
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);
CreateReplaceScript(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);
}
}
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
{
// 尝试找出占用文件的进程
var processes = Process.GetProcesses();
foreach (var process in processes)
{
try
{
// 跳过当前进程
if (process.Id == Process.GetCurrentProcess().Id)
continue;
// 检查进程是否可能占用了文件
string processName = process.ProcessName.ToLower();
if (processName.Contains("explorer") ||
processName.Contains("chrome") ||
processName.Contains("edge") ||
processName.Contains("firefox") ||
processName.Contains("ieplore") ||
processName.Contains("winword") ||
processName.Contains("excel") ||
processName.Contains("powerpnt") ||
processName.Contains("acrobat") ||
processName.Contains("reader"))
{
// 跳过常见的系统进程
continue;
}
// 尝试获取进程句柄数量,如果句柄数量较多,可能正在使用文件
uint handleCount = 0;
if (GetProcessHandleCount(process.Handle, out handleCount) && handleCount > 0)
{
// 尝试关闭进程
UpdateStatus($"尝试关闭可能占用文件的进程: {process.ProcessName}");
process.CloseMainWindow();
// 等待进程关闭
if (!process.WaitForExit(1000))
{
// 如果进程没有响应,尝试强制结束
process.Kill();
}
}
}
catch
{
// 忽略处理单个进程时的错误
}
finally
{
process.Dispose();
}
}
// 再次尝试移动文件
Thread.Sleep(500); // 等待一段时间确保文件已释放
File.Move(sourcePath, targetPath);
UpdateStatus($"文件解锁成功并已更新");
return true;
}
catch
{
// 如果仍然无法移动,返回失败
return false;
}
}
catch (Exception ex)
{
UpdateStatus($"移动文件时发生错误: {ex.Message}");
return false;
}
}
private void CreateReplaceScript(string originalFile, string newFile)
{
try
{
string batchFilePath = Path.Combine(Application.StartupPath, "update_files.bat");
string processId = Process.GetCurrentProcess().Id.ToString();
string escapedOriginalFile = originalFile.Replace("\\", "\\\\");
string escapedNewFile = newFile.Replace("\\", "\\\\");
// 创建批处理文件内容
StringBuilder batchContent = new StringBuilder();
batchContent.AppendLine("@echo off");
batchContent.AppendLine(":check_process");
batchContent.AppendLine($"tasklist /FI \"PID eq {processId}\" 2>NUL | find /I \"{ processId }\" >NUL");
batchContent.AppendLine("if %ERRORLEVEL% == 0 (");
batchContent.AppendLine(" timeout /t 1 /nobreak > NUL");
batchContent.AppendLine(" goto check_process");
batchContent.AppendLine(")");
batchContent.AppendLine("timeout /t 2 /nobreak > NUL");
batchContent.AppendLine($"del \"{escapedOriginalFile}\" /f /q");
batchContent.AppendLine($"move \"{escapedNewFile}\" \"{escapedOriginalFile}\"");
batchContent.AppendLine("del \"%~f0\" /f /q");
// 写入批处理文件
bool fileExists = File.Exists(batchFilePath);
using (StreamWriter writer = new StreamWriter(batchFilePath, fileExists))
{
if (!fileExists)
{
writer.Write(batchContent.ToString());
}
else
{
// 如果文件已存在,只添加移动文件的命令
writer.WriteLine($"del \"{escapedOriginalFile}\" /f /q");
writer.WriteLine($"move \"{escapedNewFile}\" \"{escapedOriginalFile}\"");
}
}
// 如果是第一次创建批处理文件,启动它
if (!fileExists)
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c start \"\" /min \"{batchFilePath}\"",
CreateNoWindow = true,
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden
};
Process.Start(startInfo);
}
}
catch (Exception ex)
{
UpdateStatus($"创建替换脚本时出错: {ex.Message}");
}
}
// 初始化临时目录
private 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
{
// 获取CheckDownload临时文件夹路径
string checkDownloadTempPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Temp",
"CheckDownload"
);
if (!Directory.Exists(checkDownloadTempPath))
{
return;
}
// 删除CheckDownload临时文件夹中的所有子文件夹和文件
foreach (string dir in Directory.GetDirectories(checkDownloadTempPath))
{
try
{
Directory.Delete(dir, true);
}
catch (Exception ex)
{
Debug.WriteLine($"删除临时目录失败: {dir} - {ex.Message}");
}
}
// 删除CheckDownload临时文件夹中的所有文件
foreach (string file in Directory.GetFiles(checkDownloadTempPath))
{
try
{
File.Delete(file);
}
catch (Exception ex)
{
Debug.WriteLine($"删除临时文件失败: {file} - {ex.Message}");
}
}
// 尝试删除CheckDownload临时文件夹本身
try
{
Directory.Delete(checkDownloadTempPath, true);
}
catch (Exception ex)
{
Debug.WriteLine($"删除CheckDownload临时文件夹失败: {ex.Message}");
}
}
catch (Exception ex)
{
Debug.WriteLine($"清理临时目录时发生错误: {ex.Message}");
}
}
private string CalculateMD5(string input)
{
using (var md5 = MD5.Create())
{
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}
private string CalculateMD5(byte[] data)
{
using (var md5 = MD5.Create())
{
byte[] hashBytes = md5.ComputeHash(data);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}
// 通用的带备用方案的 OSS 文件下载方法
private async Task<bool> DownloadFileWithFallback(string ossKey, string localPath)
{
// 1. 先尝试用 OSS SDK 下载
try
{
OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
var obj = client.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<string> ips = await GetIpAddressesForDomain(domain);
if (ips == null || ips.Count == 0)
{
UpdateStatus($"无法获取IP地址");
return false;
}
// 用 SDK 生成带签名的 URL
OssClient fallbackClient = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
var req = new GeneratePresignedUriRequest(OssBucketName, ossKey, SignHttpMethod.Get)
{
Expiration = DateTime.Now.AddMinutes(10)
};
var signedUrl = fallbackClient.GeneratePresignedUri(req).ToString();
var signedUri = new Uri(signedUrl);
foreach (var ip in ips)
{
try
{
// 替换URL中的host为IP
var ipUrl = signedUrl.Replace(signedUri.Host, ip);
using (var httpClient = new 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 ex)
{
UpdateStatus($"备用方案用IP下载失败");
}
}
return false;
}
}
}