升级 AppStore 缓存 Key 并在覆盖安装时强制重新登录。
统一账号作用域 Key 对齐 Android,新增缓存 schema 迁移清理旧数据,并同步更新相关模块与单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -44,13 +44,17 @@ final class AppSessionStore {
|
||||
case phone = "key_in_phone"
|
||||
case accountType = "key_in_account_type"
|
||||
case accountDisplayName = "key_in_account_display_name"
|
||||
}
|
||||
|
||||
private enum AccountKey: String, CaseIterable {
|
||||
case roleCode = "key_in_role_code"
|
||||
case roleName = "key_in_role_name"
|
||||
case currentScenicId = "key_in_current_scenic_id"
|
||||
case currentScenicName = "key_in_current_scenic_name"
|
||||
case currentStoreId = "key_in_current_store_id"
|
||||
}
|
||||
|
||||
private static let currentStoreIdSuffix = "scenic_store_id"
|
||||
|
||||
private let defaults: UserDefaults
|
||||
|
||||
/// 使用指定 UserDefaults 创建会话存储。
|
||||
@ -126,41 +130,74 @@ final class AppSessionStore {
|
||||
|
||||
/// 后端返回的原始角色编码。
|
||||
var roleCode: String {
|
||||
get { string(for: .roleCode) }
|
||||
set { defaults.set(newValue, forKey: Key.roleCode.rawValue) }
|
||||
get { accountString(for: .roleCode) }
|
||||
set { setAccountString(newValue, for: .roleCode) }
|
||||
}
|
||||
|
||||
/// 当前角色名称。
|
||||
var roleName: String {
|
||||
get { string(for: .roleName) }
|
||||
set { defaults.set(newValue, forKey: Key.roleName.rawValue) }
|
||||
get { accountString(for: .roleName) }
|
||||
set { setAccountString(newValue, for: .roleName) }
|
||||
}
|
||||
|
||||
/// 当前景区 ID,0 表示未选择。
|
||||
var currentScenicId: Int {
|
||||
get { Int(string(for: .currentScenicId)) ?? 0 }
|
||||
set { defaults.set(newValue > 0 ? String(newValue) : "", forKey: Key.currentScenicId.rawValue) }
|
||||
get { Int(accountString(for: .currentScenicId)) ?? 0 }
|
||||
set { setAccountString(newValue > 0 ? String(newValue) : "", for: .currentScenicId) }
|
||||
}
|
||||
|
||||
/// 当前景区名称。
|
||||
var currentScenicName: String {
|
||||
get { string(for: .currentScenicName) }
|
||||
set { defaults.set(newValue, forKey: Key.currentScenicName.rawValue) }
|
||||
get { accountString(for: .currentScenicName) }
|
||||
set { setAccountString(newValue, for: .currentScenicName) }
|
||||
}
|
||||
|
||||
/// 当前店铺 ID,0 表示未选择。
|
||||
var currentStoreId: Int {
|
||||
get { defaults.integer(forKey: Key.currentStoreId.rawValue) }
|
||||
set { defaults.set(newValue, forKey: Key.currentStoreId.rawValue) }
|
||||
get {
|
||||
guard let key = scenicScopedKey(Self.currentStoreIdSuffix) else { return 0 }
|
||||
return defaults.integer(forKey: key)
|
||||
}
|
||||
set {
|
||||
guard let key = scenicScopedKey(Self.currentStoreIdSuffix) else { return }
|
||||
defaults.set(newValue, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前账号缓存前缀,对齐 Android `getAccountCachePrefix`。
|
||||
var accountCachePrefix: String {
|
||||
let uid = userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let type = accountType.rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if uid.isEmpty { return "guest" }
|
||||
if type.isEmpty { return uid }
|
||||
return "\(uid)_\(type)"
|
||||
guard !uid.isEmpty, !type.isEmpty else { return "" }
|
||||
return "\(type)_\(uid)"
|
||||
}
|
||||
|
||||
/// 为当前完整账号生成账号作用域 Key;账号信息不完整时返回 nil。
|
||||
func accountScopedKey(_ suffix: String) -> String? {
|
||||
let scope = accountCachePrefix
|
||||
let normalizedSuffix = suffix.trimmingCharacters(in: CharacterSet(charactersIn: "_"))
|
||||
guard !scope.isEmpty, !normalizedSuffix.isEmpty else { return nil }
|
||||
return "\(scope)_\(normalizedSuffix)"
|
||||
}
|
||||
|
||||
/// 为当前账号与景区生成作用域 Key;缺少账号或景区时返回 nil。
|
||||
func scenicScopedKey(_ suffix: String, scenicId: Int? = nil) -> String? {
|
||||
let resolvedScenicId = scenicId ?? currentScenicId
|
||||
let normalizedSuffix = suffix.trimmingCharacters(in: CharacterSet(charactersIn: "_"))
|
||||
guard !accountCachePrefix.isEmpty, resolvedScenicId > 0, !normalizedSuffix.isEmpty else { return nil }
|
||||
return "\(accountCachePrefix)_scenic_\(resolvedScenicId)_\(normalizedSuffix)"
|
||||
}
|
||||
|
||||
/// 为当前账号、景区与点位生成作用域 Key;任一业务标识缺失时返回 nil。
|
||||
func scenicSpotScopedKey(_ suffix: String, spotId: String, scenicId: Int? = nil) -> String? {
|
||||
let resolvedScenicId = scenicId ?? currentScenicId
|
||||
let normalizedSpotId = spotId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let normalizedSuffix = suffix.trimmingCharacters(in: CharacterSet(charactersIn: "_"))
|
||||
guard !accountCachePrefix.isEmpty,
|
||||
resolvedScenicId > 0,
|
||||
!normalizedSpotId.isEmpty,
|
||||
!normalizedSuffix.isEmpty else { return nil }
|
||||
return "\(accountCachePrefix)_scenic_\(resolvedScenicId)_spot_\(normalizedSpotId)_\(normalizedSuffix)"
|
||||
}
|
||||
|
||||
/// 当前解析后的业务角色。
|
||||
@ -211,23 +248,41 @@ final class AppSessionStore {
|
||||
}
|
||||
|
||||
/// 清除登录态及账号快照,保留隐私授权和上次登录用户名。
|
||||
func clearAuthenticatedSession() {
|
||||
token = ""
|
||||
userId = ""
|
||||
userName = ""
|
||||
realName = ""
|
||||
avatar = ""
|
||||
phone = ""
|
||||
accountType = .unknown("")
|
||||
accountDisplayName = ""
|
||||
roleCode = ""
|
||||
roleName = ""
|
||||
currentScenicId = 0
|
||||
currentScenicName = ""
|
||||
currentStoreId = 0
|
||||
func clearAuthenticatedSession(accountScope: String) {
|
||||
if !accountScope.isEmpty {
|
||||
AccountKey.allCases.forEach {
|
||||
defaults.removeObject(forKey: "\(accountScope)_\($0.rawValue)")
|
||||
}
|
||||
let storePrefix = "\(accountScope)_scenic_"
|
||||
let storeSuffix = "_\(Self.currentStoreIdSuffix)"
|
||||
defaults.dictionaryRepresentation().keys
|
||||
.filter { $0.hasPrefix(storePrefix) && $0.hasSuffix(storeSuffix) }
|
||||
.forEach(defaults.removeObject(forKey:))
|
||||
}
|
||||
|
||||
[
|
||||
Key.token,
|
||||
.userId,
|
||||
.userName,
|
||||
.realName,
|
||||
.avatar,
|
||||
.phone,
|
||||
.accountType,
|
||||
.accountDisplayName,
|
||||
].forEach { defaults.removeObject(forKey: $0.rawValue) }
|
||||
}
|
||||
|
||||
private func string(for key: Key) -> String {
|
||||
defaults.string(forKey: key.rawValue) ?? ""
|
||||
}
|
||||
|
||||
private func accountString(for key: AccountKey) -> String {
|
||||
guard let scopedKey = accountScopedKey(key.rawValue) else { return "" }
|
||||
return defaults.string(forKey: scopedKey) ?? ""
|
||||
}
|
||||
|
||||
private func setAccountString(_ value: String, for key: AccountKey) {
|
||||
guard let scopedKey = accountScopedKey(key.rawValue) else { return }
|
||||
defaults.set(value, forKey: scopedKey)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user