升级 AppStore 缓存 Key 并在覆盖安装时强制重新登录。

统一账号作用域 Key 对齐 Android,新增缓存 schema 迁移清理旧数据,并同步更新相关模块与单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 09:35:55 +08:00
parent 05804ba7d6
commit c083f1d4b3
31 changed files with 852 additions and 123 deletions

View 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
}
}
}