Initial frontend repository commit.
Include app source and update .gitignore to exclude local release artifacts and signing files. Made-with: Cursor
This commit is contained in:
309
src/stores/authStore.ts
Normal file
309
src/stores/authStore.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* 认证状态管理
|
||||
*
|
||||
* 链路设计:
|
||||
* 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, websocketService, LoginRequest, RegisterRequest } from '../services';
|
||||
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 '登录失败,请稍后重试';
|
||||
}
|
||||
|
||||
// ── 启动 WebSocket 服务 ──
|
||||
async function startWebSocket(): Promise<void> {
|
||||
try {
|
||||
await websocketService.start();
|
||||
console.log('[AuthStore] WebSocket 服务启动成功');
|
||||
} catch (error) {
|
||||
console.error('[AuthStore] 启动 WebSocket 服务失败:', 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. 启动 WebSocket
|
||||
await startWebSocket();
|
||||
|
||||
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 startWebSocket();
|
||||
|
||||
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. 停止 WebSocket
|
||||
websocketService.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. 启动 WebSocket
|
||||
await startWebSocket();
|
||||
} 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);
|
||||
Reference in New Issue
Block a user