/** * 认证状态管理 * * 链路设计: * authService = 纯 HTTP 层(Token 管理),不碰 SQLite * authStore = 编排层,负责:API → DB初始化 → 写缓存 → 更新状态 * * 冷启动问题解法: * 将 userId 持久化到 AsyncStorage,这样 DB 可在 API 调用前提前初始化, * 彻底打破"需要用户ID才能初始化DB,但用户ID来自API"的循环依赖。 */ import AsyncStorage from '@react-native-async-storage/async-storage'; import { create } from 'zustand'; import { User } from '../types'; import { authService, resolveAuthApiError, LoginRequest, RegisterRequest } from '../services'; import { wsService } from '../services/wsService'; import { callStore } from './callStore'; import { initDatabase, closeDatabase, saveUserCache, saveCurrentUserCache, clearCurrentUserCache, } from '../services/database'; // AsyncStorage 中保存已登录用户 ID 的 key const USER_ID_KEY = 'auth_user_id'; // ── userId 持久化辅助函数 ── async function saveUserId(userId: string): Promise { try { await AsyncStorage.setItem(USER_ID_KEY, userId); } catch (err) { console.warn('[AuthStore] 保存 userId 失败:', err); } } async function loadUserId(): Promise { try { return await AsyncStorage.getItem(USER_ID_KEY); } catch { return null; } } async function clearUserId(): Promise { try { await AsyncStorage.removeItem(USER_ID_KEY); } catch {} } // ── 写用户缓存(DB 已就绪后调用)── async function cacheUser(user: User): Promise { try { await saveUserCache(user); await saveCurrentUserCache(user); } catch (err) { console.warn('[AuthStore] 写用户缓存失败:', err); } } // ── 将 API 错误转换为用户可读的中文提示 ── function resolveLoginError(error: any): string { const code: number = error?.code ?? 0; const msg: string = error?.message ?? ''; // 网络层错误(无法连接服务器) if ( msg.includes('Network request failed') || msg.includes('网络请求失败') || msg.includes('Failed to fetch') || code === 500 ) { return '无法连接到服务器,请检查网络连接后重试'; } // 账号或密码错误 if (code === 401 || msg.includes('密码') || msg.includes('用户名') || msg.includes('incorrect')) { return '用户名或密码错误,请重新输入'; } // 账号被禁用/封禁 if (code === 403 || msg.includes('禁止') || msg.includes('封禁') || msg.includes('forbidden')) { return '账号已被禁用,请联系管理员'; } // 服务端明确返回了 message,直接用 if (msg && msg !== '登录失败') { return msg; } return '登录失败,请稍后重试'; } // ── 启动 SSE 实时服务 ── async function startRealtime(): Promise { try { await wsService.start(); callStore.getState().initCall(); } catch (error) { console.error('[AuthStore] 启动 SSE 服务失败:', error); } } interface AuthState { isAuthenticated: boolean; currentUser: User | null; isLoading: boolean; error: string | null; login: (credentials: LoginRequest) => Promise; register: (data: RegisterRequest) => Promise; logout: () => Promise; updateUser: (user: Partial) => void; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; fetchCurrentUser: () => Promise; } export const useAuthStore = create((set) => ({ isAuthenticated: false, currentUser: null, isLoading: false, error: null, // ── 登录 ── // 流程:API → 保存Token → 初始化DB → 持久化userId → 写缓存 → 设置状态 login: async (credentials: LoginRequest) => { set({ isLoading: true, error: null }); try { // 1. API 登录,authService 只负责 HTTP + Token const response = await authService.login(credentials); const user = response.user; if (!user?.id) { throw new Error('登录响应中缺少用户信息'); } const userId = String(user.id); // 2. 初始化用户专属数据库 await initDatabase(userId); // 3. 持久化 userId(供下次冷启动提前初始化 DB 用) await saveUserId(userId); // 4. 写用户缓存(DB 已就绪) await cacheUser(user); set({ isAuthenticated: true, currentUser: user, isLoading: false, error: null, }); // 5. 启动 SSE await startRealtime(); return true; } catch (error: any) { set({ isLoading: false, error: resolveLoginError(error), }); return false; } }, // ── 注册 ── register: async (data: RegisterRequest) => { set({ isLoading: true, error: null }); try { const response = await authService.register(data); const user = response.user; if (!user?.id) { throw new Error('注册响应中缺少用户信息'); } const userId = String(user.id); await initDatabase(userId); await saveUserId(userId); await cacheUser(user); set({ isAuthenticated: true, currentUser: user, isLoading: false, error: null, }); await startRealtime(); return true; } catch (error: any) { set({ isLoading: false, error: resolveAuthApiError(error, '注册失败,请稍后重试'), }); return false; } }, // ── 登出 ── logout: async () => { set({ isLoading: true }); try { // 1. 通知服务端(Token 清理在 authService 内部完成) await authService.logout(); // 2. 停止 SSE wsService.stop(); // 3. 清除 DB 中的用户缓存(DB 此时一定已初始化) await clearCurrentUserCache().catch(() => {}); // 4. 关闭数据库连接 await closeDatabase(); // 5. 清除持久化的 userId await clearUserId(); } catch (error) { console.error('[AuthStore] 登出失败:', error); } finally { set({ isAuthenticated: false, currentUser: null, isLoading: false, error: null, }); } }, // ── 冷启动校验 ── // 流程:读取已保存的userId → 提前初始化DB → 纯API验证Token → 更新缓存 → 设置状态 fetchCurrentUser: async () => { set({ isLoading: true }); try { // 1. 从 AsyncStorage 读取上次登录保存的 userId // 这是关键:让 DB 在 API 调用前就能初始化,打破循环依赖 const savedUserId = await loadUserId(); if (savedUserId) { // 2. 用已知的 userId 提前初始化 DB try { await initDatabase(savedUserId); } catch (dbErr) { console.warn('[AuthStore] DB 预初始化失败:', dbErr); } } // 3. 纯 API 调用验证 Token 有效性(fetchCurrentUserFromAPI 完全不碰 DB) const user = await authService.fetchCurrentUserFromAPI(); if (user) { const userId = String(user.id); // 4. 若 userId 发生变化(极少数情况),重新初始化 DB 并更新持久化 if (!savedUserId || savedUserId !== userId) { await initDatabase(userId); await saveUserId(userId); } // 5. DB 已就绪,更新用户缓存 await cacheUser(user); set({ isAuthenticated: true, currentUser: user, isLoading: false, }); // 6. 启动 SSE await startRealtime(); } else { // Token 已失效或不存在 await clearUserId(); set({ isAuthenticated: false, currentUser: null, isLoading: false, }); } } catch (error) { console.error('[AuthStore] fetchCurrentUser 异常:', error); set({ isAuthenticated: false, currentUser: null, isLoading: false, error: '获取用户信息失败', }); } }, updateUser: (userData: Partial) => set((state) => ({ currentUser: state.currentUser ? { ...state.currentUser, ...userData } : null, })), setLoading: (loading: boolean) => set({ isLoading: loading }), setError: (error: string | null) => set({ error }), })); // 导出 selector hooks 以优化性能 export const useCurrentUser = () => useAuthStore((state) => state.currentUser); export const useIsAuthenticated = () => useAuthStore((state) => state.isAuthenticated); export const useAuthLoading = () => useAuthStore((state) => state.isLoading);