326 lines
12 KiB
Swift
326 lines
12 KiB
Swift
//
|
||
// 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<T: Decodable>: 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<WordDetails, NetworkError>) -> 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<WordDetailsResponse, NetworkError>) 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<Translation, NetworkError>) -> 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<TranslationResponse, NetworkError>) 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<VIPStatusResponse, NetworkError>) -> 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<VIPStatusResponse, NetworkError>) 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<IAPVerifyRsp, NetworkError>) -> 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<IAPVerifyRsp, NetworkError>) in
|
||
switch result {
|
||
case .success(let rsp):
|
||
completion(.success(rsp))
|
||
case .failure(let error):
|
||
// 直接透传
|
||
completion(.failure(error))
|
||
}
|
||
}
|
||
)
|
||
}
|
||
|
||
// 发起网络请求的统一封装。包含统一应答的解包,异常处理等。
|
||
func performRequest<T: Decodable>(
|
||
endpoint: String,
|
||
parameters: Parameters, // 明确使用 Alamofire 的 Parameters 类型
|
||
method: HTTPMethod = .post,
|
||
encoding: URLEncoding = .httpBody,
|
||
headers: HTTPHeaders? = nil,
|
||
completion: @escaping (Result<T, NetworkError>) -> 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<T>.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
|
||
}
|
||
}
|
||
}
|
||
|