|
|
@@ -0,0 +1,375 @@
|
|
|
+//
|
|
|
+// LNHttpManager.swift
|
|
|
+// Lanu
|
|
|
+//
|
|
|
+// Created by OneeChan on 2025/11/6.
|
|
|
+//
|
|
|
+
|
|
|
+import Foundation
|
|
|
+
|
|
|
+/// HTTP请求方法枚举
|
|
|
+enum HTTPMethod: String {
|
|
|
+ case get = "GET"
|
|
|
+ case post = "POST"
|
|
|
+ case put = "PUT"
|
|
|
+ case delete = "DELETE"
|
|
|
+}
|
|
|
+
|
|
|
+/// HTTP请求错误枚举
|
|
|
+enum LNHttpError: Error, LocalizedError {
|
|
|
+ case invalidURL
|
|
|
+ case invalidResponse
|
|
|
+ case networkError(Error)
|
|
|
+ case parsingError(Error)
|
|
|
+ case statusCode(Int)
|
|
|
+ case serverError(String)
|
|
|
+
|
|
|
+ var errorDescription: String {
|
|
|
+ switch self {
|
|
|
+ case .invalidURL:
|
|
|
+ return "无效的URL"
|
|
|
+ case .invalidResponse:
|
|
|
+ return "无效的响应"
|
|
|
+ case .networkError(let error):
|
|
|
+ return "网络错误: \(error.localizedDescription)"
|
|
|
+ case .parsingError(let error):
|
|
|
+ return "解析错误: \(error.localizedDescription)"
|
|
|
+ case .statusCode(let code):
|
|
|
+ return "请求失败,状态码: \(code)"
|
|
|
+ case .serverError(let error):
|
|
|
+ return error
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// HTTP请求管理器
|
|
|
+class LNHttpManager {
|
|
|
+ static let shared = LNHttpManager()
|
|
|
+ private let session: URLSession
|
|
|
+
|
|
|
+ private init() {
|
|
|
+ let configuration = URLSessionConfiguration.default
|
|
|
+ configuration.timeoutIntervalForRequest = 30
|
|
|
+ configuration.timeoutIntervalForResource = 60
|
|
|
+ session = URLSession(configuration: configuration)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 通用HTTP请求方法
|
|
|
+ /// - Parameters:
|
|
|
+ /// - urlString: 请求URL字符串
|
|
|
+ /// - method: HTTP方法
|
|
|
+ /// - parameters: 请求参数
|
|
|
+ /// - headers: 请求头
|
|
|
+ /// - completion: 完成回调
|
|
|
+ func request<T: Decodable>(
|
|
|
+ path: String,
|
|
|
+ method: HTTPMethod = .get,
|
|
|
+ parameters: [String: Any]? = nil,
|
|
|
+ headers: [String: String]? = nil,
|
|
|
+ completion: @escaping (T?, LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ var sign = ""
|
|
|
+ var commonHeader: [String: String] = [:]
|
|
|
+
|
|
|
+ let id = "\(Int(curTimeInNano))"
|
|
|
+ sign += id
|
|
|
+ commonHeader["id"] = id
|
|
|
+
|
|
|
+ let udid = curDeviceId
|
|
|
+ sign += udid
|
|
|
+ commonHeader["udid"] = udid
|
|
|
+
|
|
|
+ let app = curAppBundleIdentifier
|
|
|
+ sign += app
|
|
|
+ commonHeader["app"] = app
|
|
|
+
|
|
|
+ let device = curDeviceModelName
|
|
|
+ sign += device
|
|
|
+ commonHeader["device"] = device
|
|
|
+
|
|
|
+ let platform = "2"
|
|
|
+ sign += platform
|
|
|
+ commonHeader["platform"] = platform
|
|
|
+
|
|
|
+ let channel = "appStore"
|
|
|
+ sign += channel
|
|
|
+ commonHeader["channel"] = channel
|
|
|
+
|
|
|
+ let api = "1"
|
|
|
+ sign += api
|
|
|
+ commonHeader["api"] = api
|
|
|
+
|
|
|
+ let version = curBuildVersion
|
|
|
+ sign += version
|
|
|
+ commonHeader["version"] = version
|
|
|
+
|
|
|
+ let network = LNNetworkMonitor.curNetworkType.desc
|
|
|
+ sign += network
|
|
|
+ commonHeader["network"] = network
|
|
|
+
|
|
|
+ let time = "\(Int(curTimeInMicro))"
|
|
|
+ sign += time
|
|
|
+ commonHeader["time"] = time
|
|
|
+
|
|
|
+ let token = LNAccountManager.token
|
|
|
+ if !token.isEmpty {
|
|
|
+ sign += token
|
|
|
+ commonHeader["token"] = token
|
|
|
+ }
|
|
|
+
|
|
|
+ let secret = "abc|abc|edg|9527|1234"
|
|
|
+ sign += secret
|
|
|
+
|
|
|
+ let mergedHeader = commonHeader.merging(headers ?? [:]) { $1 }
|
|
|
+
|
|
|
+ // 检查URL是否有效
|
|
|
+ guard let url = buildURL(from: path, method: method, parameters: parameters) else {
|
|
|
+ completion(nil, .invalidURL)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建请求
|
|
|
+ var request = URLRequest(url: url)
|
|
|
+ request.httpMethod = method.rawValue
|
|
|
+
|
|
|
+ // 设置请求头
|
|
|
+ mergedHeader.forEach { key, value in
|
|
|
+ request.addValue(value, forHTTPHeaderField: key)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置请求体(POST, PUT等方法)
|
|
|
+ if method != .get, let parameters = parameters {
|
|
|
+ let body = try? JSONSerialization
|
|
|
+ .data(withJSONObject: parameters)
|
|
|
+ request.httpBody = body
|
|
|
+ if let body, let bodyStr = String(data: body, encoding: .utf8) {
|
|
|
+ sign += bodyStr
|
|
|
+ }
|
|
|
+ }
|
|
|
+ request.addValue(sign.md5, forHTTPHeaderField: "sign")
|
|
|
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
|
+
|
|
|
+ // 执行请求
|
|
|
+ let task = session.dataTask(with: request) {
|
|
|
+ [weak self] data,
|
|
|
+ response,
|
|
|
+ error in
|
|
|
+ guard let self else { return }
|
|
|
+
|
|
|
+ // 处理网络错误
|
|
|
+ if let error = error {
|
|
|
+ Log.d("receive \(request.url?.absoluteString ?? "") error: \(error.localizedDescription)")
|
|
|
+ completion(nil, .networkError(error))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查响应是否有效
|
|
|
+ guard let httpResponse = response as? HTTPURLResponse else {
|
|
|
+ Log.d("receive \(request.url?.absoluteString ?? "") response error")
|
|
|
+
|
|
|
+ completion(nil, .invalidResponse)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if let data {
|
|
|
+ Log.d("receive \(request.url?.absoluteString ?? "") code:\(httpResponse.statusCode) data \(String(data: data, encoding: .utf8) ?? "")")
|
|
|
+ } else {
|
|
|
+ Log.d("receive \(request.url?.absoluteString ?? "") code:\(httpResponse.statusCode)")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查状态码
|
|
|
+ guard 200...299 ~= httpResponse.statusCode else {
|
|
|
+ completion(nil, .statusCode(httpResponse.statusCode))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理响应数据
|
|
|
+ guard let data = data else {
|
|
|
+ completion(nil, .invalidResponse)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析JSON数据
|
|
|
+ self.parseJSON(data: data, completion: completion)
|
|
|
+ }
|
|
|
+
|
|
|
+ if let body = request.httpBody {
|
|
|
+ Log.d("send \(request.httpMethod ?? "") - \(request.url?.absoluteString ?? "") \(String(data: body, encoding: .utf8) ?? "")")
|
|
|
+ } else {
|
|
|
+ Log.d("send \(request.httpMethod ?? "") - \(request.url?.absoluteString ?? "")")
|
|
|
+ }
|
|
|
+ task.resume()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 构建请求URL(处理GET参数)
|
|
|
+ private func buildURL(from path: String, method: HTTPMethod, parameters: [String: Any]?) -> URL? {
|
|
|
+ guard var urlComponents = URLComponents(string: LNNetworkConfig.host + (path.starts(with: "/") ? path : "/\(path)")) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // GET方法的参数拼接到URL上
|
|
|
+ if method == .get, let parameters = parameters {
|
|
|
+ urlComponents.queryItems = parameters.map { key, value in
|
|
|
+ URLQueryItem(name: key, value: "\(value)")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return urlComponents.url
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 解析JSON数据
|
|
|
+ private func parseJSON<T: Decodable>(
|
|
|
+ data: Data,
|
|
|
+ completion: @escaping (T?, LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ do {
|
|
|
+ let decoder = JSONDecoder()
|
|
|
+ decoder.keyDecodingStrategy = .convertFromSnakeCase // 处理蛇形命名
|
|
|
+ let result = try decoder.decode(LNHttpResponse<T>.self, from: data)
|
|
|
+ if result.code != 0 {
|
|
|
+ completion(nil, .serverError(result.msg))
|
|
|
+ } else {
|
|
|
+ completion(result.data, nil)
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ completion(nil, .parsingError(error))
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// MARK: - 便捷请求方法扩展
|
|
|
+extension LNHttpManager {
|
|
|
+ /// 发送GET请求
|
|
|
+ func get<T: Decodable>(
|
|
|
+ path: String,
|
|
|
+ params: [String: Any]? = nil,
|
|
|
+ completion: @escaping (T?, LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ request(
|
|
|
+ path: path,
|
|
|
+ method: .get,
|
|
|
+ parameters: params,
|
|
|
+ completion: completion
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 发送POST请求
|
|
|
+ func post<T: Decodable>(
|
|
|
+ path: String,
|
|
|
+ params: [String: Any]? = nil,
|
|
|
+ completion: @escaping (T?, LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ request(
|
|
|
+ path: path,
|
|
|
+ method: .post,
|
|
|
+ parameters: params,
|
|
|
+ completion: completion
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 发送PUT请求
|
|
|
+ func put<T: Decodable>(
|
|
|
+ path: String,
|
|
|
+ params: [String: Any]? = nil,
|
|
|
+ completion: @escaping (T?, LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ request(
|
|
|
+ path: path,
|
|
|
+ method: .put,
|
|
|
+ parameters: params,
|
|
|
+ completion: completion
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 发送DELETE请求
|
|
|
+ func delete<T: Decodable>(
|
|
|
+ path: String,
|
|
|
+ params: [String: Any]? = nil,
|
|
|
+ completion: @escaping (T?, LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ request(
|
|
|
+ path: path,
|
|
|
+ method: .delete,
|
|
|
+ parameters: params,
|
|
|
+ completion: completion
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension LNHttpManager {
|
|
|
+ func request(
|
|
|
+ path: String,
|
|
|
+ method: HTTPMethod = .get,
|
|
|
+ parameters: [String: Any]? = nil,
|
|
|
+ headers: [String: String]? = nil,
|
|
|
+ completion: @escaping (LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ let handler: (
|
|
|
+ LNHttpEmptyResponse?, LNHttpError?
|
|
|
+ ) -> Void = { _, err in
|
|
|
+ completion(err)
|
|
|
+ }
|
|
|
+ request(
|
|
|
+ path: path,
|
|
|
+ method: method,
|
|
|
+ parameters: parameters,
|
|
|
+ headers: headers,
|
|
|
+ completion: handler
|
|
|
+ )
|
|
|
+ }
|
|
|
+ /// 发送GET请求
|
|
|
+ func get(
|
|
|
+ path: String,
|
|
|
+ params: [String: Any]? = nil,
|
|
|
+ completion: @escaping (LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ request(
|
|
|
+ path: path,
|
|
|
+ method: .get,
|
|
|
+ parameters: params,
|
|
|
+ completion: completion
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 发送POST请求
|
|
|
+ func post(
|
|
|
+ path: String,
|
|
|
+ params: [String: Any]? = nil,
|
|
|
+ completion: @escaping (LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ request(
|
|
|
+ path: path,
|
|
|
+ method: .post,
|
|
|
+ parameters: params,
|
|
|
+ completion: completion
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 发送PUT请求
|
|
|
+ func put(
|
|
|
+ path: String,
|
|
|
+ params: [String: Any]? = nil,
|
|
|
+ completion: @escaping (LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ request(
|
|
|
+ path: path,
|
|
|
+ method: .put,
|
|
|
+ parameters: params,
|
|
|
+ completion: completion
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 发送DELETE请求
|
|
|
+ func delete(
|
|
|
+ path: String,
|
|
|
+ params: [String: Any]? = nil,
|
|
|
+ completion: @escaping (LNHttpError?) -> Void
|
|
|
+ ) {
|
|
|
+ request(
|
|
|
+ path: path,
|
|
|
+ method: .delete,
|
|
|
+ parameters: params,
|
|
|
+ completion: completion
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|