/** * User 状态机:Actors(异步服务) * * 从 `user-machine.ts` 抽出的"Actors"段: * - `userInitActor`(fromPromise:读 UserStorage 并行 user + avatarUrl) * - `userFetchActor`(fromPromise:调 API + 持久化到本地) * - `userLogoutActor`(fromPromise:auth logout + 清本地数据) * * 设计: * - 依赖 `user-machine.helpers.ts`(toView, readInitData, userStorage) * - 仓库以接口类型注入(调用面只看接口,运行时仍是同一单例) * - 命名约定:`[user-machine]` 前缀日志(如未来加)—— 与 chat 模块对齐 */ import { fromPromise } from "xstate"; import { userRepository } from "@/data/repositories/user_repository"; import { authRepository } from "@/data/repositories/auth_repository"; import type { IAuthRepository, IUserRepository, } from "@/data/repositories/interfaces"; import { Result } from "@/utils"; import { applyEntitlementSnapshotToView, type InitData, readInitData, toEntitlementSnapshot, toView, type UserFetchData, userStorage, } from "./user-machine.helpers"; // ============================================================ // 仓库注入 // ============================================================ // 仓库以接口类型注入:调用面只看接口,运行时仍是同一单例 const userRepo: IUserRepository = userRepository; const authRepo: IAuthRepository = authRepository; // ============================================================ // Init actor:从 UserStorage 读 user + avatarUrl // ============================================================ /** * 读本地 user + avatarUrl(并行)。 * - 失败/无数据 → 返回 null,不抛错(machine 走 onDone 回到 idle,context 是 null) * - onDone 赋 currentUser + avatarUrl 到 context */ export const userInitActor = fromPromise(async () => readInitData()); // ============================================================ // Fetch actor:调 API 拉当前用户 + 持久化 // ============================================================ /** * 调 `userRepo.getCurrentUser + getEntitlements` 拉当前 user 与权益: * - user 成功 → 转 UserView + 写本地(userStorage.setUser) * - entitlements 成功 → 提取 isVip/creditBalance 写本地轻量快照 * - 任一失败都不阻断另一方,machine 层负责保留已有 context */ export const userFetchActor = fromPromise(async () => { const [userResult, entitlementsResult] = await Promise.all([ userRepo.getCurrentUser(), userRepo.getEntitlements(), ]); const snapshot = Result.isOk(entitlementsResult) ? toEntitlementSnapshot({ isVip: entitlementsResult.data.isVip, creditBalance: entitlementsResult.data.creditBalance, }) : null; if (snapshot) { await userStorage.setEntitlementSnapshot(snapshot); } if (Result.isErr(userResult)) { return { user: null, snapshot }; } const view = applyEntitlementSnapshotToView( toView(userResult.data.toJson()), snapshot, ); // 持久化到本地 await userStorage.setUser(userResult.data.toJson()); return { user: view, snapshot }; }); // ============================================================ // Logout actor:后端登出 + 清本地用户数据 // ============================================================ /** * 登出: * - 调 `authRepo.logout` 让后端吊销 token * - `userStorage.clearUserData` 清本地 user / avatar 等 * - onDone / onError 都跑 `clearUser` action(machine 层)—— 失败也清本地 */ export const userLogoutActor = fromPromise(async () => { await authRepo.logout(); });