feat: 添加项目基础文件和依赖管理
新增.gitignore、Podfile和Podfile.lock文件以管理项目依赖,添加README.md文件提供项目简介和安装步骤,创建NIMSessionManager、ClientConfig、LogManager和NetworkManager等管理类以支持网络请求和日志记录功能,更新AppDelegate和ContentView以集成NIM SDK和实现用户登录功能。
This commit is contained in:
59
yana/APIs/API.swift
Normal file
59
yana/APIs/API.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
import Alamofire
|
||||
|
||||
enum HttpRequestMethod: String {
|
||||
case get = "GET"
|
||||
case post = "POST"
|
||||
// 可扩展其他方法
|
||||
}
|
||||
|
||||
typealias HttpRequestCompletion = (Result<Data, Error>) -> Void
|
||||
|
||||
class API {
|
||||
// 通用请求方法
|
||||
static func makeRequest(
|
||||
route: String,
|
||||
method: HttpRequestMethod,
|
||||
params: [String: Any],
|
||||
completion: @escaping HttpRequestCompletion
|
||||
) {
|
||||
let httpMethod: HTTPMethod = {
|
||||
switch method {
|
||||
case .get: return .get
|
||||
case .post: return .post
|
||||
}
|
||||
}()
|
||||
|
||||
NetworkManager.shared.request(route, method: httpMethod, parameters: params) { (result: Result<Data, NetworkError>) in
|
||||
switch result {
|
||||
case .success(let data):
|
||||
completion(.success(data))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 具体接口方法示例
|
||||
static func getUserInfo(uid: String, completion: @escaping HttpRequestCompletion) {
|
||||
let route = "user/get"
|
||||
let params = ["uid": uid]
|
||||
makeRequest(route: route, method: .get, params: params, completion: completion)
|
||||
}
|
||||
|
||||
static func phoneSmsCode(mobile: String, type: String, phoneAreaCode: String, completion: @escaping HttpRequestCompletion) {
|
||||
let route = "sms/getCode"
|
||||
let params = ["mobile": mobile, "type": type, "phoneAreaCode": phoneAreaCode]
|
||||
makeRequest(route: route, method: .post, params: params, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension API //ClientConfig
|
||||
{
|
||||
static func clientInit(completion: @escaping HttpRequestCompletion) {
|
||||
makeRequest(route: "client/init",
|
||||
method: .get,
|
||||
params: [:],
|
||||
completion: completion)
|
||||
}
|
||||
}
|
9
yana/AppDelegate.swift
Normal file
9
yana/AppDelegate.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
import UIKit
|
||||
import NIMSDK
|
||||
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
NIMConfigurationManager.setupNimSDK()
|
||||
return true
|
||||
}
|
||||
}
|
@@ -15,9 +15,9 @@ struct AppConfig {
|
||||
static var baseURL: String {
|
||||
switch current {
|
||||
case .development:
|
||||
return "https://dev-api.yourdomain.com/v1"
|
||||
return "http://beta.api.molistar.xyz"
|
||||
case .production:
|
||||
return "https://api.yourdomain.com/v1"
|
||||
return "https://api.hfighting.com"
|
||||
}
|
||||
}
|
||||
|
||||
|
32
yana/Configs/ClientConfig.swift
Normal file
32
yana/Configs/ClientConfig.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
import Foundation
|
||||
|
||||
final class ClientConfig {
|
||||
static let shared = ClientConfig()
|
||||
private init() {}
|
||||
|
||||
func initializeClient() {
|
||||
print("开始初始化客户端")
|
||||
|
||||
NetworkManager.shared.enhancedRequest(
|
||||
path: "client/init",
|
||||
method: .get,
|
||||
responseType: Data.self
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
print("初始化成功,状态码:\(response.statusCode)")
|
||||
if let data = response.data {
|
||||
do {
|
||||
let json = try JSONSerialization.jsonObject(with: data)
|
||||
print("响应数据:\(json)")
|
||||
} catch {
|
||||
print("JSON解析失败:\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
print("初始化失败:\(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,15 +8,71 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var account = ""
|
||||
@State private var password = ""
|
||||
|
||||
#if DEBUG
|
||||
init() {
|
||||
_account = State(initialValue: "3184")
|
||||
_password = State(initialValue: "a0d5da073d14731cc7a01ecaa17b9174")
|
||||
}
|
||||
#endif
|
||||
@State private var isLoading = false
|
||||
@State private var loginError: String?
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
// 新增测试按钮
|
||||
Button("测试初始化") {
|
||||
ClientConfig.shared.initializeClient()
|
||||
}
|
||||
.padding(.top, 20)
|
||||
|
||||
TextField("账号", text: $account)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding()
|
||||
|
||||
SecureField("密码", text: $password)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding()
|
||||
|
||||
Button(action: handleLogin) {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text("登录")
|
||||
}
|
||||
}
|
||||
.disabled(isLoading)
|
||||
.alert("登录错误", isPresented: .constant(loginError != nil)) {
|
||||
Button("确定") { loginError = nil }
|
||||
} message: {
|
||||
Text(loginError ?? "")
|
||||
}
|
||||
|
||||
Image(systemName: "globe")
|
||||
.imageScale(.large)
|
||||
.foregroundStyle(.tint)
|
||||
Text("Hello, world!")
|
||||
Text("Hello, yana!")
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
private func handleLogin() {
|
||||
isLoading = true
|
||||
NIMSessionManager.shared
|
||||
.autoLogin(account: account, token: password) { error in
|
||||
if let error = error {
|
||||
loginError = error.localizedDescription
|
||||
} else {
|
||||
// 登录成功处理
|
||||
}
|
||||
}
|
||||
// NIMSessionManager.shared.login(account: account, token: password) { error in
|
||||
// isLoading = false
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
11
yana/Info.plist
Normal file
11
yana/Info.plist
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
51
yana/Managers/LogManager.swift
Normal file
51
yana/Managers/LogManager.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
import Foundation
|
||||
|
||||
/// 日志等级
|
||||
public enum LogLevel: Int {
|
||||
case verbose = 0
|
||||
case debug
|
||||
case info
|
||||
case warn
|
||||
case error
|
||||
}
|
||||
|
||||
public class LogManager {
|
||||
/// 单例
|
||||
public static let shared = LogManager()
|
||||
private init() {}
|
||||
|
||||
/// 日志输出
|
||||
/// - Parameters:
|
||||
/// - level: 日志等级
|
||||
/// - message: 日志内容
|
||||
/// - onlyRelease: 是否仅在 Release 环境输出(默认 false,Debug 全部输出)
|
||||
public func log(_ level: LogLevel, _ message: @autoclosure () -> String, onlyRelease: Bool = false) {
|
||||
#if DEBUG
|
||||
if onlyRelease { return }
|
||||
print("[\(level)] \(message())")
|
||||
#else
|
||||
print("[\(level)] \(message())")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 快捷方法
|
||||
public func logVerbose(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
|
||||
LogManager.shared.log(.verbose, message(), onlyRelease: onlyRelease)
|
||||
}
|
||||
|
||||
public func logDebug(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
|
||||
LogManager.shared.log(.debug, message(), onlyRelease: onlyRelease)
|
||||
}
|
||||
|
||||
public func logInfo(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
|
||||
LogManager.shared.log(.info, message(), onlyRelease: onlyRelease)
|
||||
}
|
||||
|
||||
public func logWarn(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
|
||||
LogManager.shared.log(.warn, message(), onlyRelease: onlyRelease)
|
||||
}
|
||||
|
||||
public func logError(_ message: @autoclosure () -> String, onlyRelease: Bool = false) {
|
||||
LogManager.shared.log(.error, message(), onlyRelease: onlyRelease)
|
||||
}
|
@@ -1,10 +1,33 @@
|
||||
import NIMSDK
|
||||
import NECoreKit
|
||||
import NECoreIM2Kit
|
||||
import NEChatKit
|
||||
import NEChatUIKit
|
||||
|
||||
struct NIMConfigurationManager {
|
||||
static func setupSDK() {
|
||||
NIMSDK.shared().register(
|
||||
withAppID: "79bc37000f4018a2a24ea9dc6ca08d32",
|
||||
cerName: "pikoDevelopPush"
|
||||
)
|
||||
|
||||
static func setupNimSDK() {
|
||||
let option = configureNIMSDKOption()
|
||||
setupSDK(with: option)
|
||||
setupChatSDK(with: option)
|
||||
}
|
||||
}
|
||||
|
||||
static func setupSDK(with option: NIMSDKOption) {
|
||||
NIMSDK.shared().register(with: option)
|
||||
NIMSDKConfig.shared().shouldConsiderRevokedMessageUnreadCount = true
|
||||
NIMSDKConfig.shared().shouldSyncStickTopSessionInfos = true
|
||||
}
|
||||
|
||||
static func setupChatSDK(with option: NIMSDKOption) {
|
||||
let v2Option = V2NIMSDKOption()
|
||||
v2Option.enableV2CloudConversation = false
|
||||
IMKitClient.instance.setupIM2(option, v2Option)
|
||||
}
|
||||
|
||||
static func configureNIMSDKOption() -> NIMSDKOption {
|
||||
let option = NIMSDKOption()
|
||||
option.appKey = "79bc37000f4018a2a24ea9dc6ca08d32"
|
||||
option.apnsCername = "pikoDevelopPush"
|
||||
return option
|
||||
}
|
||||
}
|
||||
|
127
yana/Managers/NIMSessionManager.swift
Normal file
127
yana/Managers/NIMSessionManager.swift
Normal file
@@ -0,0 +1,127 @@
|
||||
import Foundation
|
||||
import NIMSDK
|
||||
|
||||
// MARK: - 网络状态通知
|
||||
extension Notification.Name {
|
||||
static let NIMNetworkStateChanged = Notification.Name("NIMNetworkStateChangedNotification")
|
||||
static let NIMTokenExpired = Notification.Name("NIMTokenExpiredNotification")
|
||||
}
|
||||
|
||||
@objc
|
||||
@objcMembers
|
||||
final class NIMSessionManager: NSObject {
|
||||
|
||||
static let shared = NIMSessionManager()
|
||||
|
||||
// MARK: - 登录管理
|
||||
func autoLogin(account: String, token: String, completion: @escaping (Error?) -> Void) {
|
||||
NIMSDK.shared().v2LoginService.add(self)
|
||||
let data = NIMAutoLoginData()
|
||||
data.account = account
|
||||
data.token = token
|
||||
data.forcedMode = false
|
||||
NIMSDK.shared().loginManager.autoLogin(data)
|
||||
}
|
||||
|
||||
func login(account: String, token: String, completion: @escaping (Error?) -> Void) {
|
||||
NIMSDK.shared().loginManager.login(account, token: token) { error in
|
||||
if error == nil {
|
||||
self.registerObservers()
|
||||
}
|
||||
completion(error)
|
||||
}
|
||||
}
|
||||
|
||||
func logout() {
|
||||
NIMSDK.shared().loginManager.logout { _ in
|
||||
self.removeObservers()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 消息监听
|
||||
private func registerObservers() {
|
||||
// 在 autoLogin 方法中
|
||||
// NIMSDK.shared().v2LoginService.add(self as! V2NIMLoginServiceDelegate)
|
||||
|
||||
// 在 registerObservers 方法中
|
||||
// NIMSDK.shared().v2LoginService.add(self as! V2NIMLoginServiceDelegate)
|
||||
|
||||
// 在 removeObservers 方法中
|
||||
// NIMSDK.shared().v2LoginService.remove(self as! V2NIMLoginServiceDelegate)
|
||||
NIMSDK.shared().chatManager.add(self)
|
||||
NIMSDK.shared().loginManager.add(self)
|
||||
}
|
||||
|
||||
private func removeObservers() {
|
||||
NIMSDK.shared().v2LoginService.remove(self)
|
||||
NIMSDK.shared().chatManager.remove(self)
|
||||
NIMSDK.shared().loginManager.remove(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NIMChatManagerDelegate
|
||||
extension NIMSessionManager: NIMChatManagerDelegate {
|
||||
func onRecvMessages(_ messages: [NIMMessage]) {
|
||||
NotificationCenter.default.post(
|
||||
name: .NIMDidReceiveMessage,
|
||||
object: messages
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NIMLoginManagerDelegate
|
||||
extension NIMSessionManager: NIMLoginManagerDelegate {
|
||||
func onLogin(_ step: NIMLoginStep) {
|
||||
NotificationCenter.default.post(
|
||||
name: .NIMLoginStateChanged,
|
||||
object: step
|
||||
)
|
||||
}
|
||||
|
||||
func onAutoLoginFailed(_ error: Error) {
|
||||
if (error as NSError).code == 302 {
|
||||
NotificationCenter.default.post(name: .NIMTokenExpired, object: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 通知定义
|
||||
extension Notification.Name {
|
||||
static let NIMDidReceiveMessage = Notification.Name("NIMDidReceiveMessageNotification")
|
||||
static let NIMLoginStateChanged = Notification.Name("NIMLoginStateChangedNotification")
|
||||
}
|
||||
|
||||
// MARK: - NIMV2LoginServiceDelegate
|
||||
extension NIMSessionManager: V2NIMLoginListener {
|
||||
func onLoginStatus(_ status: V2NIMLoginStatus) {
|
||||
|
||||
}
|
||||
|
||||
func onLoginFailed(_ error: V2NIMError) {
|
||||
|
||||
}
|
||||
|
||||
func onKickedOffline(_ detail: V2NIMKickedOfflineDetail) {
|
||||
|
||||
}
|
||||
|
||||
func onLoginClientChanged(
|
||||
_ change: V2NIMLoginClientChange,
|
||||
clients: [V2NIMLoginClient]?
|
||||
) {
|
||||
|
||||
}
|
||||
// @objc func onLoginProcess(step: NIMV2LoginStep) {
|
||||
// NotificationCenter.default.post(
|
||||
// name: .NIMV2LoginStateChanged,
|
||||
// object: step
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// @objc func onKickOut(result: NIMKickOutResult) {
|
||||
// NotificationCenter.default.post(
|
||||
// name: .NIMKickOutNotification,
|
||||
// object: result
|
||||
// )
|
||||
// }
|
||||
}
|
667
yana/Managers/NetworkManager.swift
Normal file
667
yana/Managers/NetworkManager.swift
Normal file
@@ -0,0 +1,667 @@
|
||||
import Foundation
|
||||
import Alamofire
|
||||
import CoreTelephony
|
||||
import UIKit
|
||||
import Darwin // 用于 utsname 结构体
|
||||
import CommonCrypto
|
||||
|
||||
// 配置类
|
||||
//enum AppConfig {
|
||||
// static let baseURL = "https://api.example.com" // 请替换为实际的 API 基础 URL
|
||||
//}
|
||||
|
||||
// 网络状态枚举
|
||||
enum NetworkStatus: Int {
|
||||
case notReachable = 0
|
||||
case reachableViaWWAN = 1
|
||||
case reachableViaWiFi = 2
|
||||
}
|
||||
|
||||
// 扩展错误类型
|
||||
enum NetworkError: Error {
|
||||
case invalidURL
|
||||
case requestFailed(statusCode: Int, message: String?)
|
||||
case invalidResponse
|
||||
case decodingFailed
|
||||
case networkUnavailable
|
||||
case serverError(message: String)
|
||||
case unauthorized
|
||||
case rateLimited
|
||||
|
||||
var localizedDescription: String {
|
||||
switch self {
|
||||
case .invalidURL:
|
||||
return "无效的 URL"
|
||||
case .requestFailed(let statusCode, let message):
|
||||
return "请求失败: \(statusCode), \(message ?? "未知错误")"
|
||||
case .invalidResponse:
|
||||
return "无效的响应"
|
||||
case .decodingFailed:
|
||||
return "数据解析失败"
|
||||
case .networkUnavailable:
|
||||
return "网络不可用"
|
||||
case .serverError(let message):
|
||||
return "服务器错误: \(message)"
|
||||
case .unauthorized:
|
||||
return "未授权访问"
|
||||
case .rateLimited:
|
||||
return "请求过于频繁"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MD5 加密扩展
|
||||
extension String {
|
||||
func md5() -> String {
|
||||
let str = self.cString(using: .utf8)
|
||||
let strLen = CUnsignedInt(self.lengthOfBytes(using: .utf8))
|
||||
let digestLen = Int(CC_MD5_DIGEST_LENGTH)
|
||||
let result = UnsafeMutablePointer<UInt8>.allocate(capacity: digestLen)
|
||||
|
||||
CC_MD5(str!, strLen, result)
|
||||
|
||||
let hash = NSMutableString()
|
||||
for i in 0..<digestLen {
|
||||
hash.appendFormat("%02x", result[i])
|
||||
}
|
||||
|
||||
result.deallocate()
|
||||
return hash as String
|
||||
}
|
||||
}
|
||||
|
||||
// 基础参数结构体
|
||||
struct BaseParameters: Encodable {
|
||||
let acceptLanguage: String
|
||||
let os: String = "iOS"
|
||||
let osVersion: String
|
||||
let ispType: String
|
||||
let channel: String
|
||||
let model: String
|
||||
let deviceId: String
|
||||
let appVersion: String
|
||||
let app: String
|
||||
let mcc: String?
|
||||
let pub_sign: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case acceptLanguage = "Accept-Language"
|
||||
case os, osVersion, ispType, channel, model, deviceId
|
||||
case appVersion, app, mcc
|
||||
case pub_sign
|
||||
}
|
||||
|
||||
init() {
|
||||
// 获取系统首选语言(使用新的语言管理器)
|
||||
self.acceptLanguage = LanguageManager.getCurrentLanguage()
|
||||
|
||||
// 获取系统版本
|
||||
self.osVersion = UIDevice.current.systemVersion
|
||||
|
||||
// 获取运营商信息
|
||||
let networkInfo = CTTelephonyNetworkInfo()
|
||||
var ispType = "65535"
|
||||
var mcc: String? = nil // 初始化 mcc 变量
|
||||
|
||||
if #available(iOS 12.0, *) {
|
||||
// 使用新的 API
|
||||
if let carriers = networkInfo.serviceSubscriberCellularProviders,
|
||||
let carrier = carriers.values.first {
|
||||
ispType = (
|
||||
carrier.mobileNetworkCode != nil ? carrier.mobileNetworkCode : ""
|
||||
) ?? "65535"
|
||||
mcc = carrier.mobileCountryCode
|
||||
}
|
||||
} else {
|
||||
// 兼容旧版本
|
||||
if let carrier = networkInfo.subscriberCellularProvider {
|
||||
ispType = (
|
||||
carrier.mobileNetworkCode != nil ? carrier.mobileNetworkCode : ""
|
||||
) ?? "65535"
|
||||
mcc = carrier.mobileCountryCode
|
||||
}
|
||||
}
|
||||
|
||||
self.ispType = ispType
|
||||
self.mcc = mcc // 确保在所有路径中都设置了 mcc
|
||||
|
||||
// 获取渠道信息
|
||||
self.channel = ChannelManager.getCurrentChannel()
|
||||
|
||||
// 获取设备型号
|
||||
self.model = DeviceManager.getDeviceModel()
|
||||
|
||||
// 获取设备唯一标识
|
||||
self.deviceId = UIDevice.current.identifierForVendor?.uuidString ?? ""
|
||||
|
||||
// 获取应用版本
|
||||
self.appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
|
||||
|
||||
// 获取应用名称
|
||||
self.app = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? ""
|
||||
|
||||
// 生成 pub_sign
|
||||
let key = "rpbs6us1m8r2j9g6u06ff2bo18orwaya"
|
||||
let signString = "key=\(key)"
|
||||
self.pub_sign = signString.md5().uppercased()
|
||||
}
|
||||
}
|
||||
|
||||
final class NetworkManager {
|
||||
static let shared = NetworkManager()
|
||||
|
||||
// 网络响应结构体
|
||||
struct NetworkResponse<T> {
|
||||
let statusCode: Int
|
||||
let data: T?
|
||||
let headers: [AnyHashable: Any]
|
||||
let metrics: URLSessionTaskMetrics?
|
||||
|
||||
var isSuccessful: Bool {
|
||||
return (200...299).contains(statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
private let reachability = NetworkReachabilityManager()
|
||||
private var isNetworkReachable = false
|
||||
private let baseURL = AppConfig.baseURL
|
||||
private let session: Session
|
||||
private let retryLimit = 2
|
||||
|
||||
// 网络状态监听回调
|
||||
var networkStatusChanged: ((NetworkStatus) -> Void)?
|
||||
|
||||
init() {
|
||||
let configuration = URLSessionConfiguration.af.default
|
||||
configuration.httpShouldSetCookies = true
|
||||
configuration.httpCookieAcceptPolicy = .always
|
||||
configuration.timeoutIntervalForRequest = 60
|
||||
configuration.timeoutIntervalForResource = 60
|
||||
configuration.httpMaximumConnectionsPerHost = 10
|
||||
|
||||
// 支持的内容类型
|
||||
configuration.httpAdditionalHeaders = [
|
||||
"Accept": "application/json, text/json, text/javascript, text/html, text/plain, image/jpeg, image/png",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Content-Type": "application/json"
|
||||
]
|
||||
|
||||
// 强制 TLS 1.2+ 并禁用 HTTP/1.1
|
||||
configuration.tlsMinimumSupportedProtocolVersion = .TLSv12
|
||||
configuration.httpShouldUsePipelining = true
|
||||
|
||||
// 重试策略
|
||||
let retrier = RetryPolicy(retryLimit: UInt(retryLimit))
|
||||
|
||||
session = Session(
|
||||
configuration: configuration,
|
||||
interceptor: retrier,
|
||||
eventMonitors: [AlamofireLogger()]
|
||||
)
|
||||
|
||||
// 添加网络可达性监听
|
||||
setupReachability()
|
||||
}
|
||||
|
||||
private func setupReachability() {
|
||||
reachability?.startListening { [weak self] status in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch status {
|
||||
case .reachable(.ethernetOrWiFi):
|
||||
self.isNetworkReachable = true
|
||||
self.networkStatusChanged?(.reachableViaWiFi)
|
||||
case .reachable(.cellular):
|
||||
self.isNetworkReachable = true
|
||||
self.networkStatusChanged?(.reachableViaWWAN)
|
||||
case .notReachable:
|
||||
self.isNetworkReachable = false
|
||||
self.networkStatusChanged?(.notReachable)
|
||||
case .unknown:
|
||||
self.isNetworkReachable = false
|
||||
self.networkStatusChanged?(.notReachable)
|
||||
case .reachable(_):
|
||||
self.isNetworkReachable = true
|
||||
self.networkStatusChanged?(.reachableViaWiFi)
|
||||
@unknown default:
|
||||
fatalError("未知的网络状态")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 便利方法
|
||||
|
||||
/// 发送 GET 请求
|
||||
func get<T: Decodable>(
|
||||
path: String,
|
||||
queryItems: [String: String]? = nil,
|
||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||
) {
|
||||
enhancedRequest(
|
||||
path: path,
|
||||
method: .get,
|
||||
queryItems: queryItems,
|
||||
responseType: T.self
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
if let data = response.data as? T {
|
||||
completion(.success(data))
|
||||
} else {
|
||||
completion(.failure(.decodingFailed))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送 POST 请求
|
||||
func post<T: Decodable, P: Encodable>(
|
||||
path: String,
|
||||
parameters: P,
|
||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||
) {
|
||||
enhancedRequest(
|
||||
path: path,
|
||||
method: .post,
|
||||
bodyParameters: parameters,
|
||||
responseType: T.self
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
if let data = response.data as? T {
|
||||
completion(.success(data))
|
||||
} else {
|
||||
completion(.failure(.decodingFailed))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 核心请求方法
|
||||
func enhancedRequest<T>(
|
||||
path: String,
|
||||
method: HTTPMethod = .get,
|
||||
queryItems: [String: String]? = nil,
|
||||
bodyParameters: Encodable? = nil,
|
||||
responseType: T.Type = Data.self,
|
||||
completion: @escaping (Result<NetworkResponse<T>, NetworkError>) -> Void
|
||||
) {
|
||||
// 前置网络检查
|
||||
guard isNetworkReachable else {
|
||||
completion(.failure(.networkUnavailable))
|
||||
return
|
||||
}
|
||||
|
||||
guard let baseURL = URL(string: baseURL) else {
|
||||
completion(.failure(.invalidURL))
|
||||
return
|
||||
}
|
||||
|
||||
var urlComponents = URLComponents(url: baseURL.appendingPathComponent(path), resolvingAgainstBaseURL: true)
|
||||
urlComponents?.queryItems = queryItems?.map { URLQueryItem(name: $0.key, value: $0.value) }
|
||||
|
||||
guard let finalURL = urlComponents?.url else {
|
||||
completion(.failure(.invalidURL))
|
||||
return
|
||||
}
|
||||
|
||||
// 合并基础参数和自定义参数
|
||||
// TODO: 补充加密验参:pub_sign
|
||||
let baseParams = BaseParameters()
|
||||
var parameters: Parameters = baseParams.dictionary ?? [:]
|
||||
|
||||
if let customParams = bodyParameters {
|
||||
if let dict = try? customParams.asDictionary() {
|
||||
parameters.merge(dict) { (_, new) in new }
|
||||
}
|
||||
}
|
||||
|
||||
session.request(finalURL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: commonHeaders)
|
||||
.validate()
|
||||
.responseData { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
|
||||
let statusCode = response.response?.statusCode ?? -1
|
||||
let headers = response.response?.allHeaderFields ?? [:]
|
||||
let metrics = response.metrics
|
||||
|
||||
switch response.result {
|
||||
case .success(let decodedData):
|
||||
do {
|
||||
let resultData: T
|
||||
if T.self == Data.self {
|
||||
resultData = decodedData as! T
|
||||
} else if let decodableData = decodedData as? T {
|
||||
resultData = decodableData
|
||||
} else {
|
||||
throw AFError.responseSerializationFailed(reason: .decodingFailed(error: NetworkError.decodingFailed))
|
||||
}
|
||||
|
||||
let networkResponse = NetworkResponse(
|
||||
statusCode: statusCode,
|
||||
data: resultData,
|
||||
headers: headers,
|
||||
metrics: metrics
|
||||
)
|
||||
|
||||
if networkResponse.isSuccessful {
|
||||
completion(.success(networkResponse))
|
||||
} else {
|
||||
self.handleErrorResponse(statusCode: statusCode, completion: completion)
|
||||
}
|
||||
} catch {
|
||||
completion(.failure(.decodingFailed))
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
self.handleRequestError(error, statusCode: statusCode, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 错误处理
|
||||
|
||||
private func handleErrorResponse<T>(
|
||||
statusCode: Int,
|
||||
completion: (Result<NetworkResponse<T>, NetworkError>) -> Void
|
||||
) {
|
||||
switch statusCode {
|
||||
case 401:
|
||||
completion(.failure(.unauthorized))
|
||||
case 429:
|
||||
completion(.failure(.rateLimited))
|
||||
case 500...599:
|
||||
completion(.failure(.serverError(message: "服务器错误 \(statusCode)")))
|
||||
default:
|
||||
completion(.failure(.requestFailed(statusCode: statusCode, message: "请求失败")))
|
||||
}
|
||||
}
|
||||
|
||||
private func handleRequestError<T>(
|
||||
_ error: AFError,
|
||||
statusCode: Int,
|
||||
completion: (Result<NetworkResponse<T>, NetworkError>) -> Void
|
||||
) {
|
||||
if let underlyingError = error.underlyingError as? URLError {
|
||||
switch underlyingError.code {
|
||||
case .notConnectedToInternet:
|
||||
completion(.failure(.networkUnavailable))
|
||||
default:
|
||||
completion(.failure(.requestFailed(
|
||||
statusCode: statusCode,
|
||||
message: underlyingError.localizedDescription
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
completion(.failure(.invalidResponse))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 公共头信息
|
||||
private var commonHeaders: HTTPHeaders {
|
||||
var headers = HTTPHeaders()
|
||||
|
||||
// 公共头信息
|
||||
if let language = Locale.preferredLanguages.first {
|
||||
headers.add(name: "Accept-Language", value: language)
|
||||
}
|
||||
headers.add(name: "App-Version", value: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0")
|
||||
|
||||
// 登录相关头信息
|
||||
let uid = "" //AccountInfoStorage.instance?.getUid() ?? ""
|
||||
let ticket = "" //AccountInfoStorage.instance?.getTicket() ?? ""
|
||||
headers.add(name: "pub_uid", value: uid)
|
||||
headers.add(name: "pub_ticket", value: ticket)
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
// MARK: - 公共请求方法
|
||||
func request<T: Decodable, P: Encodable>(
|
||||
_ path: String,
|
||||
method: HTTPMethod = .get,
|
||||
parameters: P? = nil,
|
||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||
) {
|
||||
enhancedRequest(
|
||||
path: path,
|
||||
method: method,
|
||||
bodyParameters: parameters,
|
||||
responseType: T.self
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
if let data = response.data {
|
||||
completion(.success(data))
|
||||
} else {
|
||||
completion(.failure(.decodingFailed))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加一个便利方法,用于处理字典参数
|
||||
func request<T: Decodable>(
|
||||
_ path: String,
|
||||
method: HTTPMethod = .get,
|
||||
parameters: [String: Any]? = nil,
|
||||
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||
) {
|
||||
// 将字典转换为 Data,然后再解码为 [String: String]
|
||||
if let params = parameters {
|
||||
do {
|
||||
let jsonData = try JSONSerialization.data(withJSONObject: params)
|
||||
let decoder = JSONDecoder()
|
||||
let encodableParams = try decoder.decode([String: String].self, from: jsonData)
|
||||
|
||||
request(path, method: method, parameters: encodableParams, completion: completion)
|
||||
} catch {
|
||||
completion(.failure(.decodingFailed))
|
||||
}
|
||||
} else {
|
||||
// 如果没有参数,使用空字典
|
||||
request(path, method: method, parameters: [String: String](), completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 语言管理
|
||||
func getCurrentLanguage() -> String {
|
||||
return LanguageManager.getCurrentLanguage()
|
||||
}
|
||||
|
||||
func updateLanguage(_ language: String) {
|
||||
LanguageManager.updateLanguage(language)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Logger
|
||||
final class AlamofireLogger: EventMonitor {
|
||||
func requestDidResume(_ request: Request) {
|
||||
let allHeaders = request.request?.allHTTPHeaderFields ?? [:]
|
||||
let relevantHeaders = allHeaders.filter { !$0.key.contains("Authorization") }
|
||||
|
||||
print("🚀 Request Started: \(request.description)")
|
||||
print("📝 Headers: \(relevantHeaders)")
|
||||
|
||||
if let httpBody = request.request?.httpBody,
|
||||
let parameters = try? JSONSerialization.jsonObject(with: httpBody) {
|
||||
print("📦 Parameters: \(parameters)")
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ request: DataRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, data: Data?) {
|
||||
print("📥 Response Status: \(response.statusCode)")
|
||||
|
||||
if let data = data,
|
||||
let json = try? JSONSerialization.jsonObject(with: data) {
|
||||
print("📄 Response Data: \(json)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Encodable Extension
|
||||
private extension Encodable {
|
||||
var dictionary: [String: Any]? {
|
||||
guard let data = try? JSONEncoder().encode(self) else { return nil }
|
||||
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
|
||||
}
|
||||
|
||||
func asDictionary() throws -> [String: Any] {
|
||||
let data = try JSONEncoder().encode(self)
|
||||
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
|
||||
throw NetworkError.decodingFailed
|
||||
}
|
||||
return dictionary
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 语言管理
|
||||
enum LanguageManager {
|
||||
static let languageKey = "UserSelectedLanguage"
|
||||
|
||||
// 映射语言代码
|
||||
static func mapLanguage(_ language: String) -> String {
|
||||
// 处理完整的语言代码,如 "zh-Hans"、"zh-Hant"、"zh-HK" 等
|
||||
if language.hasPrefix("zh-Hans") || language.hasPrefix("zh-CN") {
|
||||
return "zh-Hant" // 简体中文也返回繁体
|
||||
} else if language.hasPrefix("zh") {
|
||||
return "zh-Hant" // 其他中文变体都返回繁体
|
||||
} else if language.hasPrefix("ar") {
|
||||
return "ar" // 阿拉伯语
|
||||
} else if language.hasPrefix("tr") {
|
||||
return "tr" // 土耳其语
|
||||
} else {
|
||||
return "en" // 默认英文
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前语言
|
||||
static func getCurrentLanguage() -> String {
|
||||
// 先从 UserDefaults 获取
|
||||
if let savedLanguage = UserDefaults.standard.string(forKey: languageKey) {
|
||||
return savedLanguage
|
||||
}
|
||||
|
||||
// 获取系统首选语言
|
||||
let preferredLanguages = Locale.preferredLanguages.first
|
||||
// let systemLanguage = preferredLanguages.first ?? Locale.current.languageCode ?? "en"
|
||||
|
||||
// 映射并保存语言设置
|
||||
let mappedLanguage = mapLanguage(preferredLanguages ?? "en")
|
||||
UserDefaults.standard.set(mappedLanguage, forKey: languageKey)
|
||||
UserDefaults.standard.synchronize()
|
||||
|
||||
return mappedLanguage
|
||||
}
|
||||
|
||||
// 更新语言设置
|
||||
static func updateLanguage(_ language: String) {
|
||||
let mappedLanguage = mapLanguage(language)
|
||||
UserDefaults.standard.set(mappedLanguage, forKey: languageKey)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
|
||||
// 获取系统语言代码(调试用)
|
||||
static func getSystemLanguageInfo() -> (preferred: [String], current: String?) {
|
||||
return (
|
||||
Bundle.main.preferredLocalizations,
|
||||
Locale.current.languageCode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 设备型号管理
|
||||
enum DeviceManager {
|
||||
// 获取设备标识符
|
||||
static func getDeviceIdentifier() -> String {
|
||||
var systemInfo = utsname()
|
||||
uname(&systemInfo)
|
||||
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
||||
let identifier = machineMirror.children.reduce("") { identifier, element in
|
||||
guard let value = element.value as? Int8, value != 0 else { return identifier }
|
||||
return identifier + String(UnicodeScalar(UInt8(value)))
|
||||
}
|
||||
return identifier
|
||||
}
|
||||
|
||||
// 映射设备标识符到具体型号
|
||||
static func mapDeviceModel(_ identifier: String) -> String {
|
||||
switch identifier {
|
||||
// iPhone
|
||||
case "iPhone13,1": return "iPhone 12 mini"
|
||||
case "iPhone13,2": return "iPhone 12"
|
||||
case "iPhone13,3": return "iPhone 12 Pro"
|
||||
case "iPhone13,4": return "iPhone 12 Pro Max"
|
||||
case "iPhone14,4": return "iPhone 13 mini"
|
||||
case "iPhone14,5": return "iPhone 13"
|
||||
case "iPhone14,2": return "iPhone 13 Pro"
|
||||
case "iPhone14,3": return "iPhone 13 Pro Max"
|
||||
case "iPhone14,7": return "iPhone 14"
|
||||
case "iPhone14,8": return "iPhone 14 Plus"
|
||||
case "iPhone15,2": return "iPhone 14 Pro"
|
||||
case "iPhone15,3": return "iPhone 14 Pro Max"
|
||||
case "iPhone15,4": return "iPhone 15"
|
||||
case "iPhone15,5": return "iPhone 15 Plus"
|
||||
case "iPhone16,1": return "iPhone 15 Pro"
|
||||
case "iPhone16,2": return "iPhone 15 Pro Max"
|
||||
|
||||
// iPad
|
||||
case "iPad13,1", "iPad13,2": return "iPad Air (4th generation)"
|
||||
case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7": return "iPad Pro 12.9-inch (5th generation)"
|
||||
case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11": return "iPad Pro 12.9-inch (6th generation)"
|
||||
|
||||
// iPod
|
||||
case "iPod9,1": return "iPod touch (7th generation)"
|
||||
|
||||
// 模拟器
|
||||
case "i386", "x86_64", "arm64": return "Simulator \(mapDeviceModel(ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))"
|
||||
|
||||
default: return identifier // 如果找不到对应的型号,返回原始标识符
|
||||
}
|
||||
}
|
||||
|
||||
// 获取具体的设备型号
|
||||
static func getDeviceModel() -> String {
|
||||
let identifier = getDeviceIdentifier()
|
||||
return mapDeviceModel(identifier)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 渠道管理
|
||||
enum ChannelManager {
|
||||
static let enterpriseBundleId = "com.stupidmonkey.yana.yana"//"com.hflighting.yumi"
|
||||
|
||||
enum ChannelType: String {
|
||||
case enterprise = "enterprise"
|
||||
case testflight = "testflight"
|
||||
case appstore = "appstore"
|
||||
}
|
||||
|
||||
// 判断是否企业版
|
||||
static func isEnterprise() -> Bool {
|
||||
let bundleId = Bundle.main.bundleIdentifier ?? ""
|
||||
return bundleId == enterpriseBundleId
|
||||
}
|
||||
|
||||
// 判断是否 TestFlight 版本
|
||||
static func isTestFlight() -> Bool {
|
||||
return Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
|
||||
}
|
||||
|
||||
// 获取当前渠道
|
||||
static func getCurrentChannel() -> String {
|
||||
if isEnterprise() {
|
||||
return ChannelType.enterprise.rawValue
|
||||
} else if isTestFlight() {
|
||||
return ChannelType.testflight.rawValue
|
||||
} else {
|
||||
return ChannelType.appstore.rawValue
|
||||
}
|
||||
}
|
||||
}
|
4
yana/yana-Bridging-Header.h
Normal file
4
yana/yana-Bridging-Header.h
Normal file
@@ -0,0 +1,4 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
@@ -6,9 +6,12 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import NIMSDK
|
||||
|
||||
@main
|
||||
struct yanaApp: App {
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
|
Reference in New Issue
Block a user