import Foundation /// 数据迁移管理器 /// /// 负责将旧版本的 UserDefaults 存储数据迁移到新的 Keychain 存储方案。 /// 确保用户升级应用后无需重新登录。 /// /// 迁移策略: /// 1. 检测旧数据是否存在 /// 2. 迁移到 Keychain /// 3. 验证迁移结果 /// 4. 清理旧数据 final class DataMigrationManager { // MARK: - 单例 static let shared = DataMigrationManager() private init() {} // MARK: - 迁移状态 private let migrationCompleteKey = "keychain_migration_completed_v1" // MARK: - 旧版本存储键 private enum LegacyStorageKeys { static let userId = "user_id" static let accessToken = "access_token" static let userInfo = "user_info" static let accountModel = "account_model" static let appLanguage = "AppLanguage" } // MARK: - 迁移结果 enum MigrationResult { case completed // 迁移完成 case alreadyMigrated // 已经迁移过 case noDataToMigrate // 没有需要迁移的数据 case failed(Error) // 迁移失败 var description: String { switch self { case .completed: return "数据迁移完成" case .alreadyMigrated: return "数据已经迁移过" case .noDataToMigrate: return "没有需要迁移的数据" case .failed(let error): return "迁移失败: \(error.localizedDescription)" } } } // MARK: - 公共方法 /// 执行数据迁移 /// - Returns: 迁移结果 func performMigration() -> MigrationResult { debugInfo("🔄 开始检查数据迁移...") // 检查是否已经迁移过 if isMigrationCompleted() { debugInfo("✅ 数据已经迁移过,跳过迁移") return .alreadyMigrated } // 检查是否有需要迁移的数据 let legacyData = collectLegacyData() if legacyData.isEmpty { debugInfo("ℹ️ 没有发现需要迁移的数据") markMigrationCompleted() return .noDataToMigrate } debugInfo("📦 发现需要迁移的数据: \(legacyData.keys.joined(separator: ", "))") do { // 执行迁移 try migrateToKeychain(legacyData) // 验证迁移结果 try verifyMigration(legacyData) // 清理旧数据 cleanupLegacyData(legacyData.keys) // 标记迁移完成 markMigrationCompleted() debugInfo("✅ 数据迁移完成") return .completed } catch { debugError("❌ 数据迁移失败: \(error)") return .failed(error) } } /// 强制重新迁移(用于测试或修复) func forceMigration() -> MigrationResult { resetMigrationStatus() return performMigration() } // MARK: - 私有方法 /// 检查迁移是否已完成 private func isMigrationCompleted() -> Bool { return UserDefaults.standard.bool(forKey: migrationCompleteKey) } /// 标记迁移完成 private func markMigrationCompleted() { UserDefaults.standard.set(true, forKey: migrationCompleteKey) UserDefaults.standard.synchronize() } /// 重置迁移状态 private func resetMigrationStatus() { UserDefaults.standard.removeObject(forKey: migrationCompleteKey) UserDefaults.standard.synchronize() } /// 收集旧版本数据 private func collectLegacyData() -> [String: Any] { let userDefaults = UserDefaults.standard var legacyData: [String: Any] = [:] // 检查各种旧数据 if let userId = userDefaults.string(forKey: LegacyStorageKeys.userId) { legacyData[LegacyStorageKeys.userId] = userId } if let accessToken = userDefaults.string(forKey: LegacyStorageKeys.accessToken) { legacyData[LegacyStorageKeys.accessToken] = accessToken } if let userInfoData = userDefaults.data(forKey: LegacyStorageKeys.userInfo) { legacyData[LegacyStorageKeys.userInfo] = userInfoData } if let accountModelData = userDefaults.data(forKey: LegacyStorageKeys.accountModel) { legacyData[LegacyStorageKeys.accountModel] = accountModelData } if let appLanguage = userDefaults.string(forKey: LegacyStorageKeys.appLanguage) { legacyData[LegacyStorageKeys.appLanguage] = appLanguage } return legacyData } /// 迁移数据到 Keychain private func migrateToKeychain(_ legacyData: [String: Any]) throws { let keychain = KeychainManager.shared // 迁移 AccountModel(优先级最高,包含完整认证信息) if let accountModelData = legacyData[LegacyStorageKeys.accountModel] as? Data { do { let accountModel = try JSONDecoder().decode(AccountModel.self, from: accountModelData) try keychain.store(accountModel, forKey: "account_model") debugInfo("✅ AccountModel 迁移成功") } catch { debugError("❌ AccountModel 迁移失败: \(error)") // 如果 AccountModel 迁移失败,尝试从独立字段重建 try migrateAccountModelFromIndependentFields(legacyData) } } else { // 如果没有 AccountModel,从独立字段构建 try migrateAccountModelFromIndependentFields(legacyData) } // 迁移 UserInfo if let userInfoData = legacyData[LegacyStorageKeys.userInfo] as? Data { do { let userInfo = try JSONDecoder().decode(UserInfo.self, from: userInfoData) try keychain.store(userInfo, forKey: "user_info") debugInfo("✅ UserInfo 迁移成功") } catch { debugError("❌ UserInfo 迁移失败: \(error)") throw error } } // 迁移语言设置 if let appLanguage = legacyData[LegacyStorageKeys.appLanguage] as? String { try keychain.storeString(appLanguage, forKey: "AppLanguage") debugInfo("✅ 语言设置迁移成功") } } /// 从独立字段重建 AccountModel private func migrateAccountModelFromIndependentFields(_ legacyData: [String: Any]) throws { guard let userId = legacyData[LegacyStorageKeys.userId] as? String, let accessToken = legacyData[LegacyStorageKeys.accessToken] as? String else { debugInfo("ℹ️ 没有足够的独立字段来重建 AccountModel") return } let accountModel = AccountModel( uid: userId, jti: nil, tokenType: "bearer", refreshToken: nil, netEaseToken: nil, accessToken: accessToken, expiresIn: nil, scope: nil, ticket: nil ) try KeychainManager.shared.store(accountModel, forKey: "account_model") debugInfo("✅ 从独立字段重建 AccountModel 成功") } /// 验证迁移结果 private func verifyMigration(_ legacyData: [String: Any]) throws { let keychain = KeychainManager.shared // 验证 AccountModel if legacyData[LegacyStorageKeys.accountModel] != nil || (legacyData[LegacyStorageKeys.userId] != nil && legacyData[LegacyStorageKeys.accessToken] != nil) { let accountModel: AccountModel? = try keychain.retrieve(AccountModel.self, forKey: "account_model") guard accountModel != nil else { throw MigrationError.verificationFailed("AccountModel 验证失败") } } // 验证 UserInfo if legacyData[LegacyStorageKeys.userInfo] != nil { let userInfo: UserInfo? = try keychain.retrieve(UserInfo.self, forKey: "user_info") guard userInfo != nil else { throw MigrationError.verificationFailed("UserInfo 验证失败") } } // 验证语言设置 if legacyData[LegacyStorageKeys.appLanguage] != nil { let appLanguage = try keychain.retrieveString(forKey: "AppLanguage") guard appLanguage != nil else { throw MigrationError.verificationFailed("语言设置验证失败") } } debugInfo("✅ 迁移数据验证成功") } /// 清理旧数据 private func cleanupLegacyData(_ keys: Dictionary.Keys) { let userDefaults = UserDefaults.standard for key in keys { userDefaults.removeObject(forKey: key) debugInfo("🗑️ 清理旧数据: \(key)") } userDefaults.synchronize() debugInfo("✅ 旧数据清理完成") } } // MARK: - 迁移错误 enum MigrationError: Error, LocalizedError { case verificationFailed(String) case dataCorrupted(String) case keychainError(Error) var errorDescription: String? { switch self { case .verificationFailed(let message): return "验证失败: \(message)" case .dataCorrupted(let message): return "数据损坏: \(message)" case .keychainError(let error): return "Keychain 错误: \(error.localizedDescription)" } } } // MARK: - 应用启动时的迁移支持 extension DataMigrationManager { /// 在应用启动时执行迁移 /// 这个方法应该在 AppDelegate 或 App 的初始化阶段调用 static func performStartupMigration() { let migrationResult = DataMigrationManager.shared.performMigration() switch migrationResult { case .completed: debugInfo("🎉 应用启动时数据迁移完成") case .alreadyMigrated: break // 静默处理 case .noDataToMigrate: break // 静默处理 case .failed(let error): debugError("⚠️ 应用启动时数据迁移失败: \(error)") // 这里可以添加错误上报或降级策略 } } } // MARK: - 调试支持 #if DEBUG extension DataMigrationManager { /// 调试:打印旧数据信息 func debugPrintLegacyData() { let legacyData = collectLegacyData() debugInfo("🔍 旧版本数据:") for (key, value) in legacyData { debugInfo(" - \(key): \(type(of: value))") } } /// 调试:模拟创建旧数据(用于测试) func debugCreateLegacyData() { let userDefaults = UserDefaults.standard userDefaults.set("test_user_123", forKey: LegacyStorageKeys.userId) userDefaults.set("test_access_token", forKey: LegacyStorageKeys.accessToken) userDefaults.set("zh-Hans", forKey: LegacyStorageKeys.appLanguage) userDefaults.synchronize() debugInfo("🧪 已创建测试用的旧版本数据") } /// 调试:清除所有迁移相关数据 func debugClearAllData() { // 清除 Keychain 数据 do { try KeychainManager.shared.clearAll() } catch { debugError("❌ 清除 Keychain 数据失败: \(error)") } // 清除 UserDefaults 数据 let userDefaults = UserDefaults.standard let allKeys = [ LegacyStorageKeys.userId, LegacyStorageKeys.accessToken, LegacyStorageKeys.userInfo, LegacyStorageKeys.accountModel, LegacyStorageKeys.appLanguage, migrationCompleteKey ] for key in allKeys { userDefaults.removeObject(forKey: key) } userDefaults.synchronize() debugInfo("🧪 已清除所有迁移相关数据") } } #endif