import Foundation import QCloudCOSXML import UIKit import ComposableArchitecture // MARK: - 上传服务协议 /// 上传服务协议 public protocol COSUploadServiceProtocol: Sendable { /// 上传图片数据 func uploadImage(_ imageData: Data, fileName: String) async throws -> String /// 上传UIImage func uploadUIImage(_ image: UIImage, fileName: String) async throws -> String /// 取消上传 func cancelUpload(taskId: UUID) async } // MARK: - 上传服务实现 /// 上传服务实现 public struct COSUploadService: COSUploadServiceProtocol { private let tokenService: COSTokenServiceProtocol private let configurationService: COSConfigurationServiceProtocol private let uploadTaskManager: UploadTaskManagerProtocol public init( tokenService: COSTokenServiceProtocol, configurationService: COSConfigurationServiceProtocol, uploadTaskManager: UploadTaskManagerProtocol = UploadTaskManager() ) { self.tokenService = tokenService self.configurationService = configurationService self.uploadTaskManager = uploadTaskManager } /// 上传图片数据 public func uploadImage(_ imageData: Data, fileName: String) async throws -> String { debugInfoSync("🚀 开始上传图片,数据大小: \(imageData.count) bytes") // 获取Token let tokenData = try await tokenService.getValidToken() // 确保COS服务已初始化 try await configurationService.initializeCOSService(with: tokenData) // 创建上传任务 let task = UploadTask( imageData: imageData, fileName: fileName ) // 执行上传 return try await performUpload(task: task, tokenData: tokenData) } /// 上传UIImage public func uploadUIImage(_ image: UIImage, fileName: String) async throws -> String { guard let data = image.jpegData(compressionQuality: 0.7) else { throw COSError.uploadFailed("图片压缩失败,无法生成 JPEG 数据") } return try await uploadImage(data, fileName: fileName) } /// 取消上传 public func cancelUpload(taskId: UUID) async { await uploadTaskManager.cancelTask(taskId) } // MARK: - 私有方法 /// 执行上传 private func performUpload(task: UploadTask, tokenData: TcTokenData) async throws -> String { // 注册任务 await uploadTaskManager.registerTask(task) return try await withCheckedThrowingContinuation { continuation in Task { do { // 创建上传请求 let request = try await createUploadRequest(task: task, tokenData: tokenData) // 设置进度回调 request.sendProcessBlock = { @Sendable (bytesSent, totalBytesSent, totalBytesExpectedToSend) in Task { let progress = UploadProgress( bytesSent: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend ) await self.uploadTaskManager.updateTaskProgress( taskId: task.id, progress: progress ) debugInfoSync("📊 上传进度: \(progress.progress * 100)%") } } // 设置完成回调 request.setFinish { @Sendable result, error in Task { await self.uploadTaskManager.unregisterTask(task.id) if let error = error { debugErrorSync("❌ 图片上传失败: \(error.localizedDescription)") continuation.resume(throwing: COSError.uploadFailed(error.localizedDescription)) } else { // 构建云地址 let cloudURL = tokenData.buildCloudURL(for: task.fileName) debugInfoSync("✅ 图片上传成功: \(cloudURL)") continuation.resume(returning: cloudURL) } } } // 开始上传 QCloudCOSTransferMangerService.defaultCOSTransferManager().uploadObject(request) } catch { await uploadTaskManager.unregisterTask(task.id) continuation.resume(throwing: error) } } } } /// 创建上传请求 private func createUploadRequest(task: UploadTask, tokenData: TcTokenData) async throws -> QCloudCOSXMLUploadObjectRequest { // 创建凭证 let credential = QCloudCredential() credential.secretID = tokenData.secretId credential.secretKey = tokenData.secretKey.trimmingCharacters(in: .whitespacesAndNewlines) credential.token = tokenData.sessionToken credential.startDate = tokenData.startDate credential.expirationDate = tokenData.expirationDate // 创建上传请求 let request = QCloudCOSXMLUploadObjectRequest() request.bucket = tokenData.bucket request.regionName = tokenData.region request.credential = credential request.object = task.fileName request.body = task.imageData as AnyObject // 设置加速 if tokenData.accelerate { request.enableQuic = true } return request } } // MARK: - 上传任务管理协议 /// 上传任务管理协议 public protocol UploadTaskManagerProtocol: Sendable { /// 注册任务 func registerTask(_ task: UploadTask) async /// 注销任务 func unregisterTask(_ taskId: UUID) async /// 更新任务进度 func updateTaskProgress(taskId: UUID, progress: UploadProgress) async /// 取消任务 func cancelTask(_ taskId: UUID) async /// 获取任务状态 func getTaskStatus(_ taskId: UUID) async -> UploadStatus? } // MARK: - 上传任务管理实现 /// 上传任务管理实现 public actor UploadTaskManager: UploadTaskManagerProtocol { private var activeTasks: [UUID: UploadTask] = [:] public init() {} /// 注册任务 public func registerTask(_ task: UploadTask) async { activeTasks[task.id] = task debugInfoSync("📝 注册上传任务: \(task.id)") } /// 注销任务 public func unregisterTask(_ taskId: UUID) async { activeTasks.removeValue(forKey: taskId) debugInfoSync("📝 注销上传任务: \(taskId)") } /// 更新任务进度 public func updateTaskProgress(taskId: UUID, progress: UploadProgress) async { guard var task = activeTasks[taskId] else { return } let newStatus = UploadStatus.uploading(progress: progress.progress) task = task.updatingStatus(newStatus) activeTasks[taskId] = task } /// 取消任务 public func cancelTask(_ taskId: UUID) async { guard let task = activeTasks[taskId] else { return } // 更新任务状态为失败 let updatedTask = task.updatingStatus(.failure(error: "任务已取消")) activeTasks[taskId] = updatedTask debugInfoSync("❌ 取消上传任务: \(taskId)") } /// 获取任务状态 public func getTaskStatus(_ taskId: UUID) async -> UploadStatus? { return activeTasks[taskId]?.status } } // MARK: - TCA 依赖扩展 extension DependencyValues { /// COS 上传服务依赖 public var cosUploadService: COSUploadServiceProtocol { get { self[COSUploadServiceKey.self] } set { self[COSUploadServiceKey.self] = newValue } } } /// COS 上传服务依赖键 private enum COSUploadServiceKey: DependencyKey { static let liveValue: COSUploadServiceProtocol = COSUploadService( tokenService: COSTokenService(apiService: LiveAPIService()), configurationService: COSConfigurationService() ) static let testValue: COSUploadServiceProtocol = MockCOSUploadService() } // MARK: - Mock 服务(用于测试) /// Mock 上传服务(用于测试) public struct MockCOSUploadService: COSUploadServiceProtocol { public var uploadImageResult: Result = .failure(COSError.uploadFailed("Mock error")) public var uploadUIImageResult: Result = .failure(COSError.uploadFailed("Mock error")) public init() {} public func uploadImage(_ imageData: Data, fileName: String) async throws -> String { switch uploadImageResult { case .success(let url): return url case .failure(let error): throw error } } public func uploadUIImage(_ image: UIImage, fileName: String) async throws -> String { switch uploadUIImageResult { case .success(let url): return url case .failure(let error): throw error } } public func cancelUpload(taskId: UUID) async { // Mock 实现,无需实际操作 } } // MARK: - 扩展 extension UploadTask { /// 生成唯一的文件名 public static func generateFileName(extension: String = "jpg") -> String { let uuid = UUID().uuidString return "images/\(uuid).\(`extension`)" } }