17 changed files with 848 additions and 162 deletions
After Width: | Height: | Size: 1.3 MiB |
@ -1,5 +1,48 @@ |
|||||
|
export const AUTH_CONSTANTS = { |
||||
|
// 存储键名
|
||||
|
STORAGE_KEYS: { |
||||
|
ACCESS_TOKEN: 'accessToken', |
||||
|
REFRESH_TOKEN: 'refreshToken', |
||||
|
TOKEN_EXPIRY: 'tokenExpiry', |
||||
|
REMEMBER_ME: 'rememberMe', |
||||
|
LOGIN_ATTEMPTS: 'loginAttempts', |
||||
|
LAST_LOGIN_ATTEMPT: 'lastLoginAttempt' |
||||
|
}, |
||||
|
|
||||
|
// 认证配置
|
||||
|
AUTH_CONFIG: { |
||||
|
MAX_LOGIN_ATTEMPTS: 5, |
||||
|
LOGIN_ATTEMPTS_RESET_TIME: 30 * 60 * 1000, // 30分钟
|
||||
|
TOKEN_EXPIRY_TIME: 24 * 60 * 60 * 1000, // 24小时
|
||||
|
TOKEN_REFRESH_THRESHOLD: 5 * 60 * 1000, // 5分钟
|
||||
|
}, |
||||
|
|
||||
|
// 默认权限
|
||||
|
DEFAULT_PERMISSIONS: { |
||||
|
VIEW_DASHBOARD: true, |
||||
|
MANAGE_USERS: false, |
||||
|
MANAGE_ROLES: false, |
||||
|
MANAGE_PERMISSIONS: false |
||||
|
}, |
||||
|
|
||||
|
MESSAGES: { |
||||
|
LOGIN_SUCCESS: '登录成功', |
||||
|
LOGIN_FAILED: '登录失败,请检查用户名和密码', |
||||
|
LOGOUT_SUCCESS: '已成功退出登录', |
||||
|
LOGOUT_FAILED: '退出登录失败', |
||||
|
TOKEN_REFRESHED: '令牌已刷新', |
||||
|
TOKEN_REFRESH_FAILED: '令牌刷新失败,请重新登录', |
||||
|
USER_FETCHED: '已获取用户信息', |
||||
|
USER_FETCH_FAILED: '获取用户信息失败', |
||||
|
INVALID_CREDENTIALS: '用户名或密码错误', |
||||
|
ACCOUNT_LOCKED: '账户已被锁定,请稍后再试', |
||||
|
TOKEN_EXPIRED: '登录已过期,请重新登录', |
||||
|
NETWORK_ERROR: '网络错误,请检查网络连接', |
||||
|
UNKNOWN_ERROR: '发生未知错误,请稍后重试' |
||||
|
} |
||||
|
} as const; |
||||
|
|
||||
export const DEFAULT_CREDENTIALS = { |
export const DEFAULT_CREDENTIALS = { |
||||
username: 'zhangsan', |
username: 'zhangsan', |
||||
email: 'zhangsan@example.com', |
|
||||
password: 'P@ssw0rd!' |
password: 'P@ssw0rd!' |
||||
}; |
}; |
@ -0,0 +1,20 @@ |
|||||
|
import { useEffect } from 'react'; |
||||
|
import { Dispatch } from 'react'; |
||||
|
import { AuthAction } from '@/types/auth'; |
||||
|
import { authService } from '@/services/authService'; |
||||
|
|
||||
|
export function useAuthInit(dispatch: Dispatch<AuthAction>) { |
||||
|
useEffect(() => { |
||||
|
console.log('[useAuthInit] 开始初始化认证状态'); |
||||
|
const initAuth = async () => { |
||||
|
try { |
||||
|
console.log('[useAuthInit] 调用 initializeAuth'); |
||||
|
await authService.initializeAuth(dispatch); |
||||
|
console.log('[useAuthInit] initializeAuth 完成'); |
||||
|
} catch (error) { |
||||
|
console.error('[useAuthInit] 初始化失败', error); |
||||
|
} |
||||
|
}; |
||||
|
initAuth(); |
||||
|
}, [dispatch]); |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
import { useEffect } from 'react'; |
||||
|
import { User } from '@/types/auth'; |
||||
|
|
||||
|
export function useAuthSync(user: User | null, setGlobalUser: (user: User | null) => void) { |
||||
|
useEffect(() => { |
||||
|
setGlobalUser(user); |
||||
|
}, [user, setGlobalUser]); |
||||
|
} |
@ -0,0 +1,82 @@ |
|||||
|
import { LoginRequest, LoginResponse, User, OperationResult } from '@/types/auth'; |
||||
|
import { httpClient } from '@/lib/http-client'; |
||||
|
import { AUTH_CONSTANTS } from '@/constants/auth'; |
||||
|
|
||||
|
export interface ApiService { |
||||
|
login: (request: LoginRequest) => Promise<OperationResult<LoginResponse>>; |
||||
|
refreshToken: (refreshToken: string) => Promise<OperationResult<LoginResponse>>; |
||||
|
logout: () => Promise<OperationResult<void>>; |
||||
|
getCurrentUser: () => Promise<OperationResult<User>>; |
||||
|
} |
||||
|
|
||||
|
class ApiError extends Error { |
||||
|
constructor(message: string, public statusCode?: number) { |
||||
|
super(message); |
||||
|
this.name = 'ApiError'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const apiService: ApiService = { |
||||
|
login: async (request: LoginRequest): Promise<OperationResult<LoginResponse>> => { |
||||
|
try { |
||||
|
const response = await httpClient.post<LoginResponse>('/Auth/Login', request); |
||||
|
return { |
||||
|
success: true, |
||||
|
data: response.data, |
||||
|
message: AUTH_CONSTANTS.MESSAGES.LOGIN_SUCCESS |
||||
|
}; |
||||
|
} catch (error: any) { |
||||
|
return { |
||||
|
success: false, |
||||
|
message: error.response?.data?.message || AUTH_CONSTANTS.MESSAGES.LOGIN_FAILED |
||||
|
}; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
refreshToken: async (refreshToken: string): Promise<OperationResult<LoginResponse>> => { |
||||
|
try { |
||||
|
const response = await httpClient.post<LoginResponse>('/Auth/RefreshToken', { refreshToken }); |
||||
|
return { |
||||
|
success: true, |
||||
|
data: response.data, |
||||
|
message: AUTH_CONSTANTS.MESSAGES.TOKEN_REFRESHED |
||||
|
}; |
||||
|
} catch (error: any) { |
||||
|
return { |
||||
|
success: false, |
||||
|
message: error.response?.data?.message || AUTH_CONSTANTS.MESSAGES.TOKEN_REFRESH_FAILED |
||||
|
}; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
logout: async (): Promise<OperationResult<void>> => { |
||||
|
try { |
||||
|
await httpClient.post('/Auth/Logout'); |
||||
|
return { |
||||
|
success: true, |
||||
|
message: AUTH_CONSTANTS.MESSAGES.LOGOUT_SUCCESS |
||||
|
}; |
||||
|
} catch (error: any) { |
||||
|
return { |
||||
|
success: false, |
||||
|
message: error.response?.data?.message || AUTH_CONSTANTS.MESSAGES.LOGOUT_FAILED |
||||
|
}; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getCurrentUser: async (): Promise<OperationResult<User>> => { |
||||
|
try { |
||||
|
const response = await httpClient.get<User>('/Users/CurrentUser'); |
||||
|
return { |
||||
|
success: true, |
||||
|
data: response.data, |
||||
|
message: AUTH_CONSTANTS.MESSAGES.USER_FETCHED |
||||
|
}; |
||||
|
} catch (error: any) { |
||||
|
return { |
||||
|
success: false, |
||||
|
message: error.response?.data?.message || AUTH_CONSTANTS.MESSAGES.USER_FETCH_FAILED |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
}; |
@ -1,55 +1,207 @@ |
|||||
import { LoginRequest, LoginResponse, OperationResult } from '@/types/auth'; |
import { LoginRequest, User, OperationResult, AuthAction } from '@/types/auth'; |
||||
import { httpClient } from '@/lib/http-client'; |
import { storageService } from './storageService'; |
||||
|
import { apiService } from '@/services/apiService'; |
||||
|
import { AUTH_CONSTANTS } from '@/constants/auth'; |
||||
|
|
||||
export const authService = { |
export interface AuthService { |
||||
async login(request: LoginRequest): Promise<OperationResult<LoginResponse>> { |
isTokenExpired: () => boolean; |
||||
try { |
checkLoginAttempts: () => boolean; |
||||
const response = await httpClient.post<LoginResponse>('/Auth/Login', request); |
updateLoginAttempts: (success: boolean) => void; |
||||
return response; |
handleLogin: (request: LoginRequest, dispatch: React.Dispatch<AuthAction>) => Promise<void>; |
||||
} catch (error: any) { |
handleLogout: (dispatch: React.Dispatch<AuthAction>) => Promise<void>; |
||||
return { |
handleRefreshToken: (dispatch: React.Dispatch<AuthAction>) => Promise<void>; |
||||
successMessage: null, |
initializeAuth: (dispatch: React.Dispatch<AuthAction>) => Promise<void>; |
||||
errorMessages: error.errorMessages || ['登录请求失败,请稍后重试'], |
} |
||||
data: null, |
|
||||
isSuccess: false, |
class AuthError extends Error { |
||||
}; |
constructor(message: string) { |
||||
|
super(message); |
||||
|
this.name = 'AuthError'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const authService: AuthService = { |
||||
|
isTokenExpired: () => { |
||||
|
const expiryTime = storageService.getTokenExpiry(); |
||||
|
if (!expiryTime) return true; |
||||
|
return Date.now() > parseInt(expiryTime); |
||||
|
}, |
||||
|
|
||||
|
checkLoginAttempts: () => { |
||||
|
const attempts = storageService.getLoginAttempts(); |
||||
|
const lastAttempt = storageService.getLastLoginAttempt(); |
||||
|
const now = Date.now(); |
||||
|
|
||||
|
if (attempts >= AUTH_CONSTANTS.AUTH_CONFIG.MAX_LOGIN_ATTEMPTS) { |
||||
|
if (now - lastAttempt < AUTH_CONSTANTS.AUTH_CONFIG.LOGIN_ATTEMPTS_RESET_TIME) { |
||||
|
throw new AuthError(AUTH_CONSTANTS.MESSAGES.ACCOUNT_LOCKED); |
||||
|
} |
||||
|
storageService.removeLoginAttempts(); |
||||
|
storageService.removeLastLoginAttempt(); |
||||
|
} |
||||
|
return true; |
||||
|
}, |
||||
|
|
||||
|
updateLoginAttempts: (success: boolean) => { |
||||
|
if (success) { |
||||
|
storageService.removeLoginAttempts(); |
||||
|
storageService.removeLastLoginAttempt(); |
||||
|
} else { |
||||
|
const attempts = storageService.getLoginAttempts() + 1; |
||||
|
storageService.setLoginAttempts(attempts); |
||||
|
storageService.setLastLoginAttempt(Date.now()); |
||||
} |
} |
||||
}, |
}, |
||||
|
|
||||
// 刷新token
|
handleLogin: async (request: LoginRequest, dispatch: React.Dispatch<AuthAction>) => { |
||||
async refreshToken(refreshToken: string): Promise<OperationResult<LoginResponse>> { |
|
||||
try { |
try { |
||||
const response = await httpClient.post<LoginResponse>('/Auth/RefreshToken', { |
console.log('[authService] handleLogin start', request); |
||||
refreshToken, |
dispatch({ type: 'LOGIN_START' }); |
||||
|
const result = await apiService.login(request); |
||||
|
console.log('[authService] login result', result); |
||||
|
if (!result.success) { |
||||
|
throw new AuthError(result.message || AUTH_CONSTANTS.MESSAGES.LOGIN_FAILED); |
||||
|
} |
||||
|
const { accessToken, refreshToken, user } = result.data!; |
||||
|
const expiryTime = Date.now() + AUTH_CONSTANTS.AUTH_CONFIG.TOKEN_EXPIRY_TIME; |
||||
|
storageService.setAccessToken(accessToken); |
||||
|
storageService.setRefreshToken(refreshToken); |
||||
|
storageService.setTokenExpiry(expiryTime); |
||||
|
storageService.setRememberMe(request.rememberMe); |
||||
|
console.log('[authService] tokens saved', { accessToken, refreshToken, expiryTime }); |
||||
|
dispatch({ |
||||
|
type: 'LOGIN_SUCCESS', |
||||
|
payload: { user, accessToken, refreshToken, rememberMe: request.rememberMe } |
||||
}); |
}); |
||||
return response; |
|
||||
} catch (error: any) { |
} catch (error: any) { |
||||
return { |
console.error('[authService] handleLogin error', error); |
||||
successMessage: null, |
dispatch({ |
||||
errorMessages: error.errorMessages || ['刷新令牌失败,请重新登录'], |
type: 'LOGIN_FAILURE', |
||||
data: null, |
payload: { error: error.message || AUTH_CONSTANTS.MESSAGES.UNKNOWN_ERROR } |
||||
isSuccess: false, |
}); |
||||
}; |
throw error; |
||||
} |
} |
||||
}, |
}, |
||||
|
|
||||
// 登出
|
handleLogout: async (dispatch: React.Dispatch<AuthAction>) => { |
||||
async logout(): Promise<void> { |
try { |
||||
await httpClient.post('/Auth/Logout'); |
console.log('[authService] handleLogout start'); |
||||
|
const result = await apiService.logout(); |
||||
|
console.log('[authService] logout result', result); |
||||
|
if (!result.success) { |
||||
|
throw new AuthError(result.message || AUTH_CONSTANTS.MESSAGES.LOGOUT_FAILED); |
||||
|
} |
||||
|
storageService.clearAuth(); |
||||
|
console.log('[authService] tokens cleared'); |
||||
|
dispatch({ type: 'LOGOUT' }); |
||||
|
} catch (error: any) { |
||||
|
console.error('[authService] handleLogout error', error); |
||||
|
dispatch({ |
||||
|
type: 'LOGIN_FAILURE', |
||||
|
payload: { error: error.message || AUTH_CONSTANTS.MESSAGES.UNKNOWN_ERROR } |
||||
|
}); |
||||
|
throw error; |
||||
|
} |
||||
}, |
}, |
||||
|
|
||||
// 获取当前用户信息
|
handleRefreshToken: async (dispatch: React.Dispatch<AuthAction>) => { |
||||
async getCurrentUser(): Promise<OperationResult<LoginResponse>> { |
|
||||
try { |
try { |
||||
const response = await httpClient.get<LoginResponse>('/Users/CurrentUser'); |
const refreshToken = storageService.getRefreshToken(); |
||||
return response; |
console.log('[authService] handleRefreshToken start', { refreshToken }); |
||||
|
if (!refreshToken) { |
||||
|
throw new AuthError(AUTH_CONSTANTS.MESSAGES.TOKEN_EXPIRED); |
||||
|
} |
||||
|
const result = await apiService.refreshToken(refreshToken); |
||||
|
console.log('[authService] refreshToken result', result); |
||||
|
if (!result.success || !result.data) { |
||||
|
throw new AuthError(result.message || AUTH_CONSTANTS.MESSAGES.TOKEN_REFRESH_FAILED); |
||||
|
} |
||||
|
const { accessToken, refreshToken: newRefreshToken, user } = result.data; |
||||
|
const expiryTime = Date.now() + AUTH_CONSTANTS.AUTH_CONFIG.TOKEN_EXPIRY_TIME; |
||||
|
storageService.setAccessToken(accessToken); |
||||
|
storageService.setRefreshToken(newRefreshToken); |
||||
|
storageService.setTokenExpiry(expiryTime); |
||||
|
console.log('[authService] tokens refreshed', { accessToken, newRefreshToken, expiryTime }); |
||||
|
dispatch({ |
||||
|
type: 'SET_USER', |
||||
|
payload: { user, accessToken, refreshToken: newRefreshToken } |
||||
|
}); |
||||
} catch (error: any) { |
} catch (error: any) { |
||||
return { |
console.error('[authService] handleRefreshToken error', error); |
||||
successMessage: null, |
dispatch({ |
||||
errorMessages: error.errorMessages || ['获取用户信息失败'], |
type: 'LOGIN_FAILURE', |
||||
data: null, |
payload: { error: error.message || AUTH_CONSTANTS.MESSAGES.UNKNOWN_ERROR } |
||||
isSuccess: false, |
}); |
||||
}; |
throw error; |
||||
} |
} |
||||
}, |
}, |
||||
|
|
||||
|
initializeAuth: async (dispatch: React.Dispatch<AuthAction>) => { |
||||
|
try { |
||||
|
console.log('[authService] 开始初始化认证'); |
||||
|
dispatch({ type: 'LOGIN_START' }); |
||||
|
|
||||
|
const accessToken = storageService.getAccessToken(); |
||||
|
const refreshToken = storageService.getRefreshToken(); |
||||
|
const expiry = storageService.getTokenExpiry(); |
||||
|
console.log('[authService] initializeAuth - 初始状态', { |
||||
|
accessToken: accessToken ? '存在' : '不存在', |
||||
|
refreshToken: refreshToken ? '存在' : '不存在', |
||||
|
expiry, |
||||
|
currentTime: Date.now(), |
||||
|
isExpired: authService.isTokenExpired() |
||||
|
}); |
||||
|
|
||||
|
if (!accessToken || authService.isTokenExpired()) { |
||||
|
console.log('[authService] token 缺失或过期,尝试刷新'); |
||||
|
if (!refreshToken) { |
||||
|
console.log('[authService] 没有刷新令牌,需要重新登录'); |
||||
|
dispatch({ |
||||
|
type: 'LOGIN_FAILURE', |
||||
|
payload: { error: AUTH_CONSTANTS.MESSAGES.TOKEN_EXPIRED } |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
await authService.handleRefreshToken(dispatch); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
console.log('[authService] 开始获取当前用户信息'); |
||||
|
const result = await apiService.getCurrentUser(); |
||||
|
console.log('[authService] getCurrentUser 结果', { |
||||
|
success: result.success, |
||||
|
message: result.message, |
||||
|
hasData: !!result.data, |
||||
|
data: result.data |
||||
|
}); |
||||
|
|
||||
|
if (!result.success) { |
||||
|
console.error('[authService] 获取用户信息失败', result.message); |
||||
|
throw new AuthError(result.message || AUTH_CONSTANTS.MESSAGES.USER_FETCH_FAILED); |
||||
|
} |
||||
|
|
||||
|
if (!result.data) { |
||||
|
console.error('[authService] 用户数据为空'); |
||||
|
throw new AuthError(AUTH_CONSTANTS.MESSAGES.USER_FETCH_FAILED); |
||||
|
} |
||||
|
|
||||
|
console.log('[authService] 设置用户信息到状态'); |
||||
|
dispatch({ |
||||
|
type: 'LOGIN_SUCCESS', |
||||
|
payload: { |
||||
|
user: result.data, |
||||
|
accessToken, |
||||
|
refreshToken: storageService.getRefreshToken() || '', |
||||
|
rememberMe: storageService.getRememberMe() |
||||
|
} |
||||
|
}); |
||||
|
} catch (error: any) { |
||||
|
console.error('[authService] initializeAuth 错误', error); |
||||
|
dispatch({ |
||||
|
type: 'LOGIN_FAILURE', |
||||
|
payload: { error: error.message || AUTH_CONSTANTS.MESSAGES.UNKNOWN_ERROR } |
||||
|
}); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
}; |
}; |
@ -0,0 +1,88 @@ |
|||||
|
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; |
||||
|
import { authService } from './authService'; |
||||
|
|
||||
|
const TOKEN_EXPIRY_KEY = 'tokenExpiry'; |
||||
|
const TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000; // 5分钟
|
||||
|
|
||||
|
class AxiosConfig { |
||||
|
private instance: AxiosInstance; |
||||
|
|
||||
|
constructor() { |
||||
|
this.instance = axios.create({ |
||||
|
baseURL: process.env.REACT_APP_API_URL, |
||||
|
timeout: 10000, |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
this.setupInterceptors(); |
||||
|
} |
||||
|
|
||||
|
private setupInterceptors() { |
||||
|
// 请求拦截器
|
||||
|
this.instance.interceptors.request.use( |
||||
|
async (config: InternalAxiosRequestConfig) => { |
||||
|
const token = localStorage.getItem('accessToken'); |
||||
|
if (token) { |
||||
|
// 检查token是否即将过期
|
||||
|
const expiry = localStorage.getItem(TOKEN_EXPIRY_KEY); |
||||
|
if (expiry) { |
||||
|
const expiryTime = parseInt(expiry); |
||||
|
const currentTime = new Date().getTime(); |
||||
|
if (expiryTime - currentTime < TOKEN_REFRESH_THRESHOLD) { |
||||
|
try { |
||||
|
await authService.refreshToken(localStorage.getItem('refreshToken') || ''); |
||||
|
} catch (error) { |
||||
|
// 刷新失败,清除token
|
||||
|
localStorage.removeItem('accessToken'); |
||||
|
localStorage.removeItem('refreshToken'); |
||||
|
localStorage.removeItem(TOKEN_EXPIRY_KEY); |
||||
|
window.location.href = '/login'; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
config.headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`; |
||||
|
} |
||||
|
return config; |
||||
|
}, |
||||
|
(error) => { |
||||
|
return Promise.reject(error); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 响应拦截器
|
||||
|
this.instance.interceptors.response.use( |
||||
|
(response: AxiosResponse) => { |
||||
|
return response; |
||||
|
}, |
||||
|
async (error) => { |
||||
|
if (error.response?.status === 401) { |
||||
|
// token过期,尝试刷新
|
||||
|
try { |
||||
|
const refreshToken = localStorage.getItem('refreshToken'); |
||||
|
if (refreshToken) { |
||||
|
await authService.refreshToken(refreshToken); |
||||
|
// 重试原请求
|
||||
|
const config = error.config; |
||||
|
return this.instance(config); |
||||
|
} |
||||
|
} catch (refreshError) { |
||||
|
// 刷新失败,清除token并跳转到登录页
|
||||
|
localStorage.removeItem('accessToken'); |
||||
|
localStorage.removeItem('refreshToken'); |
||||
|
localStorage.removeItem(TOKEN_EXPIRY_KEY); |
||||
|
window.location.href = '/login'; |
||||
|
} |
||||
|
} |
||||
|
return Promise.reject(error); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public getInstance(): AxiosInstance { |
||||
|
return this.instance; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const axiosInstance = new AxiosConfig().getInstance(); |
@ -0,0 +1,206 @@ |
|||||
|
import { AUTH_CONSTANTS } from '@/constants/auth'; |
||||
|
|
||||
|
export interface StorageService { |
||||
|
// Token 相关
|
||||
|
setAccessToken: (token: string) => void; |
||||
|
getAccessToken: () => string | null; |
||||
|
removeAccessToken: () => void; |
||||
|
|
||||
|
setRefreshToken: (token: string) => void; |
||||
|
getRefreshToken: () => string | null; |
||||
|
removeRefreshToken: () => void; |
||||
|
|
||||
|
// Token 过期时间
|
||||
|
setTokenExpiry: (expiryTime: number) => void; |
||||
|
getTokenExpiry: () => string | null; |
||||
|
removeTokenExpiry: () => void; |
||||
|
|
||||
|
// 记住登录状态
|
||||
|
setRememberMe: (value: boolean) => void; |
||||
|
getRememberMe: () => boolean; |
||||
|
removeRememberMe: () => void; |
||||
|
|
||||
|
// 登录尝试次数
|
||||
|
setLoginAttempts: (attempts: number) => void; |
||||
|
getLoginAttempts: () => number; |
||||
|
removeLoginAttempts: () => void; |
||||
|
|
||||
|
// 最后登录尝试时间
|
||||
|
setLastLoginAttempt: (timestamp: number) => void; |
||||
|
getLastLoginAttempt: () => number; |
||||
|
removeLastLoginAttempt: () => void; |
||||
|
|
||||
|
// 清除所有认证相关存储
|
||||
|
clearAuth: () => void; |
||||
|
} |
||||
|
|
||||
|
class StorageError extends Error { |
||||
|
constructor(message: string) { |
||||
|
super(message); |
||||
|
this.name = 'StorageError'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const storageService: StorageService = { |
||||
|
// Token 相关
|
||||
|
setAccessToken: (token: string) => { |
||||
|
try { |
||||
|
localStorage.setItem(AUTH_CONSTANTS.STORAGE_KEYS.ACCESS_TOKEN, token); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('设置访问令牌失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getAccessToken: () => { |
||||
|
try { |
||||
|
return localStorage.getItem(AUTH_CONSTANTS.STORAGE_KEYS.ACCESS_TOKEN); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('获取访问令牌失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
removeAccessToken: () => { |
||||
|
try { |
||||
|
localStorage.removeItem(AUTH_CONSTANTS.STORAGE_KEYS.ACCESS_TOKEN); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('移除访问令牌失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
setRefreshToken: (token: string) => { |
||||
|
try { |
||||
|
localStorage.setItem(AUTH_CONSTANTS.STORAGE_KEYS.REFRESH_TOKEN, token); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('设置刷新令牌失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getRefreshToken: () => { |
||||
|
try { |
||||
|
return localStorage.getItem(AUTH_CONSTANTS.STORAGE_KEYS.REFRESH_TOKEN); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('获取刷新令牌失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
removeRefreshToken: () => { |
||||
|
try { |
||||
|
localStorage.removeItem(AUTH_CONSTANTS.STORAGE_KEYS.REFRESH_TOKEN); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('移除刷新令牌失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Token 过期时间
|
||||
|
setTokenExpiry: (expiryTime: number) => { |
||||
|
try { |
||||
|
localStorage.setItem(AUTH_CONSTANTS.STORAGE_KEYS.TOKEN_EXPIRY, expiryTime.toString()); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('设置令牌过期时间失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getTokenExpiry: () => { |
||||
|
try { |
||||
|
return localStorage.getItem(AUTH_CONSTANTS.STORAGE_KEYS.TOKEN_EXPIRY); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('获取令牌过期时间失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
removeTokenExpiry: () => { |
||||
|
try { |
||||
|
localStorage.removeItem(AUTH_CONSTANTS.STORAGE_KEYS.TOKEN_EXPIRY); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('移除令牌过期时间失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 记住登录状态
|
||||
|
setRememberMe: (value: boolean) => { |
||||
|
try { |
||||
|
localStorage.setItem(AUTH_CONSTANTS.STORAGE_KEYS.REMEMBER_ME, value.toString()); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('设置记住登录状态失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getRememberMe: () => { |
||||
|
try { |
||||
|
return localStorage.getItem(AUTH_CONSTANTS.STORAGE_KEYS.REMEMBER_ME) === 'true'; |
||||
|
} catch (error) { |
||||
|
throw new StorageError('获取记住登录状态失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
removeRememberMe: () => { |
||||
|
try { |
||||
|
localStorage.removeItem(AUTH_CONSTANTS.STORAGE_KEYS.REMEMBER_ME); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('移除记住登录状态失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 登录尝试次数
|
||||
|
setLoginAttempts: (attempts: number) => { |
||||
|
try { |
||||
|
localStorage.setItem(AUTH_CONSTANTS.STORAGE_KEYS.LOGIN_ATTEMPTS, attempts.toString()); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('设置登录尝试次数失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getLoginAttempts: () => { |
||||
|
try { |
||||
|
const attempts = localStorage.getItem(AUTH_CONSTANTS.STORAGE_KEYS.LOGIN_ATTEMPTS); |
||||
|
return attempts ? parseInt(attempts) : 0; |
||||
|
} catch (error) { |
||||
|
throw new StorageError('获取登录尝试次数失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
removeLoginAttempts: () => { |
||||
|
try { |
||||
|
localStorage.removeItem(AUTH_CONSTANTS.STORAGE_KEYS.LOGIN_ATTEMPTS); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('移除登录尝试次数失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 最后登录尝试时间
|
||||
|
setLastLoginAttempt: (timestamp: number) => { |
||||
|
try { |
||||
|
localStorage.setItem(AUTH_CONSTANTS.STORAGE_KEYS.LAST_LOGIN_ATTEMPT, timestamp.toString()); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('设置最后登录尝试时间失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getLastLoginAttempt: () => { |
||||
|
try { |
||||
|
const timestamp = localStorage.getItem(AUTH_CONSTANTS.STORAGE_KEYS.LAST_LOGIN_ATTEMPT); |
||||
|
return timestamp ? parseInt(timestamp) : 0; |
||||
|
} catch (error) { |
||||
|
throw new StorageError('获取最后登录尝试时间失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
removeLastLoginAttempt: () => { |
||||
|
try { |
||||
|
localStorage.removeItem(AUTH_CONSTANTS.STORAGE_KEYS.LAST_LOGIN_ATTEMPT); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('移除最后登录尝试时间失败'); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 清除所有认证相关存储
|
||||
|
clearAuth: () => { |
||||
|
try { |
||||
|
Object.values(AUTH_CONSTANTS.STORAGE_KEYS).forEach(key => { |
||||
|
localStorage.removeItem(key); |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
throw new StorageError('清除认证存储失败'); |
||||
|
} |
||||
|
} |
||||
|
}; |
@ -0,0 +1,19 @@ |
|||||
|
interface User { |
||||
|
userId?: string; |
||||
|
userName?: string; |
||||
|
email?: string; |
||||
|
phoneNumber?: string; |
||||
|
} |
||||
|
|
||||
|
export function getUserInfo(user: User | { user?: User } | null) { |
||||
|
let realUser: User | undefined; |
||||
|
if (user && typeof user === 'object' && 'user' in user) { |
||||
|
realUser = user.user; |
||||
|
} else { |
||||
|
realUser = user as User | undefined; |
||||
|
} |
||||
|
return { |
||||
|
userName: realUser?.userName || '未登录', |
||||
|
email: realUser?.email || '' |
||||
|
}; |
||||
|
} |
Loading…
Reference in new issue