import Foundation // 添加登出通知 extension Notification.Name { static let userDidLogoutNotification = Notification.Name("UserDidLogoutNotification") } // 请求标识符 private struct RequestIdentifier { static var currentId: Int = 0 static var lock = NSLock() static func next() -> Int { lock.lock() defer { lock.unlock() } currentId += 1 return currentId } } enum NetworkError: Error { case invalidURL case noData case decodingError(Error) case serverError(String) case unauthorized case other(Error) case networkError(Error) case unknownError(Error) var localizedDescription: String { switch self { case .invalidURL: return "无效的URL" case .noData: return "没有收到数据" case .decodingError(let error): return "数据解析错误: \(error.localizedDescription)" case .serverError(let message): return "服务器错误: \(message)" case .unauthorized: return "未授权,请重新登录" case .other(let error): return error.localizedDescription case .networkError(let error): return "网络请求错误: \(error.localizedDescription)" case .unknownError(let error): return "未知错误: \(error.localizedDescription)" } } } class NetworkService { static let shared = NetworkService() // 默认请求头 private let defaultHeaders: [String: String] = [ "Content-Type": "application/json", "Accept": "application/json" ] private var isRefreshing = false private var requestsToRetry: [(URLRequest, (Result) -> Void, Int)] = [] private init() {} // MARK: - 基础请求方法 private func request( _ method: String, path: String, parameters: [String: Any]? = nil, headers: [String: String]? = nil, completion: @escaping (Result) -> Void ) { // 生成请求ID let requestId = RequestIdentifier.next() // 构建URL let fullURL = APIConfig.baseURL + path guard let url = URL(string: fullURL) else { print("❌ [Network][#\(requestId)][\(method) \(path)] 无效的URL") completion(.failure(.invalidURL)) return } // 创建请求 var request = URLRequest(url: url) request.httpMethod = method // 设置请求头 - 合并默认头、认证头和自定义头 defaultHeaders.forEach { key, value in request.setValue(value, forHTTPHeaderField: key) } // 添加认证头 APIConfig.authHeaders.forEach { key, value in request.setValue(value, forHTTPHeaderField: key) } // 添加自定义头(如果提供) headers?.forEach { key, value in request.setValue(value, forHTTPHeaderField: key) } // 设置请求体(如果是POST/PUT请求) if let parameters = parameters, (method == "POST" || method == "PUT") { do { request.httpBody = try JSONSerialization.data(withJSONObject: parameters) } catch { print("❌ [Network][#\(requestId)][\(method) \(path)] 参数序列化失败: \(error.localizedDescription)") completion(.failure(.other(error))) return } } // 打印请求信息 print(""" 🌐 [Network][#\(requestId)][\(method) \(path)] 开始请求 🔗 URL: \(url.absoluteString) 📤 Headers: \(request.allHTTPHeaderFields ?? [:]) 📦 Body: \(request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? "") """) // 创建任务 let startTime = Date() let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in let duration = String(format: "%.3fs", Date().timeIntervalSince(startTime)) // 处理响应 self?.handleResponse( requestId: requestId, method: method, path: path, data: data, response: response, error: error, request: request, duration: duration, completion: { (result: Result) in completion(result) } ) } // 开始请求 task.resume() } private func handleResponse( requestId: Int, method: String, path: String, data: Data?, response: URLResponse?, error: Error?, request: URLRequest, duration: String, completion: @escaping (Result) -> Void ) { // 打印响应信息 if let httpResponse = response as? HTTPURLResponse { let statusCode = httpResponse.statusCode let statusMessage = HTTPURLResponse.localizedString(forStatusCode: statusCode) // 处理401未授权 if statusCode == 401 { print(""" 🔑 [Network][#\(requestId)][\(method) \(path)] 检测到未授权,尝试刷新token... ⏱️ 耗时: \(duration) """) // 将请求加入重试队列 let dataResult = data.flatMap { Result.success($0) } ?? .failure(.noData) self.requestsToRetry.append((request, { result in switch result { case .success(let data): do { let decoder = JSONDecoder() let result = try decoder.decode(T.self, from: data) print(""" ✅ [Network][#\(requestId)][\(method) \(path)] 重试成功 ⏱️ 总耗时: \(duration) (包含token刷新时间) """) completion(.success(result)) } catch let decodingError as DecodingError { print(""" ❌ [Network][#\(requestId)][\(method) \(path)] JSON解析失败 🔍 错误: \(decodingError.localizedDescription) 📦 原始数据: \(String(data: data, encoding: .utf8) ?? "") """) completion(.failure(.decodingError(decodingError))) } catch { print(""" ❌ [Network][#\(requestId)][\(method) \(path)] 未知错误 🔍 错误: \(error.localizedDescription) """) completion(.failure(.unknownError(error))) } case .failure(let error): print(""" ❌ [Network][#\(requestId)][\(method) \(path)] 重试失败 🔍 错误: \(error.localizedDescription) """) completion(.failure(error)) } }, requestId)) // 如果没有正在刷新的请求,则开始刷新token if !isRefreshing { refreshAndRetryRequests() } return } // 处理其他错误状态码 if !(200...299).contains(statusCode) { let errorMessage = data.flatMap { String(data: $0, encoding: .utf8) } ?? "" print(""" ❌ [Network][#\(requestId)][\(method) \(path)] 请求失败 📊 状态码: \(statusCode) (\(statusMessage)) ⏱️ 耗时: \(duration) 🔍 错误响应: \(errorMessage) """) completion(.failure(.serverError("状态码: \(statusCode), 响应: \(errorMessage)"))) return } // 成功响应 print(""" ✅ [Network][#\(requestId)][\(method) \(path)] 请求成功 📊 状态码: \(statusCode) (\(statusMessage)) ⏱️ 耗时: \(duration) """) } // 处理网络错误 if let error = error { print(""" ❌ [Network][#\(requestId)][\(method) \(path)] 网络请求失败 ⏱️ 耗时: \(duration) 🔍 错误: \(error.localizedDescription) """) completion(.failure(.networkError(error))) return } // 检查数据是否存在 guard let data = data else { print(""" ❌ [Network][#\(requestId)][\(method) \(path)] 没有收到数据 ⏱️ 耗时: \(duration) """) completion(.failure(.noData)) return } // 打印响应数据(调试用) if let responseString = String(data: data, encoding: .utf8) { print(""" 📥 [Network][#\(requestId)][\(method) \(path)] 响应数据: \(responseString.prefix(1000))\(responseString.count > 1000 ? "..." : "") """) } do { // 解析JSON数据 let decoder = JSONDecoder() let result = try decoder.decode(T.self, from: data) completion(.success(result)) } catch let decodingError as DecodingError { print(""" ❌ [Network][#\(requestId)][\(method) \(path)] JSON解析失败 🔍 错误: \(decodingError.localizedDescription) 📦 原始数据: \(String(data: data, encoding: .utf8) ?? "") """) completion(.failure(.decodingError(decodingError))) } catch { print(""" ❌ [Network][#\(requestId)][\(method) \(path)] 未知错误 🔍 错误: \(error.localizedDescription) """) completion(.failure(.unknownError(error))) } } private func refreshAndRetryRequests() { guard !isRefreshing else { return } isRefreshing = true let refreshStartTime = Date() print("🔄 [Network] 开始刷新Token...") TokenManager.shared.refreshToken { [weak self] success, _ in guard let self = self else { return } let refreshDuration = String(format: "%.3fs", Date().timeIntervalSince(refreshStartTime)) if success { print(""" ✅ [Network] Token刷新成功 ⏱️ 耗时: \(refreshDuration) 🔄 准备重试\(self.requestsToRetry.count)个请求... """) // 重试所有待处理的请求 let requestsToRetry = self.requestsToRetry self.requestsToRetry.removeAll() for (request, completion, requestId) in requestsToRetry { var newRequest = request if let token = KeychainHelper.getAccessToken() { newRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") } let task = URLSession.shared.dataTask(with: newRequest) { data, response, error in if let data = data { completion(.success(data)) } else if let error = error { completion(.failure(.networkError(error))) } else { completion(.failure(.noData)) } } task.resume() } } else { print(""" ❌ [Network] Token刷新失败 ⏱️ 耗时: \(refreshDuration) 🚪 清除登录状态... """) // 清除token并通知需要重新登录 TokenManager.shared.clearTokens() DispatchQueue.main.async { NotificationCenter.default.post(name: .userDidLogoutNotification, object: nil) } // 所有待处理的请求都返回未授权错误 self.requestsToRetry.forEach { _, completion, _ in completion(.failure(.unauthorized)) } self.requestsToRetry.removeAll() } self.isRefreshing = false } } // MARK: - 公共方法 /// GET 请求 func get( path: String, parameters: [String: Any]? = nil, headers: [String: String]? = nil, completion: @escaping (Result) -> Void ) { request("GET", path: path, parameters: parameters, headers: headers, completion: completion) } /// POST 请求 func post( path: String, parameters: [String: Any]? = nil, headers: [String: String]? = nil, completion: @escaping (Result) -> Void ) { request("POST", path: path, parameters: parameters, headers: headers, completion: completion) } /// POST 请求(带Token) func postWithToken( path: String, parameters: [String: Any]? = nil, headers: [String: String]? = nil, completion: @escaping (Result) -> Void ) { var headers = headers ?? [:] if let token = KeychainHelper.getAccessToken() { headers["Authorization"] = "Bearer \(token)" } post(path: path, parameters: parameters, headers: headers, completion: completion) } /// DELETE 请求 func delete( path: String, parameters: [String: Any]? = nil, headers: [String: String]? = nil, completion: @escaping (Result) -> Void ) { request("DELETE", path: path, parameters: parameters, headers: headers, completion: completion) } /// PUT 请求 func put( path: String, parameters: [String: Any]? = nil, headers: [String: String]? = nil, completion: @escaping (Result) -> Void ) { request("PUT", path: path, parameters: parameters, headers: headers, completion: completion) } }