using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Ink_Canvas.Helpers { /// /// 上传队列项数据(用于序列化) /// public class UploadQueueItemData { [JsonProperty("file_path")] public string FilePath { get; set; } [JsonProperty("retry_count")] public int RetryCount { get; set; } [JsonProperty("added_time")] public DateTime AddedTime { get; set; } } /// /// 上传队列项 /// public class UploadQueueItem { public string FilePath { get; set; } public int RetryCount { get; set; } } /// /// 通用上传队列基类 /// public abstract class BaseUploadQueue : IDisposable { protected const int BATCH_SIZE = 10; // 批量上传大小 protected const int MAX_RETRY_COUNT = 3; // 最大重试次数 /// /// 上传队列 /// protected readonly ConcurrentQueue _uploadQueue = new ConcurrentQueue(); /// /// 队列处理锁,防止并发处理 /// protected readonly SemaphoreSlim _queueProcessingLock = new SemaphoreSlim(1, 1); /// /// 队列保存锁,防止并发保存 /// protected readonly SemaphoreSlim _queueSaveLock = new SemaphoreSlim(1, 1); /// /// 是否已初始化队列 /// protected bool _isQueueInitialized = false; /// /// 是否已释放资源 /// private bool _disposed = false; /// /// 队列文件名 /// protected abstract string QueueFileName { get; } /// /// 允许的文件扩展名 /// protected virtual HashSet AllowedExtensions => new HashSet { ".png", ".icstk", ".xml", ".zip" }; /// /// 获取队列文件路径 /// protected string GetQueueFilePath() { var configsDir = Path.Combine(App.RootPath, "Configs"); if (!Directory.Exists(configsDir)) { Directory.CreateDirectory(configsDir); } return Path.Combine(configsDir, QueueFileName); } /// /// 获取最大文件大小 /// /// 文件扩展名 /// 最大文件大小(字节) protected virtual long GetMaxFileSize(string extension) { return extension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024; } /// /// 初始化上传队列 /// public void InitializeQueue() { if (_isQueueInitialized) { return; } try { var queueFilePath = GetQueueFilePath(); if (!File.Exists(queueFilePath)) { _isQueueInitialized = true; return; } var jsonContent = File.ReadAllText(queueFilePath); if (string.IsNullOrWhiteSpace(jsonContent)) { _isQueueInitialized = true; return; } var queueData = JsonConvert.DeserializeObject>(jsonContent); if (queueData == null || queueData.Count == 0) { _isQueueInitialized = true; return; } int restoredCount = 0; int skippedCount = 0; foreach (var item in queueData) { // 验证文件是否存在 if (!File.Exists(item.FilePath)) { skippedCount++; continue; } // 验证文件格式和大小 if (!IsValidFile(item.FilePath)) { skippedCount++; continue; } // 恢复队列项 _uploadQueue.Enqueue(new UploadQueueItem { FilePath = item.FilePath, RetryCount = item.RetryCount }); restoredCount++; } _isQueueInitialized = true; if (restoredCount > 0) { LogHelper.WriteLogToFile($"[{GetType().Name}] 已恢复上传队列:{restoredCount}个文件,跳过{skippedCount}个无效文件", LogHelper.LogType.Event); // 如果恢复了队列,触发处理 _ = Task.Run(async () => { try { await ProcessUploadQueueAsync().ConfigureAwait(false); } catch (Exception ex) { LogHelper.WriteLogToFile($"[{GetType().Name}] 恢复上传队列后处理时出错: {ex}", LogHelper.LogType.Error); } }); } else if (skippedCount > 0) { LogHelper.WriteLogToFile($"[{GetType().Name}] 队列恢复完成:跳过{skippedCount}个无效文件", LogHelper.LogType.Event); } } catch (Exception ex) { LogHelper.WriteLogToFile($"[{GetType().Name}] 恢复上传队列时出错: {ex.Message}", LogHelper.LogType.Error); _isQueueInitialized = true; // 即使出错也标记为已初始化,避免重复尝试 } } /// /// 保存队列到文件 /// protected async Task SaveQueueToFileAsync(CancellationToken cancellationToken = default) { if (!await _queueSaveLock.WaitAsync(1000, cancellationToken)) // 最多等待1秒 { return; // 如果无法获取锁,跳过保存(避免阻塞) } try { cancellationToken.ThrowIfCancellationRequested(); var queueData = new List(); // 将队列转换为可序列化的格式 foreach (var item in _uploadQueue) { cancellationToken.ThrowIfCancellationRequested(); queueData.Add(new UploadQueueItemData { FilePath = item.FilePath, RetryCount = item.RetryCount, AddedTime = DateTime.Now }); } var queueFilePath = GetQueueFilePath(); // 如果队列为空,清空文件 if (queueData.Count == 0) { ClearQueueFile(); return; } var jsonContent = JsonConvert.SerializeObject(queueData, Formatting.Indented); // 使用进程保护的写入门控,避免安全面板中"进程文件保护"占用导致无法写入 var tempFilePath = queueFilePath + ".tmp"; ProcessProtectionManager.WithWriteAccess(queueFilePath, () => { File.WriteAllText(tempFilePath, jsonContent); if (File.Exists(queueFilePath)) File.Delete(queueFilePath); File.Move(tempFilePath, queueFilePath); }); } catch (OperationCanceledException) { // 取消操作,静默处理 } catch (Exception ex) { LogHelper.WriteLogToFile($"[{GetType().Name}] 保存上传队列时出错: {ex.Message}", LogHelper.LogType.Error); } finally { _queueSaveLock.Release(); } } /// /// 清空队列文件 /// protected void ClearQueueFile() { try { var queueFilePath = GetQueueFilePath(); ProcessProtectionManager.WithWriteAccess(queueFilePath, () => { if (File.Exists(queueFilePath)) File.WriteAllText(queueFilePath, "[]"); }); } catch (Exception ex) { LogHelper.WriteLogToFile($"[{GetType().Name}] 清空队列文件时出错: {ex.Message}", LogHelper.LogType.Error); } } /// /// 将文件加入上传队列 /// protected void EnqueueFile(string filePath, int retryCount = 0, CancellationToken cancellationToken = default) { _uploadQueue.Enqueue(new UploadQueueItem { FilePath = filePath, RetryCount = retryCount }); // 异步保存队列到文件 _ = Task.Run(async () => { try { cancellationToken.ThrowIfCancellationRequested(); await SaveQueueToFileAsync(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { // 取消操作,静默处理 } catch (Exception ex) { LogHelper.WriteLogToFile($"[{GetType().Name}] 保存上传队列时出错(后台任务): {ex}", LogHelper.LogType.Error); } }, cancellationToken); // 触发队列处理 _ = ProcessUploadQueueAsync(cancellationToken); } /// /// 处理上传队列,批量上传文件 /// protected async Task ProcessUploadQueueAsync(CancellationToken cancellationToken = default) { // 使用信号量防止并发处理 if (!await _queueProcessingLock.WaitAsync(0, cancellationToken)) { return; // 已有处理任务在运行 } try { cancellationToken.ThrowIfCancellationRequested(); var filesToUpload = new List(); // 从队列中取出最多BATCH_SIZE个文件 int count = 0; while (count < BATCH_SIZE && _uploadQueue.TryDequeue(out UploadQueueItem item)) { cancellationToken.ThrowIfCancellationRequested(); // 再次检查文件是否存在 if (File.Exists(item.FilePath) && IsValidFile(item.FilePath)) { filesToUpload.Add(item); count++; } } if (filesToUpload.Count == 0) { return; } // 检查是否启用 if (!IsUploadEnabled()) { LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:上传未启用", LogHelper.LogType.Error); // 将文件重新加入队列 foreach (var item in filesToUpload) { EnqueueFile(item.FilePath, item.RetryCount, cancellationToken); } return; } // 并发上传所有文件,并处理失败重试 var uploadTasks = filesToUpload.Select(async item => { try { cancellationToken.ThrowIfCancellationRequested(); var success = await UploadFileInternalAsync(item.FilePath, cancellationToken); if (!success) { // 检查是否是可重试的错误 if (IsRetryableError(item.FilePath)) { // 检查重试次数 if (item.RetryCount < MAX_RETRY_COUNT) { LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败,将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Event); EnqueueFile(item.FilePath, item.RetryCount + 1, cancellationToken); } else { LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error); } } } return success; } catch (OperationCanceledException) { // 取消操作,将文件重新加入队列 EnqueueFile(item.FilePath, item.RetryCount, cancellationToken); throw; } catch (Exception ex) { // 检查是否是可重试的错误(超时、网络错误等) var errorMessage = ex.Message.ToLower(); bool isRetryable = errorMessage.Contains("超时") || errorMessage.Contains("timeout") || errorMessage.Contains("网络错误") || errorMessage.Contains("network") || errorMessage.Contains("408") || // 请求超时 errorMessage.Contains("423") || // 资源锁定 errorMessage.Contains("429") || // 请求过多 errorMessage.Contains("500") || // 服务器错误 errorMessage.Contains("502") || // 网关错误 errorMessage.Contains("503") || // 服务不可用 errorMessage.Contains("504"); // 网关超时 if (isRetryable && IsRetryableError(item.FilePath)) { // 检查重试次数 if (item.RetryCount < MAX_RETRY_COUNT) { LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败({ex.Message}),将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error); EnqueueFile(item.FilePath, item.RetryCount + 1, cancellationToken); } else { LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error); } } else { LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败(不可重试): {Path.GetFileName(item.FilePath)} - {ex.Message}", LogHelper.LogType.Error); } return false; } }); await Task.WhenAll(uploadTasks).ConfigureAwait(false); // 上传完成后保存队列状态 await SaveQueueToFileAsync(cancellationToken).ConfigureAwait(false); // 检查队列中是否还有文件,如果有就继续处理 if (_uploadQueue.Count > 0) { _ = Task.Run(async () => { try { await ProcessUploadQueueAsync(cancellationToken).ConfigureAwait(false); } catch (Exception ex) { LogHelper.WriteLogToFile($"[{GetType().Name}] 继续处理上传队列时出错: {ex}", LogHelper.LogType.Error); } }, cancellationToken); } } finally { _queueProcessingLock.Release(); } } /// /// 验证文件是否有效 /// protected virtual bool IsValidFile(string filePath) { try { var fileExtension = Path.GetExtension(filePath).ToLower(); if (!AllowedExtensions.Contains(fileExtension)) { return false; } var fileInfo = new FileInfo(filePath); long maxSize = GetMaxFileSize(fileExtension); if (fileInfo.Length > maxSize) { LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:文件过大({fileInfo.Length / 1024 / 1024:F2}MB),超过{maxSize / 1024 / 1024}MB限制", LogHelper.LogType.Error); return false; } return true; } catch { return false; } } /// /// 判断错误是否可重试 /// protected bool IsRetryableError(string filePath) { // 检查文件是否存在 if (!File.Exists(filePath)) { return false; // 文件不存在,不可重试 } // 检查文件是否有效 if (!IsValidFile(filePath)) { return false; // 文件无效,不可重试 } // 检查是否启用 if (!IsUploadEnabled()) { return false; // 上传未启用,不可重试 } // 其他错误(超时、网络错误等)可以重试 return true; } /// /// 检查上传是否启用 /// protected abstract bool IsUploadEnabled(); /// /// 内部上传方法,执行实际上传操作 /// protected abstract Task UploadFileInternalAsync(string filePath, CancellationToken cancellationToken); /// /// 异步上传文件 /// public async Task UploadFileAsync(string filePath, CancellationToken cancellationToken = default) { try { cancellationToken.ThrowIfCancellationRequested(); // 检查是否启用 if (!IsUploadEnabled()) { return false; } // 基本验证 if (!File.Exists(filePath)) { LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error); return false; } if (!IsValidFile(filePath)) { return false; } // 确保队列已初始化 if (!_isQueueInitialized) { InitializeQueue(); } // 加入队列 EnqueueFile(filePath, 0, cancellationToken); return true; } catch (OperationCanceledException) { LogHelper.WriteLogToFile($"[{GetType().Name}] 上传被取消: {Path.GetFileName(filePath)}", LogHelper.LogType.Event); throw; } catch (Exception ex) { LogHelper.WriteLogToFile($"[{GetType().Name}] 加入上传队列时出错: {ex.Message}", LogHelper.LogType.Error); return false; } } /// /// 释放资源 /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// 释放资源 /// /// 是否手动释放 protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { _queueProcessingLock?.Dispose(); _queueSaveLock?.Dispose(); } _disposed = true; } } /// /// 析构函数 /// ~BaseUploadQueue() { Dispose(false); } } }