import Foundation import UIKit /// 图片上传服务,封装了图片上传和进度跟踪功能 public class ImageUploadService { // MARK: - Shared Instance public static let shared = ImageUploadService() // MARK: - Properties private let uploader: ImageUploaderGetID // MARK: - Initialization public init(uploader: ImageUploaderGetID = ImageUploaderGetID()) { self.uploader = uploader } // MARK: - Public Methods /// 上传图片并返回上传结果 /// - Parameters: /// - image: 要上传的图片 /// - progressHandler: 上传进度回调 (0.0 到 1.0) /// - completion: 完成回调,返回上传结果或错误 public func uploadImage( _ image: UIImage, progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void, completion: @escaping (Result) -> Void ) { uploader.uploadImage( image, progress: { progress in let progressInfo = ImageUploadService.UploadProgress( current: Int(progress * 100), total: 100, progress: progress, isOriginal: true ) DispatchQueue.main.async { progressHandler(progressInfo) } }, completion: { result in DispatchQueue.main.async { completion(result) } } ) } /// 上传压缩图片并返回上传结果 /// - Parameters: /// - image: 要上传的图片 /// - compressionQuality: 压缩质量 (0.0 到 1.0) /// - progressHandler: 上传进度回调 (0.0 到 1.0) /// - completion: 完成回调,返回上传结果或错误 public func uploadCompressedImage( _ image: UIImage, compressionQuality: CGFloat = 0.5, progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void, completion: @escaping (Result) -> Void ) { guard let compressedImage = image.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else { completion(.failure(NSError(domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "图片压缩失败"]))) return } uploadImage( compressedImage, progress: progressHandler, completion: completion ) } /// 上传原图和压缩图 /// - Parameters: /// - image: 原始图片 /// - compressionQuality: 压缩质量 (0.0 到 1.0) /// - progressHandler: 上传进度回调 /// - completion: 完成回调,返回原始图和压缩图的上传结果 public func uploadOriginalAndCompressedImage( _ image: UIImage, compressionQuality: CGFloat = 0.5, progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void, completion: @escaping (Result) -> Void ) { // 上传原图 uploadImage(image, progress: { progress in let originalProgress = ImageUploadService.UploadProgress( current: Int(progress.progress * 100), total: 200, // 总进度为200(原图100 + 压缩图100) progress: progress.progress * 0.5, // 原图占50% isOriginal: true ) progressHandler(originalProgress) }) { [weak self] originalResult in guard let self = self else { return } switch originalResult { case .success(let originalUploadResult): // 原图上传成功,上传压缩图 self.uploadCompressedImage( image, compressionQuality: compressionQuality, progress: { progress in let compressedProgress = ImageUploadService.UploadProgress( current: 100 + Int(progress.progress * 100), // 从100开始 total: 200, // 总进度为200(原图100 + 压缩图100) progress: 0.5 + (progress.progress * 0.5), // 压缩图占后50% isOriginal: false ) progressHandler(compressedProgress) }, completion: { compressedResult in switch compressedResult { case .success(let compressedUploadResult): let results = ImageUploadService.UploadResults( original: originalUploadResult, compressed: compressedUploadResult ) completion(.success(results)) case .failure(let error): completion(.failure(error)) } } ) case .failure(let error): completion(.failure(error)) } } } // MARK: - Unified Media Upload /// 上传媒体文件(图片或视频) /// - Parameters: /// - media: 媒体类型,可以是图片或视频 /// - compressionQuality: 缩略图/图片压缩质量 (0.0 到 1.0) /// - progressHandler: 上传进度回调 (0.0 到 1.0) /// - completion: 完成回调,返回上传结果或错误 public func uploadMedia( _ media: MediaType, compressionQuality: CGFloat = 0.7, progress progressHandler: @escaping (UploadProgress) -> Void, completion: @escaping (Result) -> Void ) { switch media { case .image(let image): // 处理图片上传 uploadCompressedImage( image, compressionQuality: compressionQuality, progress: progressHandler, completion: { result in let mediaResult = result.map { MediaUploadResult.file($0) } completion(mediaResult) } ) case .video(let videoURL, let thumbnail): // 处理视频上传 uploadVideoWithThumbnail( videoURL: videoURL, existingThumbnail: thumbnail, compressionQuality: compressionQuality, progress: progressHandler, completion: completion ) } } /// 上传视频及其缩略图 private func uploadVideoWithThumbnail( videoURL: URL, existingThumbnail: UIImage?, compressionQuality: CGFloat, progress progressHandler: @escaping (UploadProgress) -> Void, completion: @escaping (Result) -> Void ) { // 1. 提取视频缩略图 func processThumbnail(_ thumbnail: UIImage) { // 2. 压缩缩略图 guard let compressedThumbnail = thumbnail.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else { let error = NSError(domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "缩略图压缩失败"]) completion(.failure(error)) return } // 3. 上传视频文件 let videoProgress = Progress(totalUnitCount: 100) let thumbnailProgress = Progress(totalUnitCount: 100) // 组合进度 let totalProgress = Progress(totalUnitCount: 200) // 视频100 + 缩略图100 totalProgress.addChild(videoProgress, withPendingUnitCount: 100) totalProgress.addChild(thumbnailProgress, withPendingUnitCount: 100) // 上传视频 self.uploader.uploadVideo( videoURL, progress: { progress in videoProgress.completedUnitCount = Int64(progress * 100) let currentProgress = Double(totalProgress.completedUnitCount) / 200.0 progressHandler(UploadProgress( current: Int(progress * 100), total: 100, progress: currentProgress, isOriginal: true )) }, completion: { videoResult in switch videoResult { case .success(let videoUploadResult): // 4. 上传缩略图 self.uploadCompressedImage( compressedThumbnail, compressionQuality: 1.0, // 已经压缩过,不再压缩 progress: { progress in thumbnailProgress.completedUnitCount = Int64(progress.progress * 100) let currentProgress = Double(totalProgress.completedUnitCount) / 200.0 progressHandler(UploadProgress( current: 100 + Int(progress.progress * 100), total: 200, progress: currentProgress, isOriginal: false )) }, completion: { thumbnailResult in switch thumbnailResult { case .success(let thumbnailUploadResult): let result = MediaUploadResult.video( video: videoUploadResult, thumbnail: thumbnailUploadResult ) completion(.success(result)) case .failure(let error): completion(.failure(error)) } } ) case .failure(let error): completion(.failure(error)) } } ) } // 如果已有缩略图,直接使用 if let thumbnail = existingThumbnail { processThumbnail(thumbnail) } else { // 否则提取第一帧作为缩略图 MediaUtils.extractFirstFrame(from: videoURL) { result in switch result { case .success(let thumbnail): processThumbnail(thumbnail) case .failure(let error): completion(.failure(error)) } } } } // MARK: - Supporting Types /// 媒体类型 public enum MediaType { case image(UIImage) case video(URL, UIImage?) } /// 媒体上传结果 public enum MediaUploadResult { case file(ImageUploaderGetID.UploadResult) case video(video: ImageUploaderGetID.UploadResult, thumbnail: ImageUploaderGetID.UploadResult) /// 获取文件ID(对于视频,返回视频文件的ID) public var fileId: String { switch self { case .file(let result): return result.fileId case .video(let videoResult, _): return videoResult.fileId } } } /// 上传进度信息 public struct UploadProgress { public let current: Int public let total: Int public let progress: Double public let isOriginal: Bool public init(current: Int, total: Int, progress: Double, isOriginal: Bool) { self.current = current self.total = total self.progress = progress self.isOriginal = isOriginal } } /// 上传结果,包含原图和压缩图的上传信息 public struct UploadResults { public let original: ImageUploaderGetID.UploadResult public let compressed: ImageUploaderGetID.UploadResult public init(original: ImageUploaderGetID.UploadResult, compressed: ImageUploaderGetID.UploadResult) { self.original = original self.compressed = compressed } } }