using COSXML; using COSXML.Auth; using COSXML.Model.Object; using COSXML.Transfer; using WorkCameraExport.Models; using WorkCameraExport.Services.Interfaces; namespace WorkCameraExport.Services { /// /// COS 服务类 - 处理腾讯云 COS 图片上传 /// public class CosService : IDisposable { private CosXml? _cosXml; private CosTempCredentials? _credentials; private readonly ILogService? _logService; private bool _disposed; public CosService(ILogService? logService = null) { _logService = logService; } /// /// 初始化 COS 客户端 /// public void Initialize(CosTempCredentials credentials) { _credentials = credentials; _logService?.Info($"[COS] 初始化, Region={credentials.Region}, Bucket={credentials.Bucket}"); var config = new CosXmlConfig.Builder() .IsHttps(true) .SetRegion(credentials.Region) .SetDebugLog(false) .Build(); var credentialProvider = new DefaultSessionQCloudCredentialProvider( credentials.TmpSecretId, credentials.TmpSecretKey, credentials.ExpiredTime, credentials.SessionToken); _cosXml = new CosXmlServer(config, credentialProvider); _logService?.Info("[COS] 初始化完成"); } /// /// 检查是否已初始化 /// public bool IsInitialized => _cosXml != null && _credentials != null; /// /// 上传图片到 COS /// /// 本地文件路径 /// COS 对象键(路径) /// 进度回调 /// 取消令牌 /// 上传结果 public async Task<(bool Success, string Message, string? CosUrl)> UploadFileAsync( string localFilePath, string cosKey, Action? progress = null, CancellationToken cancellationToken = default) { if (!IsInitialized || _cosXml == null || _credentials == null) { _logService?.Error("[COS] 上传失败: 服务未初始化"); return (false, "COS 服务未初始化", null); } _logService?.Info($"[COS] 开始上传文件: {localFilePath} -> {cosKey}"); try { var transferConfig = new TransferConfig(); var transferManager = new TransferManager(_cosXml, transferConfig); var uploadTask = new COSXMLUploadTask(_credentials.Bucket, cosKey); uploadTask.SetSrcPath(localFilePath); if (progress != null) { uploadTask.progressCallback = (completed, total) => { progress(completed, total); }; } // 使用新的异步 API var result = await transferManager.UploadAsync(uploadTask); if (result.IsSuccessful()) { var cosUrl = $"https://{_credentials.Bucket}.cos.{_credentials.Region}.myqcloud.com/{cosKey}"; _logService?.Info($"[COS] 上传成功: {cosUrl}"); return (true, "上传成功", cosUrl); } else { _logService?.Warn($"[COS] 上传失败: result.IsSuccessful()=false"); return (false, "上传失败", null); } } catch (OperationCanceledException) { _logService?.Info("[COS] 上传已取消"); return (false, "上传已取消", null); } catch (COSXML.CosException.CosClientException clientEx) { _logService?.Error($"[COS] 客户端异常: {clientEx.Message}", clientEx); return (false, $"客户端异常: {clientEx.Message}", null); } catch (COSXML.CosException.CosServerException serverEx) { _logService?.Error($"[COS] 服务端异常: {serverEx.GetInfo()}"); return (false, $"服务端异常: {serverEx.GetInfo()}", null); } catch (Exception ex) { _logService?.Error($"[COS] 上传异常: {ex.Message}", ex); return (false, $"上传异常: {ex.Message}", null); } } /// /// 上传字节数组到 COS /// public async Task<(bool Success, string Message, string? CosUrl)> UploadBytesAsync( byte[] data, string cosKey, Action? progress = null, CancellationToken cancellationToken = default) { if (!IsInitialized || _cosXml == null || _credentials == null) { return (false, "COS 服务未初始化", null); } // 创建临时文件 var tempFile = Path.GetTempFileName(); try { await File.WriteAllBytesAsync(tempFile, data, cancellationToken); return await UploadFileAsync(tempFile, cosKey, progress, cancellationToken); } finally { // 清理临时文件 try { if (File.Exists(tempFile)) { File.Delete(tempFile); } } catch { // 忽略清理错误 } } } /// /// 批量上传图片 /// /// 上传任务列表 (本地路径, COS键) /// 最大并发数 /// 进度回调 (已完成数, 总数) /// 取消令牌 /// 上传结果列表 public async Task> BatchUploadAsync( List<(string LocalPath, string CosKey)> uploadTasks, int maxConcurrency = 5, Action? progress = null, CancellationToken cancellationToken = default) { var results = new List<(string LocalPath, bool Success, string? CosUrl, string Message)>(); var semaphore = new SemaphoreSlim(maxConcurrency); var completed = 0; var total = uploadTasks.Count; var tasks = uploadTasks.Select(async task => { await semaphore.WaitAsync(cancellationToken); try { var result = await UploadFileAsync(task.LocalPath, task.CosKey, cancellationToken: cancellationToken); lock (results) { results.Add((task.LocalPath, result.Success, result.CosUrl, result.Message)); completed++; progress?.Invoke(completed, total); } } finally { semaphore.Release(); } }); await Task.WhenAll(tasks); return results; } /// /// 生成 COS 对象键(路径) /// /// 记录时间 /// 分类(当日照片/参与人员/工作内容/部门) /// 子分类 /// 文件名 /// COS 对象键 public static string GenerateCosKey(DateTime recordTime, string category, string subCategory, string fileName) { var yearMonth = recordTime.ToString("yyyyMM"); var date = recordTime.ToString("yyyyMMdd"); if (string.IsNullOrEmpty(subCategory)) { return $"workfiles/{yearMonth}/{date}/{category}/{fileName}"; } return $"workfiles/{yearMonth}/{date}/{category}/{subCategory}/{fileName}"; } /// /// 生成唯一文件名 /// public static string GenerateFileName(string extension = ".jpg") { var timestamp = DateTime.Now.ToString("yyyyMMddHHmmssfff"); var random = new Random().Next(1000, 9999); return $"{timestamp}_{random}{extension}"; } #region IDisposable public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { _cosXml = null; _credentials = null; } _disposed = true; } } #endregion } }