feat: 实现数据迁移和用户信息管理优化

- 在AppDelegate中集成数据迁移管理器,支持从UserDefaults迁移到Keychain。
- 重构UserInfoManager,使用Keychain存储用户信息,增加内存缓存以提升性能。
- 添加API加载效果视图,增强用户体验。
- 更新SplashFeature以支持自动登录和认证状态检查。
- 语言设置迁移至Keychain,确保用户设置的安全性。
This commit is contained in:
edwinQQQ
2025-07-10 17:20:20 +08:00
parent 6084ade9ea
commit 4a1b814902
15 changed files with 1773 additions and 354 deletions

View File

@@ -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