升级 AppStore 缓存 Key 并在覆盖安装时强制重新登录。
统一账号作用域 Key 对齐 Android,新增缓存 schema 迁移清理旧数据,并同步更新相关模块与单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
116
suixinkan/DataStore/AppStoreCacheMigrator.swift
Normal file
116
suixinkan/DataStore/AppStoreCacheMigrator.swift
Normal file
@ -0,0 +1,116 @@
|
||||
//
|
||||
// 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<String> = [
|
||||
// 旧线上会话、推送与首页缓存。
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user