import Foundation import ComposableArchitecture // MARK: - HTTP Method /// HTTP 请求方法枚举 /// /// 定义了 API 请求支持的 HTTP 方法类型 /// 每个方法都有对应的字符串值,用于构建 URLRequest enum HTTPMethod: String, CaseIterable { case GET = "GET" case POST = "POST" case PUT = "PUT" case DELETE = "DELETE" case PATCH = "PATCH" } // MARK: - API Error Types /// API 错误类型枚举 /// /// 定义了 API 请求过程中可能出现的各种错误类型, /// 每种错误都包含详细的描述信息,便于调试和用户提示 /// /// 错误类型包括: /// - 网络相关错误(超时、连接失败等) /// - 数据相关错误(解析失败、数据过大等) /// - HTTP 状态码错误 /// - 其他未知错误 enum APIError: Error, Equatable { case invalidURL case noData case decodingError(String) case networkError(String) case httpError(statusCode: Int, message: String?) case timeout case resourceTooLarge case encryptionFailed // 新增:加密失败 case invalidResponse // 新增:无效响应 case ticketFailed // 新增:票据获取失败 case custom(String) // 新增:自定义错误信息 case unknown(String) var localizedDescription: String { switch self { case .invalidURL: return "无效的 URL" case .noData: return "没有收到数据" case .decodingError(let message): return "数据解析失败: \(message)" case .networkError(let message): return "网络错误: \(message)" case .httpError(let statusCode, let message): return "HTTP 错误 \(statusCode): \(message ?? "未知错误")" case .timeout: return "请求超时" case .resourceTooLarge: return "响应数据过大" case .encryptionFailed: return "数据加密失败" case .invalidResponse: return "服务器响应无效" case .ticketFailed: return "获取会话票据失败" case .custom(let message): return message case .unknown(let message): return "未知错误: \(message)" } } } // MARK: - Base Request Parameters /// 基础请求参数结构体 /// /// 包含所有 API 请求都需要的基础参数,如设备信息、应用信息、网络状态等。 /// 这些参数会自动添加到每个 API 请求中,确保服务器能够获取到完整的客户端信息。 /// /// 主要功能: /// - 自动收集设备和应用信息 /// - 生成安全签名 /// - 支持不同环境的配置 /// /// 使用示例: /// ```swift /// var baseRequest = BaseRequest() /// baseRequest.generateSignature(with: ["key": "value"]) /// ``` struct BaseRequest: Codable { let acceptLanguage: String let os: String = "iOS" let osVersion: String let netType: Int let ispType: String let channel: String let model: String let deviceId: String let appVersion: String let app: String let lang: String let mcc: String? let spType: String? var pubSign: String enum CodingKeys: String, CodingKey { case acceptLanguage = "Accept-Language" case os, osVersion, netType, ispType, channel, model, deviceId case appVersion, app, lang, mcc, spType case pubSign = "pub_sign" } init() { // 获取系统首选语言 let preferredLanguage = Locale.current.languageCode ?? "en" self.acceptLanguage = preferredLanguage self.lang = preferredLanguage // 获取系统版本 self.osVersion = UIDevice.current.systemVersion // 获取设备型号 self.model = UIDevice.current.model // 生成设备ID self.deviceId = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString // 获取应用版本 self.appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0" // 应用名称 self.app = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "eparty" // 网络类型检测(WiFi=2, 蜂窝网络=1) self.netType = NetworkTypeDetector.getCurrentNetworkType() // 运营商信息 let carrierInfo = CarrierInfoManager.getCarrierInfo() self.ispType = carrierInfo.ispType self.mcc = carrierInfo.mcc == "65535" ? nil : carrierInfo.mcc self.spType = self.mcc // 渠道信息 #if DEBUG self.channel = "molistar_enterprise" #else self.channel = "appstore" #endif // 生成符合规范的签名(先生成基础参数字典,然后计算签名) self.pubSign = "" // 临时值,稍后计算 } /// 生成符合 API 规则的安全签名 /// /// 该方法按照服务器要求的签名算法生成请求签名: /// 1. 合并基础参数和请求参数 /// 2. 过滤掉系统级参数 /// 3. 按 key 升序排序 /// 4. 拼接参数字符串 /// 5. 添加密钥并生成 MD5 签名 /// /// - Parameter requestParams: 请求特定的参数字典 mutating func generateSignature(with requestParams: [String: Any] = [:]) { // 1. 合并基础参数和请求参数 var allParams = requestParams // 添加基础参数到字典中 allParams["Accept-Language"] = self.acceptLanguage allParams["os"] = self.os allParams["osVersion"] = self.osVersion allParams["netType"] = self.netType allParams["ispType"] = self.ispType allParams["channel"] = self.channel allParams["model"] = self.model allParams["deviceId"] = self.deviceId allParams["appVersion"] = self.appVersion allParams["app"] = self.app allParams["lang"] = self.lang if let mcc = self.mcc { allParams["mcc"] = mcc } if let spType = self.spType { allParams["spType"] = spType } // 2. 移除系统级参数(根据 API rule) let systemParams = [ "Accept-Language", "pub_uid", "appVersion", "appVersionCode", "channel", "deviceId", "ispType", "netType", "os", "osVersion", "app", "ticket", "client", "lang", "mcc" ] var filteredParams = allParams for param in systemParams { filteredParams.removeValue(forKey: param) } // 3. 按 key 升序排序并拼接 // 拼接格式 "key0=value0&key1=value1&key2=value2" let sortedKeys = filteredParams.keys.sorted() let paramString = sortedKeys.map { key in "\(key)=\(String(describing: filteredParams[key] ?? ""))" }.joined(separator: "&") // 4. 添加密钥 let keyString = "key=rpbs6us1m8r2j9g6u06ff2bo18orwaya" let finalString = paramString.isEmpty ? keyString : "\(paramString)&\(keyString)" // 5. 生成大写 MD5 签名 self.pubSign = finalString.md5().uppercased() } } // MARK: - Network Type Detector struct NetworkTypeDetector { static func getCurrentNetworkType() -> Int { // WiFi = 2, 蜂窝网络 = 1 // 这里是简化实现,实际应该检测网络状态 return 2 // 默认蜂窝网络 } } // MARK: - Carrier Info Manager struct CarrierInfoManager { struct CarrierInfo { let ispType: String let mcc: String? } static func getCarrierInfo() -> CarrierInfo { // 简化实现,实际应该获取真实运营商信息 return CarrierInfo(ispType: "65535", mcc: nil) } } // MARK: - User Info Manager (for Headers) struct UserInfoManager { private static let userDefaults = UserDefaults.standard // MARK: - Storage Keys private enum StorageKeys { static let userId = "user_id" static let accessToken = "access_token" static let ticket = "user_ticket" static let userInfo = "user_info" static let accountModel = "account_model" // 新增:AccountModel存储键 } // MARK: - User ID Management static func getCurrentUserId() -> String? { return userDefaults.string(forKey: StorageKeys.userId) } static func saveUserId(_ userId: String) { userDefaults.set(userId, forKey: StorageKeys.userId) userDefaults.synchronize() print("💾 保存用户ID: \(userId)") } // MARK: - Access Token Management static func getAccessToken() -> String? { return userDefaults.string(forKey: StorageKeys.accessToken) } static func saveAccessToken(_ accessToken: String) { userDefaults.set(accessToken, forKey: StorageKeys.accessToken) userDefaults.synchronize() print("💾 保存 Access Token") } // MARK: - Ticket Management (内存存储) private static var currentTicket: String? static func getCurrentUserTicket() -> String? { return currentTicket } static func saveTicket(_ ticket: String) { currentTicket = ticket print("💾 保存 Ticket 到内存") } static func clearTicket() { currentTicket = nil print("🗑️ 清除 Ticket") } // MARK: - User Info Management static func saveUserInfo(_ userInfo: UserInfo) { do { let data = try JSONEncoder().encode(userInfo) userDefaults.set(data, forKey: StorageKeys.userInfo) userDefaults.synchronize() // 同时保存用户ID if let userId = userInfo.userId { saveUserId(userId) } print("💾 保存用户信息成功") } catch { print("❌ 保存用户信息失败: \(error)") } } static func getUserInfo() -> UserInfo? { guard let data = userDefaults.data(forKey: StorageKeys.userInfo) else { return nil } do { return try JSONDecoder().decode(UserInfo.self, from: data) } catch { print("❌ 解析用户信息失败: \(error)") return nil } } // MARK: - Complete Authentication Data Management /// 保存完整的认证信息(OAuth Token + Ticket + 用户信息) static func saveCompleteAuthenticationData( accessToken: String, ticket: String, uid: Int?, // 修改:从String?改为Int? userInfo: UserInfo? ) { saveAccessToken(accessToken) saveTicket(ticket) if let uid = uid { saveUserId("\(uid)") // 转换为字符串保存 } if let userInfo = userInfo { saveUserInfo(userInfo) } print("✅ 完整认证信息保存成功") } /// 检查是否有有效的认证信息 static func hasValidAuthentication() -> Bool { return getAccessToken() != nil && getCurrentUserTicket() != nil } /// 清除所有认证信息 static func clearAllAuthenticationData() { userDefaults.removeObject(forKey: StorageKeys.userId) userDefaults.removeObject(forKey: StorageKeys.accessToken) userDefaults.removeObject(forKey: StorageKeys.userInfo) clearAccountModel() // 新增:清除 AccountModel clearTicket() userDefaults.synchronize() print("🗑️ 清除所有认证信息") } /// 尝试恢复 Ticket(用于应用重启后) static func restoreTicketIfNeeded() async -> Bool { guard let accessToken = getAccessToken(), getCurrentUserTicket() == nil else { return false } print("🔄 尝试使用 Access Token 恢复 Ticket...") // 这里需要注入 APIService 依赖,暂时返回 false // 实际实现中应该调用 TicketHelper.createTicketRequest return false } // MARK: - Account Model Management /// 保存 AccountModel /// - Parameter accountModel: 要保存的账户模型 static func saveAccountModel(_ accountModel: AccountModel) { do { let data = try JSONEncoder().encode(accountModel) userDefaults.set(data, forKey: StorageKeys.accountModel) userDefaults.synchronize() // 同时更新各个独立字段(向后兼容) if let uid = accountModel.uid { saveUserId(uid) } if let accessToken = accountModel.accessToken { saveAccessToken(accessToken) } if let ticket = accountModel.ticket { saveTicket(ticket) } print("💾 AccountModel 保存成功") } catch { print("❌ AccountModel 保存失败: \(error)") } } /// 获取 AccountModel /// - Returns: 存储的账户模型,如果不存在或解析失败返回 nil static func getAccountModel() -> AccountModel? { guard let data = userDefaults.data(forKey: StorageKeys.accountModel) else { return nil } do { return try JSONDecoder().decode(AccountModel.self, from: data) } catch { print("❌ AccountModel 解析失败: \(error)") return nil } } /// 更新 AccountModel 中的 ticket /// - Parameter ticket: 新的票据 static func updateAccountModelTicket(_ ticket: String) { guard var accountModel = getAccountModel() else { print("❌ 无法更新 ticket:AccountModel 不存在") return } accountModel.ticket = ticket saveAccountModel(accountModel) saveTicket(ticket) // 同时更新内存中的 ticket } /// 检查是否有有效的 AccountModel /// - Returns: 是否存在有效的账户模型 static func hasValidAccountModel() -> Bool { guard let accountModel = getAccountModel() else { return false } return accountModel.hasValidAuthentication } /// 清除 AccountModel static func clearAccountModel() { userDefaults.removeObject(forKey: StorageKeys.accountModel) userDefaults.synchronize() print("🗑️ AccountModel 已清除") } } // MARK: - API Request Protocol /// API 请求协议 /// /// 定义了所有 API 请求必须实现的接口,提供了类型安全的请求定义方式。 /// 每个具体的 API 请求都应该实现这个协议。 /// /// 协议要求: /// - Response: 关联类型,定义响应数据的类型 /// - endpoint: API 端点路径 /// - method: HTTP 请求方法 /// - 可选的查询参数、请求体参数、请求头等 /// /// 使用示例: /// ```swift /// struct LoginRequest: APIRequestProtocol { /// typealias Response = LoginResponse /// let endpoint = "/auth/login" /// let method: HTTPMethod = .POST /// // ... 其他属性 /// } /// ``` protocol APIRequestProtocol { associatedtype Response: Codable var endpoint: String { get } var method: HTTPMethod { get } var queryParameters: [String: String]? { get } var bodyParameters: [String: Any]? { get } var headers: [String: String]? { get } var customHeaders: [String: String]? { get } // 新增:自定义请求头 var timeout: TimeInterval { get } var includeBaseParameters: Bool { get } } extension APIRequestProtocol { var timeout: TimeInterval { 30.0 } var includeBaseParameters: Bool { true } var headers: [String: String]? { nil } var customHeaders: [String: String]? { nil } // 新增:默认实现 } // MARK: - Generic API Response struct APIResponse: Codable { let data: T? let status: String? let message: String? let code: Int? } // 注意:String+MD5 扩展已移至 Utils/Extensions/String+MD5.swift