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 keychain = KeychainManager.shared // MARK: - Storage Keys private enum StorageKeys { static let accountModel = "account_model" static let userInfo = "user_info" } // MARK: - 内存缓存 private static var accountModelCache: AccountModel? private static var userInfoCache: UserInfo? private static let cacheQueue = DispatchQueue(label: "com.yana.userinfo.cache", attributes: .concurrent) // MARK: - User ID Management (基于 AccountModel) static func getCurrentUserId() -> String? { return getAccountModel()?.uid } // MARK: - Access Token Management (基于 AccountModel) static func getAccessToken() -> String? { return getAccountModel()?.accessToken } // MARK: - Ticket Management (优先从 AccountModel 获取) private static var currentTicket: String? static func getCurrentUserTicket() -> String? { // 优先从 AccountModel 获取 ticket(确保一致性) if let accountTicket = getAccountModel()?.ticket, !accountTicket.isEmpty { return accountTicket } // 备选:从内存获取(用于兼容性) return currentTicket } static func saveTicket(_ ticket: String) { currentTicket = ticket debugInfo("💾 保存 Ticket 到内存") } static func clearTicket() { currentTicket = nil debugInfo("🗑️ 清除 Ticket") } // MARK: - User Info Management static func saveUserInfo(_ userInfo: UserInfo) { cacheQueue.async(flags: .barrier) { do { try keychain.store(userInfo, forKey: StorageKeys.userInfo) userInfoCache = userInfo debugInfo("💾 保存用户信息成功") } catch { debugError("❌ 保存用户信息失败: \(error)") } } } static func getUserInfo() -> UserInfo? { return cacheQueue.sync { // 先检查缓存 if let cached = userInfoCache { return cached } // 从 Keychain 读取 do { let userInfo = try keychain.retrieve(UserInfo.self, forKey: StorageKeys.userInfo) userInfoCache = userInfo return userInfo } catch { debugError("❌ 读取用户信息失败: \(error)") return nil } } } // MARK: - Complete Authentication Data Management /// 保存完整的认证信息(OAuth Token + Ticket + 用户信息) static func saveCompleteAuthenticationData( accessToken: String, ticket: String, uid: Int?, userInfo: UserInfo? ) { // 创建新的 AccountModel let accountModel = AccountModel( uid: uid != nil ? "\(uid!)" : nil, jti: nil, tokenType: "bearer", refreshToken: nil, netEaseToken: nil, accessToken: accessToken, expiresIn: nil, scope: nil, ticket: ticket ) saveAccountModel(accountModel) saveTicket(ticket) if let userInfo = userInfo { saveUserInfo(userInfo) } debugInfo("✅ 完整认证信息保存成功") } /// 检查是否有有效的认证信息 static func hasValidAuthentication() -> Bool { return getAccessToken() != nil && getCurrentUserTicket() != nil } /// 清除所有认证信息 static func clearAllAuthenticationData() { clearAccountModel() clearUserInfo() clearTicket() debugInfo("🗑️ 清除所有认证信息") } /// 尝试恢复 Ticket(用于应用重启后) static func restoreTicketIfNeeded() async -> Bool { guard let accessToken = getAccessToken(), getCurrentUserTicket() == nil else { return false } debugInfo("🔄 尝试使用 Access Token 恢复 Ticket...") // 这里需要注入 APIService 依赖,暂时返回 false // 实际实现中应该调用 TicketHelper.createTicketRequest return false } // MARK: - Account Model Management /// 保存 AccountModel /// - Parameter accountModel: 要保存的账户模型 static func saveAccountModel(_ accountModel: AccountModel) { cacheQueue.async(flags: .barrier) { do { try keychain.store(accountModel, forKey: StorageKeys.accountModel) accountModelCache = accountModel // 同步更新 ticket 到内存 if let ticket = accountModel.ticket { saveTicket(ticket) } debugInfo("💾 AccountModel 保存成功") } catch { debugError("❌ AccountModel 保存失败: \(error)") } } } /// 获取 AccountModel /// - Returns: 存储的账户模型,如果不存在或解析失败返回 nil static func getAccountModel() -> AccountModel? { return cacheQueue.sync { // 先检查缓存 if let cached = accountModelCache { return cached } // 从 Keychain 读取 do { let accountModel = try keychain.retrieve(AccountModel.self, forKey: StorageKeys.accountModel) accountModelCache = accountModel return accountModel } catch { debugError("❌ 读取 AccountModel 失败: \(error)") return nil } } } /// 更新 AccountModel 中的 ticket /// - Parameter ticket: 新的票据 static func updateAccountModelTicket(_ ticket: String) { guard var accountModel = getAccountModel() else { debugError("❌ 无法更新 ticket:AccountModel 不存在") return } accountModel = AccountModel( uid: accountModel.uid, jti: accountModel.jti, tokenType: accountModel.tokenType, refreshToken: accountModel.refreshToken, netEaseToken: accountModel.netEaseToken, accessToken: accountModel.accessToken, expiresIn: accountModel.expiresIn, scope: accountModel.scope, 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() { cacheQueue.async(flags: .barrier) { do { try keychain.delete(forKey: StorageKeys.accountModel) accountModelCache = nil debugInfo("🗑️ AccountModel 已清除") } catch { debugError("❌ 清除 AccountModel 失败: \(error)") } } } /// 清除用户信息 static func clearUserInfo() { cacheQueue.async(flags: .barrier) { do { try keychain.delete(forKey: StorageKeys.userInfo) userInfoCache = nil debugInfo("🗑️ UserInfo 已清除") } catch { debugError("❌ 清除 UserInfo 失败: \(error)") } } } /// 清除所有缓存(用于测试或重置) static func clearAllCache() { cacheQueue.async(flags: .barrier) { accountModelCache = nil userInfoCache = nil debugInfo("🗑️ 清除所有内存缓存") } } /// 预加载缓存(提升首次访问性能) static func preloadCache() { cacheQueue.async { // 预加载 AccountModel _ = getAccountModel() // 预加载 UserInfo _ = getUserInfo() debugInfo("🚀 缓存预加载完成") } } // MARK: - Authentication Validation /// 检查当前认证状态是否有效 /// - Returns: 认证状态结果 static func checkAuthenticationStatus() -> AuthenticationStatus { return cacheQueue.sync { guard let accountModel = getAccountModel() else { debugInfo("🔍 认证检查:未找到 AccountModel") return .notFound } // 检查 uid 是否有效 guard let uid = accountModel.uid, !uid.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { debugInfo("🔍 认证检查:uid 无效") return .invalid } // 检查 ticket 是否有效 guard let ticket = accountModel.ticket, !ticket.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { debugInfo("🔍 认证检查:ticket 无效") return .invalid } // 可选:检查 access token 是否有效 guard let accessToken = accountModel.accessToken, !accessToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { debugInfo("🔍 认证检查:access token 无效") return .invalid } debugInfo("🔍 认证检查:认证有效 - uid: \(uid), ticket: \(ticket.prefix(10))...") return .valid } } /// 认证状态枚举 enum AuthenticationStatus: Equatable { case valid // 认证有效,可以自动登录 case invalid // 认证信息不完整或无效 case notFound // 未找到认证信息 var description: String { switch self { case .valid: return "认证有效" case .invalid: return "认证无效" case .notFound: return "未找到认证信息" } } /// 是否可以自动登录 var canAutoLogin: Bool { return self == .valid } } // MARK: - Testing and Debugging /// 测试认证 header 功能(仅用于调试) /// 模拟用户登录状态并验证 header 添加逻辑 static func testAuthenticationHeaders() { #if DEBUG debugInfo("\n🧪 开始测试认证 header 功能") // 测试1:未登录状态 debugInfo("📝 测试1:未登录状态") clearAllAuthenticationData() let headers1 = APIConfiguration.defaultHeaders let hasAuthHeaders1 = headers1.keys.contains("pub_uid") || headers1.keys.contains("pub_ticket") debugInfo(" 认证 headers 存在: \(hasAuthHeaders1) (应该为 false)") // 测试2:模拟登录状态 debugInfo("📝 测试2:模拟登录状态") let testAccount = AccountModel( uid: "12345", jti: "test-jti", tokenType: "bearer", refreshToken: nil, netEaseToken: nil, accessToken: "test-access-token", expiresIn: 3600, scope: "read write", ticket: "test-ticket-12345678901234567890" ) saveAccountModel(testAccount) let headers2 = APIConfiguration.defaultHeaders let hasUid = headers2["pub_uid"] == "12345" let hasTicket = headers2["pub_ticket"] == "test-ticket-12345678901234567890" debugInfo(" pub_uid 正确: \(hasUid) (应该为 true)") debugInfo(" pub_ticket 正确: \(hasTicket) (应该为 true)") // 测试3:清理测试数据 debugInfo("📝 测试3:清理测试数据") clearAllAuthenticationData() debugInfo("✅ 认证 header 测试完成\n") #endif } } // 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 } // MARK: - Loading Configuration /// 是否显示 loading 动画,默认 true var shouldShowLoading: Bool { get } /// 是否显示错误信息,默认 true var shouldShowError: Bool { get } } extension APIRequestProtocol { var timeout: TimeInterval { 30.0 } var includeBaseParameters: Bool { true } var headers: [String: String]? { nil } var customHeaders: [String: String]? { nil } // 新增:默认实现 // MARK: - Loading Configuration Defaults var shouldShowLoading: Bool { true } var shouldShowError: Bool { true } } // 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