import SwiftUI import PhotosUI /// 处理图片上传到远程服务器的类 /// 支持上传图片并获取服务器返回的file_id public class ImageUploaderGetID: ObservableObject { // MARK: - 类型定义 /// 上传结果 public struct UploadResult { public let fileUrl: String public let fileName: String public let fileSize: Int public let fileId: String public init(fileUrl: String, fileName: String, fileSize: Int, fileId: String) { self.fileUrl = fileUrl self.fileName = fileName self.fileSize = fileSize self.fileId = fileId } } /// 上传过程中可能发生的错误 public enum UploadError: LocalizedError { case invalidImageData case invalidURL case serverError(String) case invalidResponse case uploadFailed(Error?) case invalidFileId public var errorDescription: String? { switch self { case .invalidImageData: return "无效的图片数据" case .invalidURL: return "无效的URL" case .serverError(let message): return "服务器错误: \(message)" case .invalidResponse: return "无效的服务器响应" case .uploadFailed(let error): return "上传失败: \(error?.localizedDescription ?? "未知错误")" case .invalidFileId: return "无效的文件ID" } } } // MARK: - 属性 private let session: URLSession private let apiConfig: APIConfig.Type // MARK: - 初始化方法 /// 初始化方法 /// - Parameters: /// - session: 可选的URLSession,用于测试依赖注入 /// - apiConfig: 可选的API配置,用于测试依赖注入 public init(session: URLSession = .shared, apiConfig: APIConfig.Type = APIConfig.self) { self.session = session self.apiConfig = apiConfig } // MARK: - 公开方法 /// 上传图片到服务器 /// - Parameters: /// - image: 要上传的图片 /// - completion: 完成回调,返回Result类型的结果 public func uploadImage(_ image: UIImage, completion: @escaping (Result) -> Void) { print("🔄 开始准备上传图片...") // 1. 转换图片为Data guard let imageData = image.jpegData(compressionQuality: 0.7) else { let error = UploadError.invalidImageData print("❌ 错误:\(error.localizedDescription)") completion(.failure(error)) return } // 2. 获取上传URL getUploadURL(for: imageData) { [weak self] result in switch result { case .success((let fileId, let uploadURL)): // 3. 确认上传 self?.confirmUpload(fileId: fileId, fileName: "avatar_\(UUID().uuidString).jpg", fileSize: imageData.count) { confirmResult in completion(confirmResult) } case .failure(let error): completion(.failure(error)) } } } // MARK: - 私有方法 /// 获取上传URL private func getUploadURL(for imageData: Data, completion: @escaping (Result<(fileId: String, uploadURL: URL), Error>) -> Void) { let fileName = "avatar_\(UUID().uuidString).jpg" let parameters: [String: Any] = [ "filename": fileName, "content_type": "image/jpeg", "file_size": imageData.count ] let urlString = "\(apiConfig.baseURL)/file/generate-upload-url" guard let url = URL(string: urlString) else { completion(.failure(UploadError.invalidURL)) return } var request = URLRequest(url: url) request.httpMethod = "POST" request.allHTTPHeaderFields = apiConfig.authHeaders do { request.httpBody = try JSONSerialization.data(withJSONObject: parameters) print("📤 准备上传请求,文件名: \(fileName), 大小: \(Double(imageData.count) / 1024.0) KB") } catch { print("❌ 序列化请求参数失败: \(error.localizedDescription)") completion(.failure(error)) return } let task = session.dataTask(with: request) { data, response, error in if let error = error { completion(.failure(UploadError.uploadFailed(error))) return } guard let httpResponse = response as? HTTPURLResponse else { completion(.failure(UploadError.invalidResponse)) return } guard let data = data else { completion(.failure(UploadError.invalidResponse)) return } // 打印调试信息 if let responseString = String(data: data, encoding: .utf8) { print("📥 获取上传URL响应: \(responseString)") } do { guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], let dataDict = json["data"] as? [String: Any], let fileId = dataDict["file_id"] as? String, let uploadURLString = dataDict["upload_url"] as? String, let uploadURL = URL(string: uploadURLString) else { throw UploadError.invalidResponse } completion(.success((fileId: fileId, uploadURL: uploadURL))) } catch { completion(.failure(UploadError.invalidResponse)) } } task.resume() } /// 确认上传 private func confirmUpload(fileId: String, fileName: String, fileSize: Int, completion: @escaping (Result) -> Void) { let urlString = "\(apiConfig.baseURL)/file/confirm-upload" guard let url = URL(string: urlString) else { completion(.failure(UploadError.invalidURL)) return } var request = URLRequest(url: url) request.httpMethod = "POST" request.allHTTPHeaderFields = apiConfig.authHeaders let requestBody: [String: Any] = ["file_id": fileId] do { request.httpBody = try JSONSerialization.data(withJSONObject: requestBody) } catch { completion(.failure(error)) return } let task = session.dataTask(with: request) { data, response, error in if let error = error { completion(.failure(UploadError.uploadFailed(error))) return } guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { completion(.failure(UploadError.serverError("确认上传失败,状态码: \((response as? HTTPURLResponse)?.statusCode ?? -1)"))) return } // 创建上传结果 let uploadResult = UploadResult( fileUrl: "\(self.apiConfig.baseURL)/files/\(fileId)", fileName: fileName, fileSize: fileSize, fileId: fileId ) print("✅ 图片上传并确认成功,fileId: \(fileId)") completion(.success(uploadResult)) } task.resume() } } // MARK: - 响应模型 struct UploadURLResponse: Codable { let code: Int let message: String let data: UploadData struct UploadData: Codable { let fileId: String let uploadUrl: String enum CodingKeys: String, CodingKey { case fileId = "file_id" case uploadUrl = "upload_url" } } }