// // SessionBootstrapper.swift // suixinkan // // Created by Codex on 2026/6/20. // import Foundation import Combine @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 ) ) } }