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))
|
||
}
|
||
})
|
||
}
|
||
}
|