// // request.swift // AIGrammar // // Created by oscar on 2024/6/4. // import Foundation import Alamofire import SwiftJWT import SwiftyBeaver // jwt字段 struct MyClaims: Claims { var deviceID: String var gid: Int var exp1: Int } // 接口的统一返回格式。{"ret":%d, "message":"%s", "data":jsonData} struct APIResponse: Decodable { let ret: Int let message: String let data: T? } // 错误处理的封装 enum NetworkError: Error { case businessError(ret: Int, message: String) case other(Error) } // 改错功能,与服务器应答的数据结构;因为直接返回了数组,暂无使用 struct GrammarCheckRsp : Codable{ let data: [GrammarRes] } // 翻译功能,与页面交互的数据结构 struct Translation: Identifiable, Codable, Equatable { var id = UUID() var input: String var translation: String } // 翻译功能,服务器应答的数据结构 struct TranslationResponse: Codable { let translation: String } // 单词解析,与页面交互的数据结构 struct WordDetails { var word: String = "" var explanations: [String] = [] var phrases: [String] = [] var synonyms: [String] = [] } // 单词解析,服务器应答的数据结构 struct WordDetailsResponse: Codable { let word: String let explain: [String]? let phrase: [String]? let sync: [String]? // Mapping to WordDetails for use in UI func toWordDetails() -> WordDetails { return WordDetails( word: word, explanations: explain ?? [], phrases: phrase ?? [], synonyms: sync ?? [] ) } } // 查询用户信息,不涉及页面交互,统一使用服务端的返回结构 struct VIPStatusResponse: Codable { let id: Int let userid: String let username: String let vip: Int } // 内购验证,不涉及页面交互,统一使用服务端的返回结构 struct IAPVerifyRsp: Codable { let ret: String //let productType : String } // 定义统一的网络交互,采用单例模式。 struct NetworkManager { static let shared = NetworkManager() private let jwtSecret = globalEnvironment.jwtSecret // 获取时区 func getHeaderTimezoneInfo() -> [String: String] { let timezone = TimeZone.current let timezoneIdentifier = timezone.identifier // 时区标识符,如"America/New_York" let secondsFromGMT = timezone.secondsFromGMT() // 与GMT的秒数差 return [ "timezone": timezoneIdentifier, "secondsfromgmt": String(secondsFromGMT) ] } // 语法检查功能 func checkGrammar(inputText: String, completion: @escaping (Result<[GrammarRes], NetworkError>) -> Void) { let url = globalEnvironment.grammarURL var headers: HTTPHeaders = createAuthorizationHeader() let timezoneHeaders = getHeaderTimezoneInfo() headers.add(name: "timezone", value: timezoneHeaders["timezone"]!) headers.add(name: "secondsfromgmt", value: timezoneHeaders["secondsfromgmt"]!) let parameters: [String: Any] = [ "input": inputText, "lang": "eng" ] // 使用统一封装的网络请求,并解返回包 performRequest( endpoint: url, parameters: parameters, method: .post, encoding: URLEncoding.httpBody, headers: headers, completion: { (result: Result<[GrammarRes], NetworkError>) in switch result { case .success(let results): completion(.success(results)) case .failure(let error): // 透传错误 completion(.failure(error)) } } ) } // 单词的词典功能 func fetchWordDetails(inputText: String, lang: String = "eng", completion: @escaping (Result) -> Void) { guard !inputText.isEmpty else { return } let parameters: [String: Any] = ["input": inputText, "lang": lang] let url = globalEnvironment.dictURL var headers: HTTPHeaders = createAuthorizationHeader() let timezoneHeaders = getHeaderTimezoneInfo() headers.add(name: "timezone", value: timezoneHeaders["timezone"]!) headers.add(name: "secondsfromgmt", value: timezoneHeaders["secondsfromgmt"]!) // 使用统一封装的网络请求,并解返回包 performRequest( endpoint: url, parameters: parameters, method: .post, encoding: URLEncoding.httpBody, headers: headers, completion: { (result: Result) in switch result { case .success(let detailsResponse): let details = detailsResponse.toWordDetails() // Convert here completion(.success(details)) case .failure(let error): // 透传错误 completion(.failure(error)) } } ) } // 翻译功能 func translate(inputText: String, lang: String = "chs", completion: @escaping (Result) -> Void) { guard !inputText.isEmpty else { return } let url = globalEnvironment.translateURL let parameters: [String: Any] = ["input": inputText, "lang": lang] var headers: HTTPHeaders = createAuthorizationHeader() let timezoneHeaders = getHeaderTimezoneInfo() headers.add(name: "timezone", value: timezoneHeaders["timezone"]!) headers.add(name: "secondsfromgmt", value: timezoneHeaders["secondsfromgmt"]!) // 使用统一封装的网络请求,并解返回包 performRequest( endpoint: url, parameters: parameters, method: .post, encoding: URLEncoding.httpBody, headers: headers, completion: { (result: Result) in switch result { case .success(let translationResponse): let newTranslation = Translation(input: inputText, translation: translationResponse.translation) completion(.success(newTranslation)) case .failure(let error): // 透传错误 completion(.failure(error)) } } ) } // 翻译的点赞、点踩功能 func sendFeedback(input: String, output: String, isPositive: Bool) { let url = globalEnvironment.feedbackURL let parameters: [String: Any] = [ "product": "trans", "input": input, "output": output, "res": isPositive ? "good" : "bad" ] let headers: HTTPHeaders = createAuthorizationHeader() AF.request(url, method: .post, parameters: parameters, encoding: URLEncoding.httpBody, headers: headers).response { response in switch response.result { case .success(let data): logger.info("Feedback sent successfully.", context: ["input":input, "isPositive": isPositive]) case .failure(let error): logger.error("Error sending feedback: \(error)", context: ["input":input, "isPositive": isPositive]) } } } // 查询VIP状态 func getUserProfile(completion: @escaping (Result) -> Void) { let url = globalEnvironment.userURL let parameters: [String: Any] = [:] let headers: HTTPHeaders = createAuthorizationHeader() performRequest( endpoint: url, parameters: parameters, method: .post, encoding: URLEncoding.httpBody, headers: headers, // 示例:使用JWT token completion: { (result: Result) in switch result { case .success(let vipData): completion(.success(vipData)) case .failure(let error): // 无需处理,直接透传 completion(.failure(error)) } } ) } // 查询VIP订单 func IapVerify(receiptData: Data, appAccountToken: UUID?, productId: String, transactionId: UInt64, env: String, completion: @escaping (Result) -> Void) { let url = globalEnvironment.iapVerifyURL let parameters: [String: Any] = ["transid":transactionId, "appaccounttoken": appAccountToken ?? "", "productid":productId, "receiptdata":receiptData, "env": env] let headers: HTTPHeaders = createAuthorizationHeader() performRequest( endpoint: url, parameters: parameters, method: .post, encoding: URLEncoding.httpBody, headers: headers, // 示例:使用JWT token completion: { (result: Result) in switch result { case .success(let rsp): completion(.success(rsp)) case .failure(let error): // 直接透传 completion(.failure(error)) } } ) } // 发起网络请求的统一封装。包含统一应答的解包,异常处理等。 func performRequest( endpoint: String, parameters: Parameters, // 明确使用 Alamofire 的 Parameters 类型 method: HTTPMethod = .post, encoding: URLEncoding = .httpBody, headers: HTTPHeaders? = nil, completion: @escaping (Result) -> Void ) { let url = endpoint let defaultHeaders: HTTPHeaders = [.contentType("application/x-www-form-urlencoded")] let combinedHeaders = headers ?? defaultHeaders // 确保参数是正确的类型 AF.request(url, method: .post, parameters: parameters, encoding: encoding, headers: combinedHeaders).responseDecodable(of: APIResponse.self) { response in switch response.result { case .success(let apiResponse): if apiResponse.ret == 0, let data = apiResponse.data { completion(.success(data)) } else { completion(.failure(.businessError(ret: apiResponse.ret, message: apiResponse.message))) } case .failure(let error): if let httpResponse = response.response { logger.error("network erorr.", context: ["url":url, "StatusCode":httpResponse.statusCode]) } completion(.failure(.other(error))) } } } // 添加jwt token private func createAuthorizationHeader() -> HTTPHeaders { // Generate JWT and return headers guard let jwtToken = generateJWT(deviceID: globalEnvironment.deviceID, gID: globalEnvironment.GID, jwtSecret: jwtSecret) else { return [:] } return ["Authorization": "Bearer \(jwtToken)"] } private func generateJWT(deviceID: String, gID: Int, jwtSecret: String) -> String? { let claims = MyClaims(deviceID: deviceID, gid: gID, exp1: Int(Date().timeIntervalSince1970) + 86400) var jwt = JWT(claims: claims) do { let jwtSigner = JWTSigner.hs256(key: Data(jwtSecret.utf8)) let signedJWT = try jwt.sign(using: jwtSigner) return signedJWT } catch { logger.error("JWT encoding failed with error: \(error)") return nil } } }