407 lines
15 KiB
Swift
407 lines
15 KiB
Swift
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<Data, NetworkError>) -> Void, Int)] = []
|
||
|
||
private init() {}
|
||
|
||
// MARK: - 基础请求方法
|
||
private func request<T: Decodable>(
|
||
_ method: String,
|
||
path: String,
|
||
parameters: [String: Any]? = nil,
|
||
headers: [String: String]? = nil,
|
||
completion: @escaping (Result<T, NetworkError>) -> 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<T, NetworkError>) in
|
||
completion(result)
|
||
}
|
||
)
|
||
}
|
||
|
||
// 开始请求
|
||
task.resume()
|
||
}
|
||
|
||
private func handleResponse<T: Decodable>(
|
||
requestId: Int,
|
||
method: String,
|
||
path: String,
|
||
data: Data?,
|
||
response: URLResponse?,
|
||
error: Error?,
|
||
request: URLRequest,
|
||
duration: String,
|
||
completion: @escaping (Result<T, NetworkError>) -> 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<Data, NetworkError>.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<T: Decodable>(
|
||
path: String,
|
||
parameters: [String: Any]? = nil,
|
||
headers: [String: String]? = nil,
|
||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||
) {
|
||
request("GET", path: path, parameters: parameters, headers: headers, completion: completion)
|
||
}
|
||
|
||
/// POST 请求
|
||
func post<T: Decodable>(
|
||
path: String,
|
||
parameters: [String: Any]? = nil,
|
||
headers: [String: String]? = nil,
|
||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||
) {
|
||
request("POST", path: path, parameters: parameters, headers: headers, completion: completion)
|
||
}
|
||
|
||
/// POST 请求(带Token)
|
||
func postWithToken<T: Decodable>(
|
||
path: String,
|
||
parameters: [String: Any]? = nil,
|
||
headers: [String: String]? = nil,
|
||
completion: @escaping (Result<T, NetworkError>) -> 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<T: Decodable>(
|
||
path: String,
|
||
parameters: [String: Any]? = nil,
|
||
headers: [String: String]? = nil,
|
||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||
) {
|
||
request("DELETE", path: path, parameters: parameters, headers: headers, completion: completion)
|
||
}
|
||
|
||
/// PUT 请求
|
||
func put<T: Decodable>(
|
||
path: String,
|
||
parameters: [String: Any]? = nil,
|
||
headers: [String: String]? = nil,
|
||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||
) {
|
||
request("PUT", path: path, parameters: parameters, headers: headers, completion: completion)
|
||
}
|
||
}
|