283 lines
10 KiB
Swift
283 lines
10 KiB
Swift
|
|
//
|
|||
|
|
// APIProvider.swift
|
|||
|
|
// WoolniOriginalSwift
|
|||
|
|
//
|
|||
|
|
// Created by lyu dong on 2025/7/2.
|
|||
|
|
// Copyright © 2025 lydsnm. All rights reserved.
|
|||
|
|
//
|
|||
|
|
|
|||
|
|
import Foundation
|
|||
|
|
import Moya
|
|||
|
|
import UIKit
|
|||
|
|
|
|||
|
|
let myEndpointClosure = { (target: TargetType) -> Endpoint in
|
|||
|
|
let url = target.baseURL.absoluteString + target.path
|
|||
|
|
var task = target.task
|
|||
|
|
|
|||
|
|
var endpoint = Endpoint(
|
|||
|
|
url: url,
|
|||
|
|
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
|
|||
|
|
method: target.method,
|
|||
|
|
task: task,
|
|||
|
|
httpHeaderFields: target.headers
|
|||
|
|
)
|
|||
|
|
return endpoint
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let myRequestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
|
|||
|
|
do {
|
|||
|
|
var request = try endpoint.urlRequest()
|
|||
|
|
request.timeoutInterval = 30
|
|||
|
|
|
|||
|
|
let token = UserCore.shared.token
|
|||
|
|
if !token.isEmpty {
|
|||
|
|
let body = request.httpBody ?? Data()
|
|||
|
|
let str = String(data:body, encoding: .utf8)
|
|||
|
|
if APIConfig.apiLogEnable {
|
|||
|
|
// dlog("⚠️ request加密前参数:\(str ?? "")")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
// 加密结果
|
|||
|
|
let key = (token + "AHkt5aUUtO6HZPid").md5().uppercased()
|
|||
|
|
let aes = try AES(key: key, iv: "HBB4UO5kEmM4169Z")
|
|||
|
|
let encrypted = try aes.encrypt(str!.bytes)
|
|||
|
|
let result = encrypted.toBase64()
|
|||
|
|
if APIConfig.apiLogEnable {
|
|||
|
|
// dlog("⚠️ request加密结果:\(result)")
|
|||
|
|
}
|
|||
|
|
let dic = ["key": result]
|
|||
|
|
|
|||
|
|
let httpBody = try JSONSerialization.data(withJSONObject: dic, options: .prettyPrinted)
|
|||
|
|
request.httpBody = httpBody
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
done(.success(request))
|
|||
|
|
} catch {
|
|||
|
|
done(.failure(MoyaError.underlying(error, nil)))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let myNetworkPlugin = NetworkActivityPlugin.init { changeType, _ in
|
|||
|
|
switch changeType {
|
|||
|
|
case .began: dlog("开始请求网络")
|
|||
|
|
case .ended: dlog("结束请求网络")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public enum ServiceErrorEnum: String, Codable {
|
|||
|
|
case unknow = "-1"
|
|||
|
|
case unknowEmpty = ""
|
|||
|
|
case internalError = "00000000"
|
|||
|
|
case common = "0000"
|
|||
|
|
|
|||
|
|
case sign_usernotexist = "10050002"
|
|||
|
|
case sign_userLoginclientNotExist = "10050003"
|
|||
|
|
case sign_userNotAuthorizedClient = "10050004"
|
|||
|
|
case tokenExpired = "10050005"
|
|||
|
|
case tokenIllegal = "10050006"
|
|||
|
|
case accountIsFrozen = "10010002"
|
|||
|
|
|
|||
|
|
/// 用户未登陆
|
|||
|
|
case sign_usernotLoggedIn = "10050001"
|
|||
|
|
|
|||
|
|
/// 用户账号已被冻结
|
|||
|
|
case otherAccountFrozen = "10010005"
|
|||
|
|
/// 账号不存在
|
|||
|
|
case newAccount = "10010001"
|
|||
|
|
/// 验证码不正确
|
|||
|
|
case smscodeIncorrect = "10031001"
|
|||
|
|
/// 验证码过期
|
|||
|
|
case smscodeExpired = "10031002"
|
|||
|
|
/// 用户不存在
|
|||
|
|
case userExist = "10010004"
|
|||
|
|
|
|||
|
|
case mustNotBeBlank = "00000001"
|
|||
|
|
|
|||
|
|
case imageCheckFailed = "10019999"
|
|||
|
|
/// 生成图片超时
|
|||
|
|
case imageGenTimeOut = "8006"
|
|||
|
|
/// 查看未解锁的AI照片
|
|||
|
|
case imageBrowseButUnlock = "10010011"
|
|||
|
|
/// AI用户不存在
|
|||
|
|
case aiRoleNotExist = "10010012"
|
|||
|
|
|
|||
|
|
/// Coin 余额不足
|
|||
|
|
case insufficentCoin = "INSUFFICIENT_BALANCE"
|
|||
|
|
|
|||
|
|
public init(from decoder: Decoder) throws {
|
|||
|
|
let container = try decoder.singleValueContainer()
|
|||
|
|
let rawValue = try container.decode(String.self)
|
|||
|
|
self = ServiceErrorEnum(rawValue: rawValue) ?? .unknow
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func encode(to encoder: Encoder) throws {
|
|||
|
|
var container = encoder.singleValueContainer()
|
|||
|
|
switch self {
|
|||
|
|
case .unknow:
|
|||
|
|
// 编码成一个默认值,例如 "-999",或直接跳过
|
|||
|
|
try container.encode("unknown")
|
|||
|
|
default:
|
|||
|
|
try container.encode(self.rawValue)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 假设 ResponseData 是一个泛型结构体,用于包装 API 响应
|
|||
|
|
struct ResponseData<T: Codable>: Codable {
|
|||
|
|
/// OK or ERROR
|
|||
|
|
let status: String?
|
|||
|
|
let errorCode: ServiceErrorEnum?
|
|||
|
|
let errorMsg: String?
|
|||
|
|
let content: T?
|
|||
|
|
let traceId : String?
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class ResponseContentPageData<T: Codable>: Codable{
|
|||
|
|
var pn: Int?
|
|||
|
|
var ps: Int?
|
|||
|
|
var datas: [T]?
|
|||
|
|
var tc: Int?
|
|||
|
|
var sortType:String?
|
|||
|
|
var sortField: String?
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class RequestPageData:Codable {
|
|||
|
|
var pn : Int = 1
|
|||
|
|
var ps : Int = 20
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public enum ResponseError: Error {
|
|||
|
|
case serviceError(code: ServiceErrorEnum?, msg: String?)
|
|||
|
|
case deserializeError
|
|||
|
|
case networkError
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//extension ResponseError {
|
|||
|
|
// enum Code: String, Codable {
|
|||
|
|
// case tokenExpired
|
|||
|
|
// case tokenIllegal
|
|||
|
|
// case accountIsFrozen
|
|||
|
|
// case sign_usernotexist
|
|||
|
|
// case sign_usernotLoggedIn
|
|||
|
|
// case sign_userLoginclientNotExist
|
|||
|
|
// case sign_userNotAuthorizedClient
|
|||
|
|
// case newAccount
|
|||
|
|
// case hcaptchaNeed
|
|||
|
|
// case common
|
|||
|
|
// }
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
extension MoyaProvider {
|
|||
|
|
@discardableResult
|
|||
|
|
public func request<T: Codable>(_ target: Target,
|
|||
|
|
progress _: ProgressBlock? = .none,
|
|||
|
|
modelType TTType: T.Type,
|
|||
|
|
autoShowErrMsg: Bool = true,
|
|||
|
|
completion: @escaping (Result<T?, ResponseError>) -> Void) -> Cancellable {
|
|||
|
|
|
|||
|
|
if APIConfig.apiLogEnable {
|
|||
|
|
// 🗣\(target.method)\n
|
|||
|
|
var paramsLog = ""
|
|||
|
|
if case let .requestParameters(parameters, _) = target.task {
|
|||
|
|
if let jsonData = try? JSONSerialization.data(withJSONObject: parameters, options: [.prettyPrinted]),
|
|||
|
|
let jsonString = String(data: jsonData, encoding: .utf8) {
|
|||
|
|
paramsLog = "\n\(jsonString)"
|
|||
|
|
} else {
|
|||
|
|
paramsLog = "\(parameters)"
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
paramsLog = "\(target.task)"
|
|||
|
|
}
|
|||
|
|
dlog("headers: \(target.headers ?? ["": ""])\n「🗣️Request」path: ⭐️\(target.path)⭐️\n🗣️⭐️params:\(paramsLog)") //
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
return request(target, completion: { result in
|
|||
|
|
|
|||
|
|
|
|||
|
|
switch result {
|
|||
|
|
case let .success(response):
|
|||
|
|
do {
|
|||
|
|
// 使用 JSONDecoder 解析 response.data
|
|||
|
|
let decoder = JSONDecoder()
|
|||
|
|
let data = try decoder.decode(ResponseData<T>.self, from: response.data)
|
|||
|
|
|
|||
|
|
// 调试日志:将 JSON 转换为字符串用于日志输出
|
|||
|
|
if APIConfig.apiLogEnable {
|
|||
|
|
let jsonObject = try JSONSerialization.jsonObject(with: response.data)
|
|||
|
|
let jsonData = try JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted)
|
|||
|
|
let jsonString = String(data: jsonData, encoding: .utf8) ?? String(data: response.data, encoding: .utf8) ?? ""
|
|||
|
|
dlog("👉⭐️\(target.path)⭐️ response:\n\(jsonString)")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let status = data.status
|
|||
|
|
let code = data.errorCode
|
|||
|
|
let msg = data.errorMsg
|
|||
|
|
|
|||
|
|
if (status ?? "") == "OK" {
|
|||
|
|
let model = data.content
|
|||
|
|
completion(.success(model))
|
|||
|
|
} else {
|
|||
|
|
var toastMsg = autoShowErrMsg
|
|||
|
|
|
|||
|
|
if code == .tokenExpired || code == .tokenIllegal || code == .accountIsFrozen || code == .sign_usernotexist || code == .sign_usernotLoggedIn || code == .sign_userLoginclientNotExist || code == .sign_userNotAuthorizedClient {
|
|||
|
|
// ⚠️ 弹出登陆
|
|||
|
|
UserCore.shared.logout()
|
|||
|
|
|
|||
|
|
if let Vc = UIWindow.getTopViewController(), (Vc is CLLoginMainController || Vc is PersonalInformationFillController) {
|
|||
|
|
// do nothing.
|
|||
|
|
} else {
|
|||
|
|
// AppRouter.goBackRootController(jumpIndex: .home) {
|
|||
|
|
// NotificationCenter.post(name: .presentSignInVc, object: nil, userInfo: nil)
|
|||
|
|
// }
|
|||
|
|
NotificationCenter.post(name: .presentSignInVc, object: nil, userInfo: nil)
|
|||
|
|
}
|
|||
|
|
} else if code == .newAccount {
|
|||
|
|
// ⚠️ 不进行Toast的Msg类型
|
|||
|
|
toastMsg = false
|
|||
|
|
} else if code == .insufficentCoin {
|
|||
|
|
// ⚠️钱包余额不足
|
|||
|
|
// refresh wallet
|
|||
|
|
toastMsg = false
|
|||
|
|
|
|||
|
|
var handled = false
|
|||
|
|
if let cow = target as? AICowAPI{
|
|||
|
|
switch cow{
|
|||
|
|
case .voiceCallOperate, .voiceAsr2, .voiceTts:
|
|||
|
|
IMAIViewModel.shared.showChatModelInsufficentCoinSheet()
|
|||
|
|
handled = true
|
|||
|
|
default:
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if handled == false{
|
|||
|
|
// 弹出充值Sheet
|
|||
|
|
CLPurchase.shared.showIAPBuyCoinSheet()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if toastMsg, let msgUnpack = msg, msgUnpack.count > 0 {
|
|||
|
|
UIWindow.getTopDisplayWindow()?.makeToast(msgUnpack)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
dlog("⛔️error: code = \(code ?? .common), msg = \(data.errorMsg ?? "业务状态失败")")
|
|||
|
|
completion(.failure(.serviceError(code: code, msg: msg)))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch {
|
|||
|
|
dlog("⛔️请求成功,但解析失败: \(error), Response:⛔️\(CodableHelper.jsonString(from: response.data) ?? "x")⛔️")
|
|||
|
|
completion(.failure(.deserializeError))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
case let .failure(error):
|
|||
|
|
dlog("⛔️ \(target.path) 网络连接失败\(error)")
|
|||
|
|
UIWindow.getTopDisplayWindow()?.makeToast("Internet connection failed")
|
|||
|
|
completion(.failure(.networkError))
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|