112 lines
4.0 KiB
Swift
112 lines
4.0 KiB
Swift
//
|
||
// SessionBootstrapper.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/20.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
@MainActor
|
||
/// 登录态启动恢复器,负责冷启动时从缓存恢复并校验 token。
|
||
final class SessionBootstrapper {
|
||
private let tokenStore: SessionTokenStore
|
||
private let snapshotStore: AccountSnapshotStore
|
||
private var didAttemptRestore = false
|
||
|
||
/// 初始化启动恢复器,并注入 token 与账号快照存储。
|
||
init(
|
||
tokenStore: SessionTokenStore,
|
||
snapshotStore: AccountSnapshotStore
|
||
) {
|
||
self.tokenStore = tokenStore
|
||
self.snapshotStore = snapshotStore
|
||
}
|
||
|
||
/// 创建使用默认存储服务的启动恢复器。
|
||
convenience init() {
|
||
self.init(
|
||
tokenStore: SessionTokenStore(),
|
||
snapshotStore: AccountSnapshotStore()
|
||
)
|
||
}
|
||
|
||
/// 从本地缓存恢复登录态并请求服务端校验。
|
||
func restore(
|
||
appSession: AppSession,
|
||
accountContext: AccountContext,
|
||
permissionContext: PermissionContext,
|
||
profileAPI: UserProfileServing,
|
||
accountContextAPI: AccountContextServing,
|
||
toastCenter: ToastCenter
|
||
) async {
|
||
guard !didAttemptRestore, !appSession.isLoggedIn else { return }
|
||
didAttemptRestore = true
|
||
|
||
guard let token = tokenStore.load() else {
|
||
appSession.logout()
|
||
return
|
||
}
|
||
|
||
appSession.beginRestoring(token: token)
|
||
let cachedSnapshot = restoreCachedSnapshot(to: accountContext)
|
||
|
||
do {
|
||
try await AccountContextLoader().refresh(
|
||
accountContext: accountContext,
|
||
permissionContext: permissionContext,
|
||
profileAPI: profileAPI,
|
||
accountContextAPI: accountContextAPI,
|
||
cachedCurrentRoleId: cachedSnapshot?.currentRoleId,
|
||
cachedCurrentScenicId: cachedSnapshot?.currentScenicId,
|
||
cachedCurrentStoreId: cachedSnapshot?.currentStoreId
|
||
)
|
||
saveLatestSnapshot(from: accountContext, permissionContext: permissionContext)
|
||
appSession.markLoggedIn(token: token)
|
||
} catch {
|
||
if APIError.isAuthenticationExpired(error) {
|
||
try? tokenStore.clear()
|
||
snapshotStore.clear()
|
||
accountContext.reset()
|
||
permissionContext.reset()
|
||
appSession.logout()
|
||
toastCenter.show("登录状态已失效,请重新登录")
|
||
} else {
|
||
appSession.markLoggedIn(token: token)
|
||
toastCenter.show("网络异常,已使用本地登录状态")
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 将缓存快照恢复到账号上下文。
|
||
private func restoreCachedSnapshot(to accountContext: AccountContext) -> AccountSnapshot? {
|
||
guard let snapshot = snapshotStore.load() else { return nil }
|
||
accountContext.applyLogin(profile: snapshot.profile)
|
||
accountContext.replaceScopes(
|
||
scenic: snapshot.scenicScopes,
|
||
stores: snapshot.storeScopes,
|
||
currentScenicId: snapshot.currentScenicId,
|
||
currentStoreId: snapshot.currentStoreId
|
||
)
|
||
return snapshot
|
||
}
|
||
|
||
/// 保存服务端校验后的最新账号上下文。
|
||
private func saveLatestSnapshot(from accountContext: AccountContext, permissionContext: PermissionContext) {
|
||
let existing = snapshotStore.load()
|
||
snapshotStore.save(
|
||
AccountSnapshot(
|
||
profile: accountContext.profile,
|
||
accountType: existing?.accountType,
|
||
businessUserId: existing?.businessUserId,
|
||
currentRoleId: permissionContext.currentRole?.id ?? existing?.currentRoleId,
|
||
scenicScopes: accountContext.scenicScopes,
|
||
storeScopes: accountContext.storeScopes,
|
||
currentScenicId: accountContext.currentScenic?.id,
|
||
currentStoreId: accountContext.currentStore?.id
|
||
)
|
||
)
|
||
}
|
||
|
||
}
|