// // AppStoreCacheMigrator.swift // suixinkan // import Foundation /// 管理 AppStore 持久化结构版本,并在覆盖升级时清理不再兼容的旧缓存。 enum AppStoreCacheMigrator { /// 当前 AppStore 缓存结构版本。 static let currentSchemaVersion = 1 /// AppStore 缓存结构版本标记。 static let schemaVersionKey = "suixinkan.cache.schema.version" private static let exactKeysToRemove: Set = [ // 旧线上会话、推送与首页缓存。 "suixinkan.session.v1", "apns_device_token", "apns_uploaded_token", "home.common.menu.uris", "home.common.menu.android.baseline.v2", "home.routing.unknown.records", "payment.voice.broadcast.enabled", // 重构版预发布阶段的全局会话字段。 "key_in_token", "key_last_login_username", "key_privacy_agreement_accepted", "key_in_user_id", "key_in_user_name", "key_in_real_name", "key_in_avatar", "key_in_phone", "key_in_account_type", "key_in_account_display_name", "key_in_role_id", "key_in_role_code", "key_in_role_name", "key_in_current_scenic_id", "key_in_current_scenic_name", "key_in_current_store_id", // 可能由早期实现写入的未分域业务字段。 "key_role_permission_list", "key_in_permission", "key_current_role_scenic_list", "key_online_status", "key_last_location_report_time", "key_location_reminder_minutes", "key_is_open_receive_voice", // 旧线上排队全局偏好与残留数据。 "scenic_queue_tts_enabled", "scenic_queue_background_poll_enabled", "scenic_queue_selected_spot_id", "scenic_queue_selected_spot_name", "scenic_queue_custom_tts_text", "scenic_queue_photo_estimate_seconds", "scenic_queue_broadcast_interval_seconds", "scenic_queue_countdown_threshold_seconds", "scenic_queue_show_start_shooting_button", "scenic_queue_auto_call_ahead_count", "scenic_queue_quick_call_button_enabled", "scenic_queue_prepare_call_button_enabled", "scenic_queue_config_logs", "scenic_queue_settings_snapshot", "scenic_queue_preset_voices", ] private static let preReleaseAccountScopedSuffixes = [ "key_in_role_id", "key_role_permission_list", "key_in_permission", "key_current_role_scenic_list", "key_online_status", "key_last_location_report_time", "key_location_reminder_minutes", "key_is_open_receive_voice", ] private static let dynamicKeyPatterns = [ #"^account_.+_scenic_[^_]+_scenic_queue_selected_spot_(id|name)$"#, #"^account_.+_scenic_[^_]+_scenic_queue_custom_tts_text_spot_[^_]+$"#, #"^account_.+_scenic_[^_]+_scenic_queue_settings_snapshot_spot_[^_]+$"#, #"^account_.+_scenic_[^_]+_scenic_queue_preset_voices_spot_[^_]+$"#, #"^scenic_[^_]+_spot_[^_]+_scenic_queue_(settings_snapshot|preset_voices)$"#, #"^scenic_[^_]+_scenic_queue_settings_snapshot$"#, #"^.+_role_.*_common_uris$"#, #"^.+__scenic_queue_(punch_spot_id|punch_spot_name|punch_spot_remark|remark|settings_snapshot|offline_tts_v1|preset_voices_v1)$"#, ] /// 在缓存结构版本落后时执行一次旧数据清理,并在成功后写入当前版本。 static func migrateIfNeeded(defaults: UserDefaults) { guard defaults.integer(forKey: schemaVersionKey) < currentSchemaVersion else { return } let persistedKeys = Array(defaults.dictionaryRepresentation().keys) for key in persistedKeys where shouldRemove(key) { defaults.removeObject(forKey: key) } defaults.set(currentSchemaVersion, forKey: schemaVersionKey) } private static func shouldRemove(_ key: String) -> Bool { if exactKeysToRemove.contains(key) { return true } if preReleaseAccountScopedSuffixes.contains(where: { key.hasSuffix("_\($0)") }) { return true } return dynamicKeyPatterns.contains { pattern in key.range(of: pattern, options: .regularExpression) != nil } } }