import Foundation // MARK: - API Service Protocol /// API 服务协议,定义了网络请求的核心接口 /// /// 该协议支持泛型请求,可以处理任何符合 `APIRequestProtocol` 的请求类型 /// 并返回对应的响应类型。这种设计提供了类型安全和灵活性。 /// /// 使用示例: /// ```swift /// let apiService: APIServiceProtocol = LiveAPIService() /// let request = ConfigRequest() /// let response = try await apiService.request(request) /// ``` protocol APIServiceProtocol: Sendable { /// 发起网络请求 /// - Parameter request: 符合 APIRequestProtocol 的请求对象 /// - Returns: 请求对应的响应对象 /// - Throws: APIError 或其他网络相关错误 func request(_ request: T) async throws -> T.Response } // MARK: - Live API Service Implementation /// 实际的 API 服务实现类 /// /// 该类负责处理真实的网络请求,包括: /// - URL 构建和参数处理 /// - 请求头设置和认证 /// - 请求体编码和签名生成 /// - 响应解析和错误处理 /// - 日志记录和性能监控 /// /// 特性: /// - 支持 GET/POST/PUT/DELETE 等 HTTP 方法 /// - 自动添加基础参数和安全签名 /// - 完整的错误处理和重试机制 /// - 详细的请求/响应日志记录 /// - 防止资源超限的保护机制 struct LiveAPIService: APIServiceProtocol, Sendable { private let session: URLSession private let baseURL: String // 缓存主 actor 配置,避免并发隔离问题 private static let cachedBaseURL: String = APIConfiguration.baseURL private static let cachedTimeout: TimeInterval = APIConfiguration.timeout /// 初始化 API 服务 /// - Parameter baseURL: API 服务器基础 URL,默认使用静态缓存 init(baseURL: String = LiveAPIService.cachedBaseURL) { self.baseURL = baseURL // 配置 URLSession 以防止资源超限问题 let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = LiveAPIService.cachedTimeout config.timeoutIntervalForResource = LiveAPIService.cachedTimeout * 2 config.waitsForConnectivity = true config.allowsCellularAccess = true // 设置数据大小限制 config.urlCache = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil) self.session = URLSession(configuration: config) } /// 发起网络请求的核心方法 /// /// 该方法处理完整的请求生命周期: /// 1. 构建请求 URL 和参数 /// 2. 设置请求头和认证信息 /// 3. 处理请求体和签名生成 /// 4. 发起网络请求 /// 5. 解析响应数据 /// 6. 记录日志和性能指标 /// /// - Parameter request: 符合 APIRequestProtocol 的请求对象 /// - Returns: 解析后的响应对象 /// - Throws: APIError 包含详细的错误信息 func request(_ request: T) async throws -> T.Response { let startTime = Date() // 开始 Loading 管理 let loadingId = await APILoadingManager.shared.startLoading( shouldShowLoading: request.shouldShowLoading, shouldShowError: request.shouldShowError ) // 构建 URL guard let url = await buildURL(for: request) else { await APILoadingManager.shared.setError(loadingId, errorMessage: APIError.invalidURL.localizedDescription) throw APIError.invalidURL } // 构建 URLRequest var urlRequest = URLRequest(url: url) urlRequest.httpMethod = request.method.rawValue urlRequest.timeoutInterval = request.timeout urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") // 设置请求头(必须 await 获取) var headers = await APIConfiguration.defaultHeaders() if let customHeaders = request.headers { headers.merge(customHeaders) { _, new in new } } // 添加自定义请求头支持 if let additionalHeaders = request.customHeaders { headers.merge(additionalHeaders) { _, new in new } } for (key, value) in headers { urlRequest.setValue(value, forHTTPHeaderField: key) } // 处理请求体 var requestBody: Data? = nil if request.method != .GET, let bodyParams = request.bodyParameters { do { var finalBody = bodyParams // 如果需要包含基础参数,则先合并所有参数,再统一生成签名 if request.includeBaseParameters { // 第一步:创建基础参数实例(不包含签名) var baseParams = await BaseRequest() // 第二步:基于所有参数(bodyParams + 基础参数)统一生成签名 baseParams.generateSignature(with: bodyParams) // 第三步:将包含正确签名的基础参数合并到最终请求体 let baseDict = try baseParams.toDictionary() finalBody.merge(baseDict) { _, new in new } // 基础参数(包括签名)优先 debugInfoSync("🔐 签名生成完成 - 基于所有参数统一生成: \(baseParams.pubSign)") } requestBody = try JSONSerialization.data(withJSONObject: finalBody, options: []) urlRequest.httpBody = requestBody // urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") // HTTP Body 的详细输出由 APILogger 统一处理(带脱敏)。这里不再重复输出。 } catch { let encodingError = APIError.decodingError("请求体编码失败: \(error.localizedDescription)") await APILoadingManager.shared.setError(loadingId, errorMessage: encodingError.localizedDescription) throw encodingError } } // 记录请求日志,传递完整的 headers 信息 APILogger.logRequest(request, url: url, body: requestBody, finalHeaders: headers) do { // 发起请求 let (data, response) = try await session.data(for: urlRequest) let duration = Date().timeIntervalSince(startTime) // 检查响应 guard let httpResponse = response as? HTTPURLResponse else { let networkError = APIError.networkError("无效的响应类型") await APILoadingManager.shared.setError(loadingId, errorMessage: networkError.localizedDescription) throw networkError } // 检查数据大小 if data.count > APIConfiguration.maxDataSize { APILogger.logError(APIError.resourceTooLarge, url: url, duration: duration) await APILoadingManager.shared.setError(loadingId, errorMessage: APIError.resourceTooLarge.localizedDescription) throw APIError.resourceTooLarge } // 记录响应日志 APILogger.logResponse(data: data, response: httpResponse, duration: duration) // 性能警告 APILogger.logPerformanceWarning(duration: duration) // 检查 HTTP 状态码 guard 200...299 ~= httpResponse.statusCode else { let errorMessage = extractErrorMessage(from: data) let httpError = APIError.httpError(statusCode: httpResponse.statusCode, message: errorMessage) await APILoadingManager.shared.setError(loadingId, errorMessage: httpError.localizedDescription) throw httpError } // 检查数据是否为空 guard !data.isEmpty else { await APILoadingManager.shared.setError(loadingId, errorMessage: APIError.noData.localizedDescription) throw APIError.noData } // 解析响应数据 do { let decoder = JSONDecoder() let decodedResponse = try decoder.decode(T.Response.self, from: data) APILogger.logDecodedResponse(decodedResponse, type: T.Response.self) // 请求成功,完成 loading await APILoadingManager.shared.finishLoading(loadingId) return decodedResponse } catch { let decodingError = APIError.decodingError("响应解析失败: \(error.localizedDescription)") await APILoadingManager.shared.setError(loadingId, errorMessage: decodingError.localizedDescription) throw decodingError } } catch let error as APIError { let duration = Date().timeIntervalSince(startTime) APILogger.logError(error, url: url, duration: duration) await APILoadingManager.shared.setError(loadingId, errorMessage: error.localizedDescription) throw error } catch { let duration = Date().timeIntervalSince(startTime) let apiError = mapSystemError(error) APILogger.logError(apiError, url: url, duration: duration) await APILoadingManager.shared.setError(loadingId, errorMessage: apiError.localizedDescription) throw apiError } } // MARK: - 用户信息更新 func updateUser(request: UpdateUserRequest) async throws -> UpdateUserResponse { try await self.request(request) } // MARK: - Private Helper Methods /// 构建完整的请求 URL /// /// 该方法负责: /// - 拼接基础 URL 和端点路径 /// - 处理查询参数 /// - 为 GET 请求添加基础参数和签名 /// /// - Parameter request: API 请求对象 /// - Returns: 构建完成的 URL,如果构建失败则返回 nil @MainActor private func buildURL(for request: T) -> URL? { guard var urlComponents = URLComponents(string: baseURL + request.endpoint) else { return nil } // 处理查询参数 var queryItems: [URLQueryItem] = [] // 对于 GET 请求,将基础参数添加到查询参数中 if request.method == .GET && request.includeBaseParameters { do { // 第一步:创建基础参数实例(不包含签名) var baseParams = BaseRequest() // 第二步:基于所有参数(queryParams + 基础参数)统一生成签名 let queryParamsDict = request.queryParameters ?? [:] baseParams.generateSignature(with: queryParamsDict) // 第三步:将包含正确签名的基础参数添加到查询参数 let baseDict = try baseParams.toDictionary() for (key, value) in baseDict { queryItems.append(URLQueryItem(name: key, value: "\(value)")) } debugInfoSync("🔐 GET请求签名生成完成 - 基于所有参数统一生成: \(baseParams.pubSign)") } catch { debugWarnSync("警告:无法添加基础参数到查询字符串") } } // 添加自定义查询参数 if let customParams = request.queryParameters { for (key, value) in customParams { queryItems.append(URLQueryItem(name: key, value: value)) } } if !queryItems.isEmpty { urlComponents.queryItems = queryItems } return urlComponents.url } /// 从响应数据中提取错误消息 /// /// 尝试从 JSON 响应中提取错误信息,支持多种常见的错误字段格式 /// /// - Parameter data: 响应数据 /// - Returns: 提取到的错误消息,如果没有找到则返回 nil private func extractErrorMessage(from data: Data) -> String? { guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil } // 尝试多种可能的错误消息字段 if let message = json["message"] as? String { return message } else if let error = json["error"] as? String { return error } else if let msg = json["msg"] as? String { return msg } else if let detail = json["detail"] as? String { return detail } else if let errorDescription = json["error_description"] as? String { return errorDescription } else if let errorDict = json["error"] as? [String: Any], let nestedMsg = errorDict["message"] as? String { return nestedMsg } else if let errors = json["errors"] as? [[String: Any]], let firstMsg = errors.first? ["message"] as? String { return firstMsg } return nil } /// 将系统错误映射为 API 错误 /// /// 将 URLError 等系统级错误转换为统一的 APIError 类型, /// 便于上层代码进行统一的错误处理 /// /// - Parameter error: 系统错误 /// - Returns: 映射后的 APIError private func mapSystemError(_ error: Error) -> APIError { if let urlError = error as? URLError { switch urlError.code { case .timedOut: return .timeout case .cannotConnectToHost, .notConnectedToInternet: return .networkError(urlError.localizedDescription) case .dataLengthExceedsMaximum: return .resourceTooLarge default: return .networkError(urlError.localizedDescription) } } return .unknown(error.localizedDescription) } } // MARK: - Mock API Service (for testing) /// 并发安全的 Mock API Service actor MockAPIServiceActor: APIServiceProtocol, Sendable { private var mockResponses: [String: Any] = [:] func setMockResponse(for endpoint: String, response: T) { mockResponses[endpoint] = response } func request(_ request: T) async throws -> T.Response { try await Task.sleep(nanoseconds: 500_000_000) // 0.5 秒 if let mockResponse = mockResponses[request.endpoint] as? T.Response { return mockResponse } throw APIError.noData } } // MARK: - TCA Dependency Integration (optional) #if canImport(ComposableArchitecture) import ComposableArchitecture private enum APIServiceKey: DependencyKey { static let liveValue: any APIServiceProtocol & Sendable = LiveAPIService() static let testValue: any APIServiceProtocol & Sendable = MockAPIServiceActor() } extension DependencyValues { var apiService: (any APIServiceProtocol & Sendable) { get { self[APIServiceKey.self] } set { self[APIServiceKey.self] = newValue } } } #endif // MARK: - BaseRequest Dictionary Conversion extension BaseRequest { func toDictionary() throws -> [String: Any] { let data = try JSONEncoder().encode(self) guard let dictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { throw APIError.decodingError("无法转换基础参数为字典") } return dictionary } }