208 lines
7.1 KiB
Swift
208 lines
7.1 KiB
Swift
import Foundation
|
||
|
||
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 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
|
||
) {
|
||
// 构建URL
|
||
guard let url = URL(string: APIConfig.baseURL + path) else {
|
||
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 {
|
||
completion(.failure(.other(error)))
|
||
return
|
||
}
|
||
}
|
||
|
||
// 打印请求信息(调试用)
|
||
print("🌐 [Network] \(method) \(url.absoluteString)")
|
||
if let headers = request.allHTTPHeaderFields {
|
||
print("📤 Headers: \(headers)")
|
||
}
|
||
if let body = request.httpBody, let bodyString = String(data: body, encoding: .utf8) {
|
||
print("📦 Body: \(bodyString)")
|
||
}
|
||
|
||
// 创建任务
|
||
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
|
||
// 处理响应
|
||
self?.handleResponse(data: data, response: response, error: error, completion: completion)
|
||
}
|
||
|
||
// 开始请求
|
||
task.resume()
|
||
}
|
||
|
||
private func handleResponse<T: Decodable>(
|
||
data: Data?,
|
||
response: URLResponse?,
|
||
error: Error?,
|
||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||
) {
|
||
// 打印响应信息(调试用)
|
||
if let httpResponse = response as? HTTPURLResponse {
|
||
print("📥 [Network] Status: \(httpResponse.statusCode) \(HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode))")
|
||
if let headers = httpResponse.allHeaderFields as? [String: Any] {
|
||
print("📥 Headers: \(headers)")
|
||
}
|
||
|
||
// 检查状态码
|
||
if !(200...299).contains(httpResponse.statusCode) {
|
||
print("❌ [Network] 请求失败,状态码: \(httpResponse.statusCode)")
|
||
if let data = data, let errorResponse = String(data: data, encoding: .utf8) {
|
||
print("❌ [Network] 错误响应: \(errorResponse)")
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理网络错误
|
||
if let error = error {
|
||
print("❌ [Network] 网络请求错误: \(error.localizedDescription)")
|
||
completion(.failure(.networkError(error)))
|
||
return
|
||
}
|
||
|
||
// 检查数据是否存在
|
||
guard let data = data else {
|
||
print("❌ [Network] 没有收到数据")
|
||
completion(.failure(.noData))
|
||
return
|
||
}
|
||
|
||
// 打印响应数据(调试用)
|
||
if let responseString = String(data: data, encoding: .utf8) {
|
||
print("📥 [Network] 响应数据: \(responseString)")
|
||
}
|
||
|
||
do {
|
||
// 解析JSON数据
|
||
let decoder = JSONDecoder()
|
||
let result = try decoder.decode(T.self, from: data)
|
||
completion(.success(result))
|
||
} catch let decodingError as DecodingError {
|
||
print("❌ [Network] JSON解析失败: \(decodingError.localizedDescription)")
|
||
if let dataString = String(data: data, encoding: .utf8) {
|
||
print("📋 [Network] 原始响应: \(dataString)")
|
||
}
|
||
completion(.failure(.decodingError(decodingError)))
|
||
} catch {
|
||
print("❌ [Network] 未知错误: \(error.localizedDescription)")
|
||
completion(.failure(.unknownError(error)))
|
||
}
|
||
}
|
||
|
||
// 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)
|
||
}
|
||
|
||
/// 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)
|
||
}
|
||
}
|