Paper_Trading / frontend /src /store /authStore.ts
superxuu
feat: implement user auth, daily limits, and built-in Vmq payment gateway
57864b1
/**
* 用户认证状态管理 Store
* 管理 token、用户信息、VIP 状态,持久化到 localStorage
*/
import { create } from 'zustand';
import { api } from '@/lib/api';
export interface AuthState {
// 状态
token: string | null;
userId: number | null;
username: string | null;
isVip: boolean;
vipExpireAt: string | null;
dailyUsed: number;
dailyRemaining: number | null; // null = VIP 无限制
dailyLimit: number | null;
isLoading: boolean;
isInitialized: boolean;
// Actions
login: (username: string, password: string) => Promise<void>;
register: (username: string, password: string) => Promise<void>;
logout: () => Promise<void>;
fetchUserInfo: () => Promise<void>;
fetchUsage: () => Promise<void>;
initialize: () => Promise<void>;
}
const TOKEN_KEY = 'auth_token';
export const useAuthStore = create<AuthState>((set, get) => ({
token: null,
userId: null,
username: null,
isVip: false,
vipExpireAt: null,
dailyUsed: 0,
dailyRemaining: null,
dailyLimit: null,
isLoading: false,
isInitialized: false,
initialize: async () => {
if (typeof window === 'undefined') {
set({ isInitialized: true });
return;
}
const savedToken = localStorage.getItem(TOKEN_KEY);
if (!savedToken) {
set({ isInitialized: true });
return;
}
set({ token: savedToken, isLoading: true });
try {
const userInfo = await api.me(savedToken);
set({
userId: userInfo.user_id,
username: userInfo.username,
isVip: userInfo.is_vip,
vipExpireAt: userInfo.vip_expire_at,
isInitialized: true,
isLoading: false,
});
// 同时获取今日使用量
try {
const usage = await api.getUsageToday(savedToken);
set({ dailyUsed: usage.used, dailyRemaining: usage.remaining, dailyLimit: usage.limit });
} catch { /* 静默失败 */ }
} catch {
// Token 过期或无效,清除
localStorage.removeItem(TOKEN_KEY);
set({
token: null,
userId: null,
username: null,
isVip: false,
vipExpireAt: null,
isInitialized: true,
isLoading: false,
});
}
},
login: async (username: string, password: string) => {
set({ isLoading: true });
try {
const res = await api.login(username, password);
localStorage.setItem(TOKEN_KEY, res.token);
set({
token: res.token,
userId: res.user_id,
username: res.username,
isLoading: false,
});
// Fetch VIP info and usage
try {
const vipInfo = await api.vipStatus(res.token);
set({ isVip: vipInfo.is_vip, vipExpireAt: vipInfo.vip_expire_at });
const usage = await api.getUsageToday(res.token);
set({ dailyUsed: usage.used, dailyRemaining: usage.remaining, dailyLimit: usage.limit });
} catch {
// VIP/usage 查询失败不影响登录
}
} catch (error) {
set({ isLoading: false });
throw error;
}
},
register: async (username: string, password: string) => {
set({ isLoading: true });
try {
const res = await api.register(username, password);
localStorage.setItem(TOKEN_KEY, res.token);
set({
token: res.token,
userId: res.user_id,
username: res.username,
isVip: false,
vipExpireAt: null,
isLoading: false,
});
} catch (error) {
set({ isLoading: false });
throw error;
}
},
logout: async () => {
const { token } = get();
if (token) {
try {
await api.logout(token);
} catch {
// 忽略登出接口错误
}
}
localStorage.removeItem(TOKEN_KEY);
set({
token: null,
userId: null,
username: null,
isVip: false,
vipExpireAt: null,
dailyUsed: 0,
dailyRemaining: null,
dailyLimit: null,
});
},
fetchUsage: async () => {
const { token } = get();
if (!token) return;
try {
const usage = await api.getUsageToday(token);
set({ dailyUsed: usage.used, dailyRemaining: usage.remaining, dailyLimit: usage.limit });
} catch { /* 静默失败 */ }
},
fetchUserInfo: async () => {
const { token } = get();
if (!token) return;
try {
const userInfo = await api.me(token);
set({
userId: userInfo.user_id,
username: userInfo.username,
isVip: userInfo.is_vip,
vipExpireAt: userInfo.vip_expire_at,
});
} catch {
// 静默失败
}
},
}));