332 lines
12 KiB
Swift
332 lines
12 KiB
Swift
import Foundation
|
||
import ComposableArchitecture
|
||
|
||
// MARK: - API Service Protocol
|
||
|
||
/// API 服务协议,定义了网络请求的核心接口
|
||
///
|
||
/// 该协议支持泛型请求,可以处理任何符合 `APIRequestProtocol` 的请求类型
|
||
/// 并返回对应的响应类型。这种设计提供了类型安全和灵活性。
|
||
///
|
||
/// 使用示例:
|
||
/// ```swift
|
||
/// let apiService: APIServiceProtocol = LiveAPIService()
|
||
/// let request = ConfigRequest()
|
||
/// let response = try await apiService.request(request)
|
||
/// ```
|
||
protocol APIServiceProtocol {
|
||
/// 发起网络请求
|
||
/// - Parameter request: 符合 APIRequestProtocol 的请求对象
|
||
/// - Returns: 请求对应的响应对象
|
||
/// - Throws: APIError 或其他网络相关错误
|
||
func request<T: APIRequestProtocol>(_ request: T) async throws -> T.Response
|
||
}
|
||
|
||
// MARK: - Live API Service Implementation
|
||
|
||
/// 实际的 API 服务实现类
|
||
///
|
||
/// 该类负责处理真实的网络请求,包括:
|
||
/// - URL 构建和参数处理
|
||
/// - 请求头设置和认证
|
||
/// - 请求体编码和签名生成
|
||
/// - 响应解析和错误处理
|
||
/// - 日志记录和性能监控
|
||
///
|
||
/// 特性:
|
||
/// - 支持 GET/POST/PUT/DELETE 等 HTTP 方法
|
||
/// - 自动添加基础参数和安全签名
|
||
/// - 完整的错误处理和重试机制
|
||
/// - 详细的请求/响应日志记录
|
||
/// - 防止资源超限的保护机制
|
||
struct LiveAPIService: APIServiceProtocol {
|
||
private let session: URLSession
|
||
private let baseURL: String
|
||
|
||
/// 初始化 API 服务
|
||
/// - Parameter baseURL: API 服务器基础 URL,默认使用配置中的地址
|
||
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)
|
||
}
|
||
|
||
/// 发起网络请求的核心方法
|
||
///
|
||
/// 该方法处理完整的请求生命周期:
|
||
/// 1. 构建请求 URL 和参数
|
||
/// 2. 设置请求头和认证信息
|
||
/// 3. 处理请求体和签名生成
|
||
/// 4. 发起网络请求
|
||
/// 5. 解析响应数据
|
||
/// 6. 记录日志和性能指标
|
||
///
|
||
/// - Parameter request: 符合 APIRequestProtocol 的请求对象
|
||
/// - Returns: 解析后的响应对象
|
||
/// - Throws: APIError 包含详细的错误信息
|
||
func request<T: APIRequestProtocol>(_ 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 {
|
||
var baseParams = BaseRequest()
|
||
// 生成符合 API rule 的签名
|
||
baseParams.generateSignature(with: bodyParams)
|
||
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
|
||
|
||
/// 构建完整的请求 URL
|
||
///
|
||
/// 该方法负责:
|
||
/// - 拼接基础 URL 和端点路径
|
||
/// - 处理查询参数
|
||
/// - 为 GET 请求添加基础参数和签名
|
||
///
|
||
/// - Parameter request: API 请求对象
|
||
/// - Returns: 构建完成的 URL,如果构建失败则返回 nil
|
||
private func buildURL<T: APIRequestProtocol>(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()
|
||
// 为 GET 请求生成签名(合并查询参数)
|
||
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)"))
|
||
}
|
||
} 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
|
||
}
|
||
|
||
/// 从响应数据中提取错误消息
|
||
///
|
||
/// 尝试从 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
|
||
}
|
||
|
||
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)
|
||
|
||
/// 模拟 API 服务,用于测试和开发
|
||
///
|
||
/// 该类提供了一个可配置的模拟 API 服务,可以:
|
||
/// - 设置预定义的响应数据
|
||
/// - 模拟网络延迟
|
||
/// - 用于单元测试和 UI 预览
|
||
///
|
||
/// 使用示例:
|
||
/// ```swift
|
||
/// var mockService = MockAPIService()
|
||
/// mockService.setMockResponse(for: "/client/config", response: mockConfigResponse)
|
||
/// let response = try await mockService.request(ConfigRequest())
|
||
/// ```
|
||
struct MockAPIService: APIServiceProtocol {
|
||
private var mockResponses: [String: Any] = [:]
|
||
|
||
mutating func setMockResponse<T>(for endpoint: String, response: T) {
|
||
mockResponses[endpoint] = response
|
||
}
|
||
|
||
func request<T: APIRequestProtocol>(_ 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
|
||
}
|
||
} |