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 } } public protocol NetworkServiceProtocol { func postWithToken( path: String, parameters: [String: Any], completion: @escaping (Result) -> Void ) @discardableResult func upload( request: URLRequest, fileData: Data, onProgress: @escaping (Double) -> Void, completion: @escaping (Result<(Data?, URLResponse), Error>) -> Void ) -> URLSessionUploadTask? } extension NetworkService: NetworkServiceProtocol { public func postWithToken( path: String, parameters: [String: Any], completion: @escaping (Result) -> Void ) { var headers = [String: String]() if let token = KeychainHelper.getAccessToken() { headers["Authorization"] = "Bearer \(token)" } post(path: path, parameters: parameters, headers: headers, completion: completion) } @discardableResult public func upload( request: URLRequest, fileData: Data, onProgress: @escaping (Double) -> Void, completion: @escaping (Result<(Data?, URLResponse), Error>) -> Void ) -> URLSessionUploadTask? { var request = request // Set content length header if not already set if request.value(forHTTPHeaderField: "Content-Length") == nil { request.setValue("\(fileData.count)", forHTTPHeaderField: "Content-Length") } var progressObserver: NSKeyValueObservation? let task = URLSession.shared.uploadTask(with: request, from: fileData) { [weak self] data, response, error in // Invalidate the progress observer when the task completes progressObserver?.invalidate() if let error = error { completion(.failure(error)) return } guard let response = response else { completion(.failure(NetworkError.invalidURL)) return } completion(.success((data, response))) } // Add progress tracking if available if #available(iOS 11.0, *) { progressObserver = task.progress.observe(\.fractionCompleted) { progressValue, _ in DispatchQueue.main.async { onProgress(progressValue.fractionCompleted) } } } task.resume() return task } } public enum NetworkError: Error { case invalidURL case noData case decodingError(Error) case serverError(String) case unauthorized case other(Error) case networkError(Error) case unknownError(Error) case invalidParameters public 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)" case .invalidParameters: return "无效的参数" } } } 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: 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) } // 添加认证头 - 排除登录接口 if !path.contains("/iam/login/") { 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 { if JSONSerialization.isValidJSONObject(parameters) { request.httpBody = try JSONSerialization.data(withJSONObject: parameters) } else { print("❌ [Network][#\(requestId)][\(method) \(path)] 参数不是有效的JSON对象") completion(.failure(.invalidParameters)) return } } 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: Any? = nil, headers: [String: String]? = nil, completion: @escaping (Result) -> Void ) { var params: Any? if let parameters = parameters { if let dict = parameters as? [String: Any] { params = dict } else if let array = parameters as? [Any] { params = array } else { print("❌ [Network] POST 请求参数类型不支持") completion(.failure(.invalidParameters)) return } } request("POST", path: path, parameters: params, headers: headers, completion: completion) } /// POST 请求(带Token) func postWithToken( path: String, parameters: 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 ) { var headers = headers ?? [:] if let token = KeychainHelper.getAccessToken() { headers["Authorization"] = "Bearer \(token)" } 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) } }