import Foundation // 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" } @MainActor init() { // 获取系统首选语言 let preferredLanguage = Locale.current.language.languageCode?.identifier ?? "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 key = SigningKeyProvider.signingKey() let keyString = "key=\(key)" 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, 其他/无网络 = 0 return NetworkMonitor.shared.currentType } } // 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: - 内存缓存 // 已迁移到 UserInfoCacheActor private static let cacheQueue = DispatchQueue(label: "com.yana.userinfo.cache", attributes: .concurrent) // MARK: - User ID Management (基于 AccountModel) static func getCurrentUserId() async -> String? { return await getAccountModel()?.uid } // MARK: - Access Token Management (基于 AccountModel) static func getAccessToken() async -> String? { return await getAccountModel()?.accessToken } // MARK: - Ticket Management (优先从 AccountModel 获取) // 已迁移到 UserInfoCacheActor static func getCurrentUserTicket() async -> String? { // 优先从 AccountModel 获取 ticket(确保一致性) if let accountTicket = await getAccountModel()?.ticket, !accountTicket.isEmpty { return accountTicket } // 备选:从 actor 获取(用于兼容性) return await cacheActor.getCurrentTicket() } static func saveTicket(_ ticket: String) async { await cacheActor.setCurrentTicket(ticket) debugInfoSync("💾 保存 Ticket 到内存") } static func clearTicket() async { await cacheActor.clearCurrentTicket() debugInfoSync("🗑️ 清除 Ticket") } // MARK: - User Info Management static func saveUserInfo(_ userInfo: UserInfo) async { do { try keychain.store(userInfo, forKey: StorageKeys.userInfo) await cacheActor.setUserInfo(userInfo) debugInfoSync("💾 保存用户信息成功") } catch { debugErrorSync("❌ 保存用户信息失败: \(error)") } } static func getUserInfo() async -> UserInfo? { // 先检查缓存 if let cached = await cacheActor.getUserInfo() { return cached } // 从 Keychain 读取 do { let userInfo = try keychain.retrieve(UserInfo.self, forKey: StorageKeys.userInfo) await cacheActor.setUserInfo(userInfo) return userInfo } catch { debugErrorSync("❌ 读取用户信息失败: \(error)") return nil } } // MARK: - Complete Authentication Data Management /// 保存完整的认证信息(OAuth Token + Ticket + 用户信息) static func saveCompleteAuthenticationData( accessToken: String, ticket: String, uid: Int?, userInfo: UserInfo? ) async { // 创建新的 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 ) await saveAccountModel(accountModel) await saveTicket(ticket) if let userInfo = userInfo { await saveUserInfo(userInfo) } debugInfoSync("✅ 完整认证信息保存成功") } /// 检查是否有有效的认证信息 static func hasValidAuthentication() async -> Bool { let token = await getAccessToken() let ticket = await getCurrentUserTicket() return token != nil && ticket != nil } /// 清除所有认证信息 static func clearAllAuthenticationData() async { await clearAccountModel() await clearUserInfo() await clearTicket() debugInfoSync("🗑️ 清除所有认证信息") } /// 尝试恢复 Ticket(用于应用重启后) static func restoreTicketIfNeeded() async -> Bool { guard let _ = await getAccessToken(), await getCurrentUserTicket() == nil else { return false } debugInfoSync("🔄 尝试使用 Access Token 恢复 Ticket...") // 这里需要注入 APIService 依赖,暂时返回 false // 实际实现中应该调用 TicketHelper.createTicketRequest return false } // MARK: - Account Model Management /// 保存 AccountModel /// - Parameter accountModel: 要保存的账户模型 static func saveAccountModel(_ accountModel: AccountModel) async { do { try keychain.store(accountModel, forKey: StorageKeys.accountModel) await cacheActor.setAccountModel(accountModel) // 同步更新 ticket 到内存 if let ticket = accountModel.ticket { await saveTicket(ticket) } debugInfoSync("💾 AccountModel 保存成功") } catch { debugErrorSync("❌ AccountModel 保存失败: \(error)") } } /// 获取 AccountModel /// - Returns: 存储的账户模型,如果不存在或解析失败返回 nil static func getAccountModel() async -> AccountModel? { // 先检查缓存 if let cached = await cacheActor.getAccountModel() { return cached } // 从 Keychain 读取 do { let accountModel = try keychain.retrieve( AccountModel.self, forKey: StorageKeys.accountModel ) await cacheActor.setAccountModel(accountModel) return accountModel } catch { debugErrorSync("❌ 读取 AccountModel 失败: \(error)") return nil } } /// 更新 AccountModel 中的 ticket /// - Parameter ticket: 新的票据 static func updateAccountModelTicket(_ ticket: String) async { guard var accountModel = await getAccountModel() else { debugErrorSync("❌ 无法更新 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 ) await saveAccountModel(accountModel) await saveTicket(ticket) // 同时更新内存中的 ticket } /// 检查是否有有效的 AccountModel /// - Returns: 是否存在有效的账户模型 static func hasValidAccountModel() async -> Bool { guard let accountModel = await getAccountModel() else { return false } return accountModel.hasValidAuthentication } /// 清除 AccountModel static func clearAccountModel() async { do { try keychain.delete(forKey: StorageKeys.accountModel) await cacheActor.clearAccountModel() debugInfoSync("🗑️ AccountModel 已清除") } catch { debugErrorSync("❌ 清除 AccountModel 失败: \(error)") } } /// 清除用户信息 static func clearUserInfo() async { do { try keychain.delete(forKey: StorageKeys.userInfo) await cacheActor.clearUserInfo() debugInfoSync("🗑️ UserInfo 已清除") } catch { debugErrorSync("❌ 清除 UserInfo 失败: \(error)") } } /// 清除所有缓存(用于测试或重置) static func clearAllCache() async { await cacheActor.clearAccountModel() await cacheActor.clearUserInfo() debugInfoSync("🗑️ 清除所有内存缓存") } /// 预加载缓存(提升首次访问性能) static func preloadCache() async { await cacheActor.setAccountModel(await getAccountModel()) await cacheActor.setUserInfo(await getUserInfo()) debugInfoSync("🚀 缓存预加载完成") } // MARK: - Authentication Validation /// 检查当前认证状态是否有效 /// - Returns: 认证状态结果 static func checkAuthenticationStatus() async -> AuthenticationStatus { guard let accountModel = await getAccountModel() else { debugInfoSync("🔍 认证检查:未找到 AccountModel") return .notFound } guard let uid = accountModel.uid, !uid.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { debugInfoSync("🔍 认证检查:uid 无效") return .invalid } guard let ticket = accountModel.ticket, !ticket.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { debugInfoSync("🔍 认证检查:ticket 无效") return .invalid } guard let accessToken = accountModel.accessToken, !accessToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { debugInfoSync("🔍 认证检查:access token 无效") return .invalid } debugInfoSync("🔍 认证检查:认证有效 - 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() async { #if DEBUG debugInfoSync("\n🧪 开始测试认证 header 功能") // 测试1:未登录状态 debugInfoSync("📝 测试1:未登录状态") await clearAllAuthenticationData() let headers1 = await APIConfiguration.defaultHeaders() let hasAuthHeaders1 = headers1.keys.contains("pub_uid") || headers1.keys.contains("pub_ticket") debugInfoSync(" 认证 headers 存在: \(hasAuthHeaders1) (应该为 false)") // 测试2:模拟登录状态 debugInfoSync("📝 测试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" ) await saveAccountModel(testAccount) let headers2 = await APIConfiguration.defaultHeaders() let hasUid = headers2["pub_uid"] == "12345" let hasTicket = headers2["pub_ticket"] == "test-ticket-12345678901234567890" debugInfoSync(" pub_uid 正确: \(hasUid) (应该为 true)") debugInfoSync(" pub_ticket 正确: \(hasTicket) (应该为 true)") // 测试3:清理测试数据 debugInfoSync("📝 测试3:清理测试数据") await clearAllAuthenticationData() debugInfoSync("✅ 认证 header 测试完成\n") #endif } } // MARK: - User Info Cache Actor actor UserInfoCacheActor { private var accountModelCache: AccountModel? private var userInfoCache: UserInfo? private var currentTicket: String? // AccountModel func getAccountModel() -> AccountModel? { accountModelCache } func setAccountModel(_ model: AccountModel?) { accountModelCache = model } func clearAccountModel() { accountModelCache = nil } // UserInfo func getUserInfo() -> UserInfo? { userInfoCache } func setUserInfo(_ info: UserInfo?) { userInfoCache = info } func clearUserInfo() { userInfoCache = nil } // Ticket func getCurrentTicket() -> String? { currentTicket } func setCurrentTicket(_ ticket: String?) { currentTicket = ticket } func clearCurrentTicket() { currentTicket = nil } } extension UserInfoManager { static let cacheActor = UserInfoCacheActor() } // MARK: - API Request Protocol /// API 请求协议 /// /// 定义了所有 API 请求必须实现的接口,提供了类型安全的请求定义方式。 /// 每个具体的 API 请求都应该实现这个协议。 /// /// 协议要求: /// - Response: 关联类型,定义响应数据的类型,必须 Sendable /// - endpoint: API 端点路径 /// - method: HTTP 请求方法 /// - 可选的查询参数、请求体参数、请求头等 /// /// 使用示例: /// ```swift /// struct LoginRequest: APIRequestProtocol { /// typealias Response = LoginResponse /// let endpoint = "/auth/login" /// let method: HTTPMethod = .POST /// // ... 其他属性 /// } /// ``` protocol APIRequestProtocol: Sendable { associatedtype Response: Codable & Sendable 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 // MARK: - 腾讯云 COS Token 相关模型 // 注意:TcTokenRequest 和 TcTokenResponse 已迁移到 Utils/TCCos/Models/COSModels.swift // 请使用 COSModels.swift 中的版本 // 注意:TcTokenData 已迁移到 Utils/TCCos/Models/COSModels.swift // 请使用 COSModels.swift 中的 TcTokenData // MARK: - User Info API Management extension UserInfoManager { /// 从服务器获取用户信息 /// - Parameters: /// - uid: 用户ID,如果为nil则使用当前登录用户的ID /// - apiService: API服务依赖 /// - Returns: 用户信息,如果获取失败返回nil static func fetchUserInfoFromServer( uid: String? = nil, apiService: APIServiceProtocol ) async -> UserInfo? { // 确定要查询的用户ID let targetUid: String if let uid = uid { targetUid = uid } else { // 使用当前登录用户的ID guard let currentUid = await getCurrentUserId() else { debugErrorSync("❌ 无法获取用户信息:当前用户未登录") return nil } targetUid = currentUid } debugInfoSync("👤 开始获取用户信息") debugInfoSync(" 目标UID: \(targetUid)") do { let request = UserInfoHelper.createGetUserInfoRequest(uid: targetUid) let response = try await apiService.request(request) if response.isSuccess { debugInfoSync("✅ 用户信息获取成功") if let userInfo = response.data { // 保存到本地 await saveUserInfo(userInfo) debugInfoSync("💾 用户信息已保存到本地") return userInfo } else { debugErrorSync("❌ 用户信息为空") return nil } } else { debugErrorSync("❌ 获取用户信息失败: \(response.errorMessage)") return nil } } catch { debugErrorSync("❌ 获取用户信息异常: \(error.localizedDescription)") return nil } } /// 刷新当前用户的用户信息 /// - Parameter apiService: API服务依赖 /// - Returns: 是否刷新成功 static func refreshCurrentUserInfo(apiService: APIServiceProtocol) async -> Bool { guard let currentUid = await getCurrentUserId() else { debugErrorSync("❌ 无法刷新用户信息:当前用户未登录") return false } debugInfoSync("🔄 开始刷新当前用户信息") debugInfoSync(" 当前UID: \(currentUid)") if let _ = await fetchUserInfoFromServer(uid: currentUid, apiService: apiService) { debugInfoSync("✅ 用户信息刷新成功") return true } else { debugErrorSync("❌ 用户信息刷新失败") return false } } /// 获取指定用户的用户信息(带缓存) /// - Parameters: /// - uid: 用户ID /// - apiService: API服务依赖 /// - forceRefresh: 是否强制刷新,默认false /// - Returns: 用户信息,如果获取失败返回nil static func getUserInfoWithCache( uid: String, apiService: APIServiceProtocol, forceRefresh: Bool = false ) async -> UserInfo? { // 如果不强制刷新,先检查本地缓存 if !forceRefresh { if let cachedUserInfo = await getUserInfo() { debugInfoSync("📱 使用本地缓存的用户信息") return cachedUserInfo } } // 从服务器获取 debugInfoSync("🌐 从服务器获取用户信息") return await fetchUserInfoFromServer(uid: uid, apiService: apiService) } /// 在APP启动时自动获取用户信息(如果用户已登录) /// - Parameter apiService: API服务依赖 /// - Returns: 是否成功获取或已有缓存 static func autoFetchUserInfoOnAppLaunch(apiService: APIServiceProtocol) async -> Bool { // 检查用户是否已登录 let authStatus = await checkAuthenticationStatus() guard authStatus.canAutoLogin else { debugInfoSync("🔍 APP启动:用户未登录,跳过用户信息获取") return false } // 检查是否已有用户信息缓存 if let _ = await getUserInfo() { debugInfoSync("📱 APP启动:使用现有用户信息缓存") return true } // 自动获取用户信息 debugInfoSync("🔄 APP启动:自动获取用户信息") return await refreshCurrentUserInfo(apiService: apiService) } } // MARK: - 用户信息更新 struct UpdateUserRequest: APIRequestProtocol { typealias Response = UpdateUserResponse let avatar: String? let nick: String? let uid: Int let ticket: String var endpoint: String { APIEndpoint.updateUser.path } var method: HTTPMethod { .POST } // 参数全部通过queryParameters传递 var queryParameters: [String: String]? { var params: [String: String] = [ "uid": String(uid), "ticket": ticket ] if let avatar = avatar { params["avatar"] = avatar } if let nick = nick { params["nick"] = nick } return params } var bodyParameters: [String: Any]? { nil } } struct UpdateUserResponse: Codable, Equatable { let code: Int let message: String let data: UserInfo? }