Files
frontend/src/stores/authStore.ts

310 lines
8.5 KiB
TypeScript
Raw Normal View History

/**
*
*
*
* 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 {
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<void> {
try {
await AsyncStorage.setItem(USER_ID_KEY, userId);
} catch (err) {
console.warn('[AuthStore] 保存 userId 失败:', err);
}
}
async function loadUserId(): Promise<string | null> {
try {
return await AsyncStorage.getItem(USER_ID_KEY);
} catch {
return null;
}
}
async function clearUserId(): Promise<void> {
try {
await AsyncStorage.removeItem(USER_ID_KEY);
} catch {}
}
// ── 写用户缓存DB 已就绪后调用)──
async function cacheUser(user: User): Promise<void> {
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<void> {
try {
await wsService.start();
} catch (error) {
console.error('[AuthStore] 启动 SSE 服务失败:', error);
}
}
interface AuthState {
isAuthenticated: boolean;
currentUser: User | null;
isLoading: boolean;
error: string | null;
login: (credentials: LoginRequest) => Promise<boolean>;
register: (data: RegisterRequest) => Promise<boolean>;
logout: () => Promise<void>;
updateUser: (user: Partial<User>) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
fetchCurrentUser: () => Promise<void>;
}
export const useAuthStore = create<AuthState>((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<User>) =>
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);