// // EPSDKManager.swift // YuMi // // Created by AI on 2025-10-11. // import Foundation /// 第三方 SDK 统一管理器(单例) /// 统一入口:对外提供所有 SDK 能力 /// 内部管理:QCloud 初始化、配置、上传等 @objc class EPSDKManager: NSObject, QCloudSignatureProvider, QCloudCredentailFenceQueueDelegate { // MARK: - Singleton @objc static let shared = EPSDKManager() // MARK: - Properties // QCloud 配置缓存 private var qcloudConfig: EPQCloudConfig? // QCloud 初始化状态 private var isQCloudInitializing = false // QCloud 初始化回调队列 private var qcloudInitCallbacks: [(Bool, String?) -> Void] = [] // QCloud 凭证队列 private var credentialFenceQueue: QCloudCredentailFenceQueue? // 线程安全锁 private let lock = NSLock() // 内部图片上传器 private let uploader = EPImageUploader() // MARK: - Initialization private override init() { super.init() } // MARK: - Public API (对外统一入口) /// 批量上传图片(统一入口) /// - Parameters: /// - images: 要上传的图片数组 /// - progress: 进度回调 (已上传数, 总数) /// - success: 成功回调,返回图片信息数组 /// - failure: 失败回调 @objc func uploadImages( _ images: [UIImage], progress: @escaping (Int, Int) -> Void, success: @escaping ([[String: Any]]) -> Void, failure: @escaping (String) -> Void ) { guard !images.isEmpty else { success([]) return } // 确保 QCloud 已就绪 ensureQCloudReady { [weak self] isReady, errorMsg in guard let self = self, isReady else { DispatchQueue.main.async { failure(errorMsg ?? YMLocalizedString("error.qcloud_init_failed")) } return } // 委托给内部 uploader 执行 self.uploader.performBatchUpload( images, bucket: self.qcloudConfig?.bucket ?? "", customDomain: self.qcloudConfig?.customDomain ?? "", progress: progress, success: success, failure: failure ) } } /// 检查 QCloud 是否已就绪 /// - Returns: true 表示已初始化且未过期 @objc func isQCloudReady() -> Bool { lock.lock() defer { lock.unlock() } guard let config = qcloudConfig else { return false } return !config.isExpired } // MARK: - Internal Methods /// 确保 QCloud 已就绪(自动初始化) private func ensureQCloudReady(completion: @escaping (Bool, String?) -> Void) { if isQCloudReady() { completion(true, nil) return } // 未初始化或已过期,重新初始化 initializeQCloud(completion: completion) } /// 初始化 QCloud(获取 Token 并配置 SDK) private func initializeQCloud(completion: @escaping (Bool, String?) -> Void) { lock.lock() // 如果正在初始化,加入回调队列 if isQCloudInitializing { qcloudInitCallbacks.append(completion) lock.unlock() return } // 如果已初始化且未过期,直接返回 if let config = qcloudConfig, !config.isExpired { lock.unlock() completion(true, nil) return } // 开始初始化 isQCloudInitializing = true qcloudInitCallbacks.append(completion) lock.unlock() // 调用 API 获取 QCloud Token // API: GET tencent/cos/getToken Api.getQCloudInfo { [weak self] (data, code, msg) in guard let self = self else { return } self.lock.lock() if code == 200, let dict = data?.data as? [String: Any], let config = EPQCloudConfig(dictionary: dict) { // 保存配置 self.qcloudConfig = config // 配置 QCloud SDK self.configureQCloudSDK(with: config) // 初始化完成 self.isQCloudInitializing = false let callbacks = self.qcloudInitCallbacks self.qcloudInitCallbacks.removeAll() self.lock.unlock() // 短暂延迟确保 SDK 配置完成 DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { callbacks.forEach { $0(true, nil) } } } else { // 初始化失败 self.isQCloudInitializing = false let callbacks = self.qcloudInitCallbacks self.qcloudInitCallbacks.removeAll() self.lock.unlock() let errorMsg = msg ?? YMLocalizedString("error.qcloud_config_failed") DispatchQueue.main.async { callbacks.forEach { $0(false, errorMsg) } } } } } /// 配置 QCloud SDK(参考 UploadFile.m line 42-64) private func configureQCloudSDK(with config: EPQCloudConfig) { let configuration = QCloudServiceConfiguration() configuration.appID = config.appId let endpoint = QCloudCOSXMLEndPoint() endpoint.regionName = config.region endpoint.useHTTPS = true // 全球加速(参考 UploadFile.m line 56-59) if config.accelerate == 1 { endpoint.suffix = "cos.accelerate.myqcloud.com" } configuration.endpoint = endpoint configuration.signatureProvider = self // 注册 COS 服务 QCloudCOSXMLService.registerDefaultCOSXML(with: configuration) QCloudCOSTransferMangerService.registerDefaultCOSTransferManger(with: configuration) // 初始化凭证队列 credentialFenceQueue = QCloudCredentailFenceQueue() credentialFenceQueue?.delegate = self } // MARK: - QCloudSignatureProvider Protocol /// 提供签名(参考 UploadFile.m line 67-104) func signature( with fields: QCloudSignatureFields, request: QCloudBizHTTPRequest, urlRequest: NSMutableURLRequest, compelete: @escaping QCloudHTTPAuthentationContinueBlock ) { guard let config = qcloudConfig else { let error = NSError(domain: "com.yumi.qcloud", code: -1, userInfo: [NSLocalizedDescriptionKey: YMLocalizedString("error.qcloud_config_not_initialized")]) compelete(nil, error) return } let credential = QCloudCredential() credential.secretID = config.secretId credential.secretKey = config.secretKey credential.token = config.sessionToken credential.startDate = Date(timeIntervalSince1970: TimeInterval(config.startTime)) credential.expirationDate = Date(timeIntervalSince1970: TimeInterval(config.expireTime)) let creator = QCloudAuthentationV5Creator(credential: credential) let signature = creator?.signature(forData: urlRequest) compelete(signature, nil) } // MARK: - QCloudCredentailFenceQueueDelegate Protocol /// 管理凭证(参考 UploadFile.m line 107-133) func fenceQueue( _ queue: QCloudCredentailFenceQueue, requestCreatorWithContinue continueBlock: @escaping QCloudCredentailFenceQueueContinue ) { guard let config = qcloudConfig else { let error = NSError(domain: "com.yumi.qcloud", code: -1, userInfo: [NSLocalizedDescriptionKey: YMLocalizedString("error.qcloud_config_not_initialized")]) continueBlock(nil, error) return } let credential = QCloudCredential() credential.secretID = config.secretId credential.secretKey = config.secretKey credential.token = config.sessionToken credential.startDate = Date(timeIntervalSince1970: TimeInterval(config.startTime)) credential.expirationDate = Date(timeIntervalSince1970: TimeInterval(config.expireTime)) let creator = QCloudAuthentationV5Creator(credential: credential) continueBlock(creator, nil) } }