import Foundation import ComposableArchitecture // MARK: - API Service Protocol protocol APIServiceProtocol { func request(_ request: T) async throws -> T.Response } // MARK: - Live API Service Implementation struct LiveAPIService: APIServiceProtocol { private let session: URLSession private let baseURL: String init(baseURL: String = APIConfiguration.baseURL) { self.baseURL = baseURL // 配置 URLSession 以防止资源超限问题 let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = APIConfiguration.timeout config.timeoutIntervalForResource = APIConfiguration.timeout * 2 config.waitsForConnectivity = true config.allowsCellularAccess = true // 设置数据大小限制 config.urlCache = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil) self.session = URLSession(configuration: config) } func request(_ request: T) async throws -> T.Response { let startTime = Date() // 构建 URL guard let url = buildURL(for: request) else { throw APIError.invalidURL } // 构建 URLRequest var urlRequest = URLRequest(url: url) urlRequest.httpMethod = request.method.rawValue urlRequest.timeoutInterval = request.timeout // 设置请求头 var headers = APIConfiguration.defaultHeaders if let customHeaders = request.headers { headers.merge(customHeaders) { _, 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 { let baseParams = BaseRequest() let baseDict = try baseParams.toDictionary() finalBody.merge(baseDict) { existing, _ in existing } } requestBody = try JSONSerialization.data(withJSONObject: finalBody, options: []) urlRequest.httpBody = requestBody } catch { throw APIError.decodingError("请求体编码失败: \(error.localizedDescription)") } } // 记录请求日志,传递完整的 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 { throw APIError.networkError("无效的响应类型") } // 检查数据大小 if data.count > APIConfiguration.maxDataSize { APILogger.logError(APIError.resourceTooLarge, url: url, duration: duration) 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) throw APIError.httpError(statusCode: httpResponse.statusCode, message: errorMessage) } // 检查数据是否为空 guard !data.isEmpty else { throw APIError.noData } // 解析响应数据 do { let decoder = JSONDecoder() let decodedResponse = try decoder.decode(T.Response.self, from: data) APILogger.logDecodedResponse(decodedResponse, type: T.Response.self) return decodedResponse } catch { throw APIError.decodingError("响应解析失败: \(error.localizedDescription)") } } catch let error as APIError { let duration = Date().timeIntervalSince(startTime) APILogger.logError(error, url: url, duration: duration) throw error } catch { let duration = Date().timeIntervalSince(startTime) let apiError = mapSystemError(error) APILogger.logError(apiError, url: url, duration: duration) throw apiError } } // MARK: - Private Helper Methods 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 { let baseParams = BaseRequest() let baseDict = try baseParams.toDictionary() for (key, value) in baseDict { queryItems.append(URLQueryItem(name: key, value: "\(value)")) } } catch { print("警告:无法添加基础参数到查询字符串") } } // 添加自定义查询参数 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 } 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 } return nil } 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) struct MockAPIService: APIServiceProtocol { private var mockResponses: [String: Any] = [:] mutating 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 private enum APIServiceKey: DependencyKey { static let liveValue: APIServiceProtocol = LiveAPIService() static let testValue: APIServiceProtocol = MockAPIService() } extension DependencyValues { var apiService: APIServiceProtocol { get { self[APIServiceKey.self] } set { self[APIServiceKey.self] = newValue } } } // 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 } }