Initial commit
This commit is contained in:
@ -6,3 +6,16 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension Color {
|
||||
static func hex(_ hex: UInt, alpha: Double = 1.0) -> Color {
|
||||
return Color(
|
||||
red: Double((hex >> 16) & 0xFF) / 255.0,
|
||||
green: Double((hex >> 8) & 0xFF) / 255.0,
|
||||
blue: Double(hex & 0xFF) / 255.0,
|
||||
opacity: alpha
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,3 +6,55 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class CommonFunc {
|
||||
static let shared = CommonFunc()
|
||||
private let profanityList: [String] = ["fuck", "shit", "porn", "习近平", "鸡巴", "阴茎"] // Example profanity words
|
||||
|
||||
private init() {} // Private initializer to ensure singleton usage
|
||||
|
||||
func validateInputEng(input: String, isSingleWord: Bool = false) -> (isValid: Bool, message: String) {
|
||||
// Check for profanity
|
||||
for badWord in profanityList {
|
||||
if input.lowercased().contains(badWord) {
|
||||
return (false, globalEnvironment.DirtyInputErrToast)
|
||||
}
|
||||
}
|
||||
|
||||
// Check characters based on the isSingleWord flag
|
||||
let pattern = isSingleWord ? "^[A-Za-z]+$" : "^[A-Za-z0-9 .,;:!?'\"@-]+$"
|
||||
let regex = try! NSRegularExpression(pattern: pattern)
|
||||
let range = NSRange(location: 0, length: input.utf16.count)
|
||||
|
||||
if regex.firstMatch(in: input, options: [], range: range) == nil {
|
||||
let characterSet = isSingleWord ? "english letters" : "english letters, numbers, and punctuation"
|
||||
return (false, "Input should only contain \(characterSet).")
|
||||
}
|
||||
|
||||
return (true, "Input is valid.")
|
||||
}
|
||||
|
||||
func validateChineseInput(input: String) -> (isValid: Bool, message: String) {
|
||||
// Check for profanity
|
||||
for word in profanityList {
|
||||
if input.contains(word) {
|
||||
return (false, globalEnvironment.DirtyInputErrToast)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all characters are valid Chinese characters or punctuation
|
||||
for character in input {
|
||||
if !character.isPunctuation && !(0x4E00...0x9FFF).contains(character.unicodeScalars.first!.value) &&
|
||||
!(0x3400...0x4DBF).contains(character.unicodeScalars.first!.value) &&
|
||||
!(0x20000...0x2A6DF).contains(character.unicodeScalars.first!.value) {
|
||||
// 既不是中文,又不是英文,返回错误
|
||||
let res = self.validateInputEng(input: input)
|
||||
if !res.isValid{
|
||||
return (false, "Input contains invalid characters.")
|
||||
}
|
||||
}
|
||||
}
|
||||
return (true, "Input is valid.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -6,3 +6,228 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import StoreKit
|
||||
import SwiftUI
|
||||
import SwiftyBeaver
|
||||
|
||||
enum IAPProduct: String, CaseIterable {
|
||||
case premiumFeature1 = "grammar_1_week"
|
||||
case premiumFeature2 = "grammar_1_month"
|
||||
case premiumFeature3 = "grammar_1_year"
|
||||
|
||||
var weight: Int {
|
||||
switch self {
|
||||
case .premiumFeature1:
|
||||
return 1
|
||||
case .premiumFeature2:
|
||||
return 2
|
||||
case .premiumFeature3:
|
||||
return 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class IAPManager: ObservableObject {
|
||||
@Published var products: [Product] = [] // 获取商品列表
|
||||
@Published var purchasedProducts: [Product] = [] // 已购买的商品列表
|
||||
private var processedTransactionIDs = Set<UInt64>() // 已经发到服务端校验的交易ID
|
||||
|
||||
init() {
|
||||
Task {
|
||||
await requestProducts()
|
||||
await updatePurchasedProducts()
|
||||
await listenForTransactions()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取商品列表,并按照权重排序。
|
||||
func requestProducts() async {
|
||||
do {
|
||||
let products = try await Product.products(for: IAPProduct.allCases.map { $0.rawValue })
|
||||
DispatchQueue.main.async {
|
||||
// 按照产品的权重排序
|
||||
self.products = products.sorted { product1, product2 in
|
||||
let weight1 = IAPProduct(rawValue: product1.id)?.weight ?? 0
|
||||
let weight2 = IAPProduct(rawValue: product2.id)?.weight ?? 0
|
||||
return weight1 < weight2
|
||||
}
|
||||
|
||||
// 打印每个产品及其相关变量
|
||||
for product in self.products {
|
||||
if let receiptData = try? product.jsonRepresentation {
|
||||
if let jsonString = String(data: receiptData, encoding: .utf8) {
|
||||
logger.info("product details for \(product.id): \(jsonString)")
|
||||
} else {
|
||||
logger.info("product details for \(product.id): ", context: ["Tile": product.displayName, "Price": product.price, "DisplayPrice": product.displayPrice])
|
||||
}
|
||||
}else {
|
||||
logger.info("product details for \(product.id): ", context: ["Tile": product.displayName, "Price": product.price, "DisplayPrice": product.displayPrice])
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("Failed to fetch products: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
// 商品购买
|
||||
func buy(product: Product, completion: @escaping (Result<String, Error>) -> Void) async {
|
||||
do {
|
||||
let result = try await product.purchase()
|
||||
switch result {
|
||||
case .success(let verification):
|
||||
if case .verified(let transaction) = verification {
|
||||
// 获取购买信息
|
||||
let appAccountToken = transaction.appAccountToken
|
||||
let productId = transaction.productID
|
||||
let transactionId = transaction.id
|
||||
|
||||
// 打印日志
|
||||
if let receiptData = try? transaction.jsonRepresentation {
|
||||
if let jsonString = String(data: receiptData, encoding: .utf8) {
|
||||
logger.info("Transaction details for \(transactionId): \(jsonString)")
|
||||
} else {
|
||||
logger.info("Transaction details for \(transactionId)", context: ["ProductID" : productId, "AppAccountToken" : appAccountToken ?? "", "OriTransactionID":transaction.originalID.value])
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.info("Transaction details for \(transactionId)", context: ["ProductID" : productId, "AppAccountToken" : appAccountToken ?? "", "OriTransactionID":transaction.originalID.value])
|
||||
}
|
||||
|
||||
// 发送到服务端进行校验
|
||||
await validateReceipt(receiptData: transaction.jsonRepresentation, appAccountToken: appAccountToken, productId: productId, transactionId: transactionId, env: transaction.environment.rawValue)
|
||||
|
||||
await transaction.finish()
|
||||
await updatePurchasedProducts()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// 购买成功的消息
|
||||
completion(.success("Purchase Successful"))
|
||||
}
|
||||
}
|
||||
case .userCancelled:
|
||||
// 用户取消支付,这个行为最好能报上去,或者做点其他营销动作
|
||||
logger.error("User cancelled the purchase.")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// 用户取消购买
|
||||
completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Purchase Cancelled"])))
|
||||
}
|
||||
case .pending:
|
||||
// 需要上报上去
|
||||
logger.error("Purchase is pending.")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// 购买失败
|
||||
completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Purchase Pending"])))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
} catch {
|
||||
logger.error("Failed to purchase product: \(error.localizedDescription)")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.apple.com/documentation/storekit/transaction/3851204-currententitlements
|
||||
// A sequence of the latest transactions that entitle a user to in-app purchases and subscriptions.
|
||||
func updatePurchasedProducts() async {
|
||||
for await result in Transaction.currentEntitlements {
|
||||
if case .verified(let transaction) = result {
|
||||
if let product = products.first(where: { $0.id == transaction.productID }) {
|
||||
DispatchQueue.main.async {
|
||||
self.purchasedProducts.append(product)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.apple.com/documentation/storekit/transaction/3851206-updates
|
||||
// The asynchronous sequence that emits a transaction when the system creates or updates transactions that occur outside of the app or on other devices.
|
||||
// Note that after a successful in-app purchase on the same device, StoreKit returns the transaction through Product.PurchaseResult.success(_:).
|
||||
func listenForTransactions() async {
|
||||
for await transactionResult in Transaction.updates {
|
||||
if case .verified(let transaction) = transactionResult {
|
||||
if let product = products.first(where: { $0.id == transaction.productID }) {
|
||||
DispatchQueue.main.async {
|
||||
self.purchasedProducts.append(product)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取购买信息
|
||||
let appAccountToken = transaction.appAccountToken
|
||||
let productId = transaction.productID
|
||||
let transactionId = transaction.id
|
||||
|
||||
// 打印日志
|
||||
if let receiptData = try? transaction.jsonRepresentation {
|
||||
if let jsonString = String(data: receiptData, encoding: .utf8) {
|
||||
logger.info("Transaction details for \(transactionId): \(jsonString)")
|
||||
} else {
|
||||
logger.info("Transaction details for \(transactionId)", context: ["ProductID" : productId, "AppAccountToken" : appAccountToken ?? "", "OriTransactionID":transaction.originalID.value])
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.info("Transaction details for \(transactionId)", context: ["ProductID" : productId, "AppAccountToken" : appAccountToken ?? "", "OriTransactionID":transaction.originalID.value])
|
||||
}
|
||||
|
||||
// 发送到服务端进行校验
|
||||
await validateReceipt(receiptData: transaction.jsonRepresentation, appAccountToken: appAccountToken, productId: productId, transactionId: transactionId, env: transaction.environment.rawValue)
|
||||
await transaction.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.apple.com/documentation/storekit/appstore/3791906-sync
|
||||
// Synchronizes your app’s transaction information and subscription status with information from the App Store.
|
||||
func restorePurchases(completion: @escaping (Result<String, Error>) -> Void) async {
|
||||
do {
|
||||
try await AppStore.sync()
|
||||
await updatePurchasedProducts()
|
||||
completion(.success("Purchase Successful"))
|
||||
logger.info("Purchases restored")
|
||||
} catch {
|
||||
logger.error("Failed to restore purchases: \(error.localizedDescription)")
|
||||
completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Purchase Pending"])))
|
||||
}
|
||||
}
|
||||
|
||||
// 服务端校验收据
|
||||
func validateReceipt(receiptData: Data, appAccountToken: UUID?, productId: String, transactionId: UInt64, env: String) async {
|
||||
// 调用NetworkManager中的IapVerify进行收据验证
|
||||
NetworkManager.shared.IapVerify(receiptData: receiptData, appAccountToken: appAccountToken, productId: productId, transactionId: transactionId, env: env) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
|
||||
DispatchQueue.main.async {
|
||||
logger.info("Receipt verification succeeded: \(response.ret)", context: ["ProductID":productId, "TransactionID":transactionId])
|
||||
|
||||
// 处理后续逻辑
|
||||
self.updatePurchasedStatus(productId: productId)
|
||||
}
|
||||
case .failure(let error):
|
||||
// 处理错误情况
|
||||
DispatchQueue.main.async {
|
||||
switch error {
|
||||
case .businessError(let ret, let message):
|
||||
logger.error("Business error - Ret: \(ret), Message: \(message)", context: ["ProductID":productId, "TransactionID":transactionId])
|
||||
case .other(let error):
|
||||
logger.error("network occurred: \(error.localizedDescription)", context: ["ProductID":productId, "TransactionID":transactionId])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 示例方法,用于更新购买状态
|
||||
private func updatePurchasedStatus(productId: String) {
|
||||
// 逻辑来标记产品为已购买,例如更新本地数据库、发送通知等
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,5 +6,88 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Alamofire
|
||||
import TrustDecision
|
||||
import SwiftJWT
|
||||
import SwiftyBeaver
|
||||
|
||||
class InitApp {
|
||||
static let shared = InitApp(environment: globalEnvironment)
|
||||
|
||||
private let userDefaults = UserDefaults.standard
|
||||
private let deviceIDKey = "DeviceID"
|
||||
private let vipStatusKey = "VIPStatus"
|
||||
private var deviceID: String = ""
|
||||
private var vipStatus: Bool = false
|
||||
|
||||
var environment: GlobalEnvironment
|
||||
|
||||
init(environment: GlobalEnvironment) {
|
||||
self.environment = environment
|
||||
initializeApp()
|
||||
}
|
||||
|
||||
private func initializeApp() {
|
||||
fetchDeviceID()
|
||||
}
|
||||
|
||||
private func fetchDeviceID() {
|
||||
if let savedDeviceID = userDefaults.string(forKey: deviceIDKey) {
|
||||
self.environment.deviceID = savedDeviceID
|
||||
logger.info("DeviceID from UserDefaults: \(savedDeviceID)")
|
||||
// 获取用户状态
|
||||
getUser()
|
||||
} else {
|
||||
getDeviceIDFromTrustDecision()
|
||||
}
|
||||
}
|
||||
|
||||
private func getDeviceIDFromTrustDecision() {
|
||||
var options = [String : NSObject]()
|
||||
let responseCallback: ([String : Any]) -> Void = { response in
|
||||
DispatchQueue.main.async { // 确保在主线程更新UI
|
||||
if let deviceId = response["device_id"] as? String {
|
||||
self.deviceID = deviceId
|
||||
self.userDefaults.set(deviceId, forKey: self.deviceIDKey)
|
||||
self.environment.deviceID = deviceId
|
||||
logger.info("Device ID from TrustDecision: \(self.deviceID)")
|
||||
|
||||
// 获取VIP状态
|
||||
self.getUser()
|
||||
}
|
||||
}
|
||||
}
|
||||
options["callback"] = unsafeBitCast(responseCallback as @convention(block) ([String : Any]) -> Void, to: AnyObject.self) as? NSObject
|
||||
let manager = TDMobRiskManager.sharedManager()
|
||||
manager?.pointee.initWithOptions(options)
|
||||
}
|
||||
|
||||
|
||||
private func getUser() {
|
||||
NetworkManager.shared.getUserProfile() { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result {
|
||||
case .success(let userData):
|
||||
self.vipStatus = userData.vip == 1
|
||||
self.environment.GID = userData.id
|
||||
self.environment.userID = userData.userid
|
||||
self.environment.userName = userData.username
|
||||
self.environment.isVip = userData.vip == 1
|
||||
logger.info("getUserProfile: ID: \(userData.id), userID: \(userData.userid), userName: \(userData.username), vip: \(userData.vip)")
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case .businessError(let ret, let message):
|
||||
logger.error("Business error - Ret: \(ret), Message: \(message)")
|
||||
case .other(let error):
|
||||
logger.error("network occurred: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshUserInfo(){
|
||||
getUser()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -6,3 +6,38 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyBeaver
|
||||
|
||||
let logger = SwiftyBeaver.self
|
||||
|
||||
func setupLogging() {
|
||||
let console = ConsoleDestination()
|
||||
let file = FileDestination()
|
||||
//file.logFileURL = URL(fileURLWithPath: "/path/to/your/log/file.log")
|
||||
|
||||
// use custom format and set console output to short time, log level & message
|
||||
// console.format = "$DHH:mm:ss$d $L $M"
|
||||
// or use this for JSON output:
|
||||
// console.format = "$J"
|
||||
|
||||
// In Xcode 15, specifying the logging method as .logger to display color, subsystem, and category information in the console.(Relies on the OSLog API)
|
||||
//console.logPrintWay = .logger(subsystem: "Main", category: "UI")
|
||||
|
||||
// If you prefer not to use the OSLog API, you can use print instead.
|
||||
// console.logPrintWay = .print
|
||||
|
||||
|
||||
console.format = "$DHH:mm:ss$d $C$L$c $N.$F:$l - $M"
|
||||
|
||||
// 自定义颜色
|
||||
console.levelColor.verbose = "⚪️ " // White
|
||||
console.levelColor.debug = "🔵 " // Blue
|
||||
console.levelColor.info = "🟢 " // Green
|
||||
console.levelColor.warning = "🟡 " // Yellow
|
||||
console.levelColor.error = "🔴 " // Red
|
||||
|
||||
logger.addDestination(console)
|
||||
logger.addDestination(file)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -10,35 +10,50 @@ 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]?
|
||||
@ -56,6 +71,7 @@ struct WordDetailsResponse: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
// 查询用户信息,不涉及页面交互,统一使用服务端的返回结构
|
||||
struct VIPStatusResponse: Codable {
|
||||
let id: Int
|
||||
let userid: String
|
||||
@ -63,73 +79,122 @@ struct VIPStatusResponse: Codable {
|
||||
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 ([GrammarRes]?, Error?) -> Void) {
|
||||
func checkGrammar(inputText: String, completion: @escaping (Result<[GrammarRes], NetworkError>) -> Void) {
|
||||
let url = globalEnvironment.grammarURL
|
||||
let headers: HTTPHeaders = createAuthorizationHeader()
|
||||
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"
|
||||
]
|
||||
AF.request(url, method: .post, parameters: parameters, encoding: URLEncoding.httpBody, headers: headers)
|
||||
.responseDecodable(of: [GrammarRes].self) { response in
|
||||
switch response.result {
|
||||
case .success(let results):
|
||||
completion(results, nil)
|
||||
case .failure(let error):
|
||||
completion(nil, error)
|
||||
|
||||
// 使用统一封装的网络请求,并解返回包
|
||||
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, Error>) -> Void) {
|
||||
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
|
||||
let headers: HTTPHeaders = createAuthorizationHeader()
|
||||
var headers: HTTPHeaders = createAuthorizationHeader()
|
||||
let timezoneHeaders = getHeaderTimezoneInfo()
|
||||
headers.add(name: "timezone", value: timezoneHeaders["timezone"]!)
|
||||
headers.add(name: "secondsfromgmt", value: timezoneHeaders["secondsfromgmt"]!)
|
||||
|
||||
AF.request(url, method: .post, parameters: parameters, encoding: URLEncoding.httpBody, headers: headers)
|
||||
.responseDecodable(of: WordDetailsResponse.self) { response in
|
||||
switch response.result {
|
||||
// 使用统一封装的网络请求,并解返回包
|
||||
performRequest(
|
||||
endpoint: url,
|
||||
parameters: parameters,
|
||||
method: .post,
|
||||
encoding: URLEncoding.httpBody,
|
||||
headers: headers,
|
||||
completion: { (result: Result<WordDetailsResponse, NetworkError>) in
|
||||
switch result {
|
||||
case .success(let detailsResponse):
|
||||
print("Success: Received data for \(detailsResponse.word)")
|
||||
let details = detailsResponse.toWordDetails() // Convert here
|
||||
completion(.success(details))
|
||||
case .failure(let error):
|
||||
print("Error: \(error)")
|
||||
print("Response Data: \(String(data: response.data ?? Data(), encoding: .utf8) ?? "No data")")
|
||||
|
||||
// 透传错误
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 翻译功能
|
||||
func translate(inputText: String, lang: String = "chs", completion: @escaping (Result<Translation, Error>) -> Void) {
|
||||
|
||||
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]
|
||||
let headers: HTTPHeaders = createAuthorizationHeader()
|
||||
var headers: HTTPHeaders = createAuthorizationHeader()
|
||||
let timezoneHeaders = getHeaderTimezoneInfo()
|
||||
headers.add(name: "timezone", value: timezoneHeaders["timezone"]!)
|
||||
headers.add(name: "secondsfromgmt", value: timezoneHeaders["secondsfromgmt"]!)
|
||||
|
||||
AF.request(url, method: .post, parameters: parameters, encoding: URLEncoding.httpBody, headers: headers)
|
||||
.responseDecodable(of: TranslationResponse.self) { response in
|
||||
switch response.result {
|
||||
// 使用统一封装的网络请求,并解返回包
|
||||
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))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 翻译的点赞、点踩功能
|
||||
@ -146,16 +211,15 @@ struct NetworkManager {
|
||||
AF.request(url, method: .post, parameters: parameters, encoding: URLEncoding.httpBody, headers: headers).response { response in
|
||||
switch response.result {
|
||||
case .success(let data):
|
||||
print("Feedback sent successfully: \(String(describing: data))")
|
||||
logger.info("Feedback sent successfully.", context: ["input":input, "isPositive": isPositive])
|
||||
case .failure(let error):
|
||||
print("Error sending feedback: \(error)")
|
||||
logger.error("Error sending feedback: \(error)", context: ["input":input, "isPositive": isPositive])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 查询VIP状态
|
||||
func getUserProfile(completion: @escaping (Result<VIPStatusResponse, Error>) -> Void) {
|
||||
func getUserProfile(completion: @escaping (Result<VIPStatusResponse, NetworkError>) -> Void) {
|
||||
let url = globalEnvironment.userURL
|
||||
|
||||
let parameters: [String: Any] = [:]
|
||||
@ -167,27 +231,52 @@ struct NetworkManager {
|
||||
method: .post,
|
||||
encoding: URLEncoding.httpBody,
|
||||
headers: headers, // 示例:使用JWT token
|
||||
completion: { (result: Result<VIPStatusResponse, Error>) in
|
||||
completion: { (result: Result<VIPStatusResponse, NetworkError>) in
|
||||
switch result {
|
||||
case .success(let vipData):
|
||||
logger.info("VIP Status: \(vipData.vip)")
|
||||
completion(.success(vipData))
|
||||
case .failure(let error):
|
||||
logger.error("Error: \(error.localizedDescription)")
|
||||
// 无需处理,直接透传
|
||||
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, Error>) -> Void
|
||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||
) {
|
||||
let url = endpoint
|
||||
let defaultHeaders: HTTPHeaders = [.contentType("application/x-www-form-urlencoded")]
|
||||
@ -200,15 +289,18 @@ struct NetworkManager {
|
||||
if apiResponse.ret == 0, let data = apiResponse.data {
|
||||
completion(.success(data))
|
||||
} else {
|
||||
let error = NSError(domain: "", code: apiResponse.ret, userInfo: [NSLocalizedDescriptionKey: apiResponse.message])
|
||||
completion(.failure(error))
|
||||
completion(.failure(.businessError(ret: apiResponse.ret, message: apiResponse.message)))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(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 {
|
||||
|
||||
Reference in New Issue
Block a user