feat: 实现数据迁移和用户信息管理优化
- 在AppDelegate中集成数据迁移管理器,支持从UserDefaults迁移到Keychain。 - 重构UserInfoManager,使用Keychain存储用户信息,增加内存缓存以提升性能。 - 添加API加载效果视图,增强用户体验。 - 更新SplashFeature以支持自动登录和认证状态检查。 - 语言设置迁移至Keychain,确保用户设置的安全性。
This commit is contained in:
@@ -237,37 +237,27 @@ struct CarrierInfoManager {
|
||||
|
||||
// MARK: - User Info Manager (for Headers)
|
||||
struct UserInfoManager {
|
||||
private static let userDefaults = UserDefaults.standard
|
||||
private static let keychain = KeychainManager.shared
|
||||
|
||||
// MARK: - Storage Keys
|
||||
private enum StorageKeys {
|
||||
static let userId = "user_id"
|
||||
static let accessToken = "access_token"
|
||||
static let ticket = "user_ticket"
|
||||
static let accountModel = "account_model"
|
||||
static let userInfo = "user_info"
|
||||
static let accountModel = "account_model" // 新增:AccountModel存储键
|
||||
}
|
||||
|
||||
// MARK: - User ID Management
|
||||
// MARK: - 内存缓存
|
||||
private static var accountModelCache: AccountModel?
|
||||
private static var userInfoCache: UserInfo?
|
||||
private static let cacheQueue = DispatchQueue(label: "com.yana.userinfo.cache", attributes: .concurrent)
|
||||
|
||||
// MARK: - User ID Management (基于 AccountModel)
|
||||
static func getCurrentUserId() -> String? {
|
||||
return userDefaults.string(forKey: StorageKeys.userId)
|
||||
return getAccountModel()?.uid
|
||||
}
|
||||
|
||||
static func saveUserId(_ userId: String) {
|
||||
userDefaults.set(userId, forKey: StorageKeys.userId)
|
||||
userDefaults.synchronize()
|
||||
print("💾 保存用户ID: \(userId)")
|
||||
}
|
||||
|
||||
// MARK: - Access Token Management
|
||||
// MARK: - Access Token Management (基于 AccountModel)
|
||||
static func getAccessToken() -> String? {
|
||||
return userDefaults.string(forKey: StorageKeys.accessToken)
|
||||
}
|
||||
|
||||
static func saveAccessToken(_ accessToken: String) {
|
||||
userDefaults.set(accessToken, forKey: StorageKeys.accessToken)
|
||||
userDefaults.synchronize()
|
||||
print("💾 保存 Access Token")
|
||||
return getAccountModel()?.accessToken
|
||||
}
|
||||
|
||||
// MARK: - Ticket Management (内存存储)
|
||||
@@ -289,32 +279,33 @@ struct UserInfoManager {
|
||||
|
||||
// MARK: - User Info Management
|
||||
static func saveUserInfo(_ userInfo: UserInfo) {
|
||||
do {
|
||||
let data = try JSONEncoder().encode(userInfo)
|
||||
userDefaults.set(data, forKey: StorageKeys.userInfo)
|
||||
userDefaults.synchronize()
|
||||
|
||||
// 同时保存用户ID
|
||||
if let userId = userInfo.userId {
|
||||
saveUserId(userId)
|
||||
cacheQueue.async(flags: .barrier) {
|
||||
do {
|
||||
try keychain.store(userInfo, forKey: StorageKeys.userInfo)
|
||||
userInfoCache = userInfo
|
||||
print("💾 保存用户信息成功")
|
||||
} catch {
|
||||
print("❌ 保存用户信息失败: \(error)")
|
||||
}
|
||||
|
||||
print("💾 保存用户信息成功")
|
||||
} catch {
|
||||
print("❌ 保存用户信息失败: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
static func getUserInfo() -> UserInfo? {
|
||||
guard let data = userDefaults.data(forKey: StorageKeys.userInfo) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
return try JSONDecoder().decode(UserInfo.self, from: data)
|
||||
} catch {
|
||||
print("❌ 解析用户信息失败: \(error)")
|
||||
return nil
|
||||
return cacheQueue.sync {
|
||||
// 先检查缓存
|
||||
if let cached = userInfoCache {
|
||||
return cached
|
||||
}
|
||||
|
||||
// 从 Keychain 读取
|
||||
do {
|
||||
let userInfo = try keychain.retrieve(UserInfo.self, forKey: StorageKeys.userInfo)
|
||||
userInfoCache = userInfo
|
||||
return userInfo
|
||||
} catch {
|
||||
print("❌ 读取用户信息失败: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,15 +314,24 @@ struct UserInfoManager {
|
||||
static func saveCompleteAuthenticationData(
|
||||
accessToken: String,
|
||||
ticket: String,
|
||||
uid: Int?, // 修改:从String?改为Int?
|
||||
uid: Int?,
|
||||
userInfo: UserInfo?
|
||||
) {
|
||||
saveAccessToken(accessToken)
|
||||
saveTicket(ticket)
|
||||
// 创建新的 AccountModel
|
||||
let accountModel = AccountModel(
|
||||
uid: uid != nil ? "\(uid!)" : nil,
|
||||
jti: nil,
|
||||
tokenType: "bearer",
|
||||
refreshToken: nil,
|
||||
netEaseToken: nil,
|
||||
accessToken: accessToken,
|
||||
expiresIn: nil,
|
||||
scope: nil,
|
||||
ticket: ticket
|
||||
)
|
||||
|
||||
if let uid = uid {
|
||||
saveUserId("\(uid)") // 转换为字符串保存
|
||||
}
|
||||
saveAccountModel(accountModel)
|
||||
saveTicket(ticket)
|
||||
|
||||
if let userInfo = userInfo {
|
||||
saveUserInfo(userInfo)
|
||||
@@ -347,12 +347,9 @@ struct UserInfoManager {
|
||||
|
||||
/// 清除所有认证信息
|
||||
static func clearAllAuthenticationData() {
|
||||
userDefaults.removeObject(forKey: StorageKeys.userId)
|
||||
userDefaults.removeObject(forKey: StorageKeys.accessToken)
|
||||
userDefaults.removeObject(forKey: StorageKeys.userInfo)
|
||||
clearAccountModel() // 新增:清除 AccountModel
|
||||
clearAccountModel()
|
||||
clearUserInfo()
|
||||
clearTicket()
|
||||
userDefaults.synchronize()
|
||||
|
||||
print("🗑️ 清除所有认证信息")
|
||||
}
|
||||
@@ -375,40 +372,41 @@ struct UserInfoManager {
|
||||
/// 保存 AccountModel
|
||||
/// - Parameter accountModel: 要保存的账户模型
|
||||
static func saveAccountModel(_ accountModel: AccountModel) {
|
||||
do {
|
||||
let data = try JSONEncoder().encode(accountModel)
|
||||
userDefaults.set(data, forKey: StorageKeys.accountModel)
|
||||
userDefaults.synchronize()
|
||||
|
||||
// 同时更新各个独立字段(向后兼容)
|
||||
if let uid = accountModel.uid {
|
||||
saveUserId(uid)
|
||||
cacheQueue.async(flags: .barrier) {
|
||||
do {
|
||||
try keychain.store(accountModel, forKey: StorageKeys.accountModel)
|
||||
accountModelCache = accountModel
|
||||
|
||||
// 同步更新 ticket 到内存
|
||||
if let ticket = accountModel.ticket {
|
||||
saveTicket(ticket)
|
||||
}
|
||||
|
||||
print("💾 AccountModel 保存成功")
|
||||
} catch {
|
||||
print("❌ AccountModel 保存失败: \(error)")
|
||||
}
|
||||
if let accessToken = accountModel.accessToken {
|
||||
saveAccessToken(accessToken)
|
||||
}
|
||||
if let ticket = accountModel.ticket {
|
||||
saveTicket(ticket)
|
||||
}
|
||||
|
||||
print("💾 AccountModel 保存成功")
|
||||
} catch {
|
||||
print("❌ AccountModel 保存失败: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取 AccountModel
|
||||
/// - Returns: 存储的账户模型,如果不存在或解析失败返回 nil
|
||||
static func getAccountModel() -> AccountModel? {
|
||||
guard let data = userDefaults.data(forKey: StorageKeys.accountModel) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
return try JSONDecoder().decode(AccountModel.self, from: data)
|
||||
} catch {
|
||||
print("❌ AccountModel 解析失败: \(error)")
|
||||
return nil
|
||||
return cacheQueue.sync {
|
||||
// 先检查缓存
|
||||
if let cached = accountModelCache {
|
||||
return cached
|
||||
}
|
||||
|
||||
// 从 Keychain 读取
|
||||
do {
|
||||
let accountModel = try keychain.retrieve(AccountModel.self, forKey: StorageKeys.accountModel)
|
||||
accountModelCache = accountModel
|
||||
return accountModel
|
||||
} catch {
|
||||
print("❌ 读取 AccountModel 失败: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +418,18 @@ struct UserInfoManager {
|
||||
return
|
||||
}
|
||||
|
||||
accountModel.ticket = ticket
|
||||
accountModel = AccountModel(
|
||||
uid: accountModel.uid,
|
||||
jti: accountModel.jti,
|
||||
tokenType: accountModel.tokenType,
|
||||
refreshToken: accountModel.refreshToken,
|
||||
netEaseToken: accountModel.netEaseToken,
|
||||
accessToken: accountModel.accessToken,
|
||||
expiresIn: accountModel.expiresIn,
|
||||
scope: accountModel.scope,
|
||||
ticket: ticket
|
||||
)
|
||||
|
||||
saveAccountModel(accountModel)
|
||||
saveTicket(ticket) // 同时更新内存中的 ticket
|
||||
}
|
||||
@@ -436,9 +445,105 @@ struct UserInfoManager {
|
||||
|
||||
/// 清除 AccountModel
|
||||
static func clearAccountModel() {
|
||||
userDefaults.removeObject(forKey: StorageKeys.accountModel)
|
||||
userDefaults.synchronize()
|
||||
print("🗑️ AccountModel 已清除")
|
||||
cacheQueue.async(flags: .barrier) {
|
||||
do {
|
||||
try keychain.delete(forKey: StorageKeys.accountModel)
|
||||
accountModelCache = nil
|
||||
print("🗑️ AccountModel 已清除")
|
||||
} catch {
|
||||
print("❌ 清除 AccountModel 失败: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除用户信息
|
||||
static func clearUserInfo() {
|
||||
cacheQueue.async(flags: .barrier) {
|
||||
do {
|
||||
try keychain.delete(forKey: StorageKeys.userInfo)
|
||||
userInfoCache = nil
|
||||
print("🗑️ UserInfo 已清除")
|
||||
} catch {
|
||||
print("❌ 清除 UserInfo 失败: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除所有缓存(用于测试或重置)
|
||||
static func clearAllCache() {
|
||||
cacheQueue.async(flags: .barrier) {
|
||||
accountModelCache = nil
|
||||
userInfoCache = nil
|
||||
print("🗑️ 清除所有内存缓存")
|
||||
}
|
||||
}
|
||||
|
||||
/// 预加载缓存(提升首次访问性能)
|
||||
static func preloadCache() {
|
||||
cacheQueue.async {
|
||||
// 预加载 AccountModel
|
||||
_ = getAccountModel()
|
||||
// 预加载 UserInfo
|
||||
_ = getUserInfo()
|
||||
print("🚀 缓存预加载完成")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Authentication Validation
|
||||
|
||||
/// 检查当前认证状态是否有效
|
||||
/// - Returns: 认证状态结果
|
||||
static func checkAuthenticationStatus() -> AuthenticationStatus {
|
||||
return cacheQueue.sync {
|
||||
guard let accountModel = getAccountModel() else {
|
||||
print("🔍 认证检查:未找到 AccountModel")
|
||||
return .notFound
|
||||
}
|
||||
|
||||
// 检查 uid 是否有效
|
||||
guard let uid = accountModel.uid, !uid.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
print("🔍 认证检查:uid 无效")
|
||||
return .invalid
|
||||
}
|
||||
|
||||
// 检查 ticket 是否有效
|
||||
guard let ticket = accountModel.ticket, !ticket.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
print("🔍 认证检查:ticket 无效")
|
||||
return .invalid
|
||||
}
|
||||
|
||||
// 可选:检查 access token 是否有效
|
||||
guard let accessToken = accountModel.accessToken, !accessToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
print("🔍 认证检查:access token 无效")
|
||||
return .invalid
|
||||
}
|
||||
|
||||
print("🔍 认证检查:认证有效 - uid: \(uid), ticket: \(ticket.prefix(10))...")
|
||||
return .valid
|
||||
}
|
||||
}
|
||||
|
||||
/// 认证状态枚举
|
||||
enum AuthenticationStatus: Equatable {
|
||||
case valid // 认证有效,可以自动登录
|
||||
case invalid // 认证信息不完整或无效
|
||||
case notFound // 未找到认证信息
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .valid:
|
||||
return "认证有效"
|
||||
case .invalid:
|
||||
return "认证无效"
|
||||
case .notFound:
|
||||
return "未找到认证信息"
|
||||
}
|
||||
}
|
||||
|
||||
/// 是否可以自动登录
|
||||
var canAutoLogin: Bool {
|
||||
return self == .valid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,6 +580,12 @@ protocol APIRequestProtocol {
|
||||
var customHeaders: [String: String]? { get } // 新增:自定义请求头
|
||||
var timeout: TimeInterval { get }
|
||||
var includeBaseParameters: Bool { get }
|
||||
|
||||
// MARK: - Loading Configuration
|
||||
/// 是否显示 loading 动画,默认 true
|
||||
var shouldShowLoading: Bool { get }
|
||||
/// 是否显示错误信息,默认 true
|
||||
var shouldShowError: Bool { get }
|
||||
}
|
||||
|
||||
extension APIRequestProtocol {
|
||||
@@ -482,6 +593,10 @@ extension APIRequestProtocol {
|
||||
var includeBaseParameters: Bool { true }
|
||||
var headers: [String: String]? { nil }
|
||||
var customHeaders: [String: String]? { nil } // 新增:默认实现
|
||||
|
||||
// MARK: - Loading Configuration Defaults
|
||||
var shouldShowLoading: Bool { true }
|
||||
var shouldShowError: Bool { true }
|
||||
}
|
||||
|
||||
// MARK: - Generic API Response
|
||||
|
Reference in New Issue
Block a user