feat(工具函数): 新增核心工具函数模块

- 新增认证工具函数(auth.ts):Token管理、权限检查
- 新增 HTTP 请求封装(request.ts):Axios拦截器、响应处理
- 新增本地存储工具(storage.ts):统一的存储接口
This commit is contained in:
gaoziman 2025-11-18 20:45:19 +08:00
parent f312be1aa8
commit bc70cbf087
3 changed files with 716 additions and 0 deletions

216
src/utils/auth.ts Normal file
View File

@ -0,0 +1,216 @@
/**
*
* @description
*/
import { ROLE_ADMIN, UNAUTHORIZED_CODE } from '@/constants';
import type { LocalUserInfo } from '@/types/auth';
import { getToken, getUserInfo, getUserRole, clearAuthInfo } from './storage';
// ==================== 认证状态检查 ====================
/**
*
* @returns
*/
export function isAuthenticated(): boolean {
const token = getToken();
return !!token;
}
/**
*
* @returns
*/
export function requireAuth(): boolean {
return !isAuthenticated();
}
// ==================== 权限检查 ====================
/**
*
* @returns
*/
export function isAdmin(): boolean {
const userInfo = getUserInfo();
if (!userInfo) {
return false;
}
return userInfo.role === ROLE_ADMIN;
}
/**
*
* @param role -
* @returns
*/
export function hasRole(role: string): boolean {
const userInfo = getUserInfo();
if (!userInfo) {
return false;
}
return userInfo.role === role;
}
/**
*
* @param roles -
* @returns
*/
export function hasAnyRole(roles: string[]): boolean {
const userInfo = getUserInfo();
if (!userInfo) {
return false;
}
return roles.includes(userInfo.role);
}
/**
*
* @param roles -
* @returns
*/
export function hasAllRoles(roles: string[]): boolean {
const userInfo = getUserInfo();
if (!userInfo) {
return false;
}
// 注意:由于后端一个用户只有一个角色,这个函数实际上只在 roles 长度为 1 时返回 true
return roles.length === 1 && roles[0] === userInfo.role;
}
/**
* 访
* @param requiredRole -
* @returns
*/
export function hasPermission(requiredRole?: string): boolean {
// 如果未指定角色要求,只需要已登录即可
if (!requiredRole) {
return isAuthenticated();
}
// 检查是否具有指定角色
return hasRole(requiredRole);
}
// ==================== 获取认证信息 ====================
/**
*
* @returns null
*/
export function getCurrentUser(): LocalUserInfo | null {
return getUserInfo();
}
/**
*
* @returns null
*/
export function getCurrentUserRole(): string | null {
const userInfo = getUserInfo();
return userInfo ? userInfo.role : null;
}
/**
* ID
* @returns ID或 null
*/
export function getCurrentUserId(): number | null {
const userInfo = getUserInfo();
return userInfo ? userInfo.userId : null;
}
/**
*
* @returns null
*/
export function getCurrentUsername(): string | null {
const userInfo = getUserInfo();
return userInfo ? userInfo.username : null;
}
// ==================== 认证请求头 ====================
/**
*
* @returns
*/
export function getAuthHeaders(): Record<string, string> {
const token = getToken();
if (!token) {
return {};
}
return {
Authorization: token,
};
}
// ==================== 登出处理 ====================
/**
*
* @param redirectToLogin - true
*/
export function handleLogout(redirectToLogin = true): void {
// 清理所有认证信息
clearAuthInfo();
// 跳转到登录页
if (redirectToLogin) {
// 保存当前路径,登录后可以跳转回来
const currentPath = window.location.pathname + window.location.search;
if (currentPath !== '/login') {
sessionStorage.setItem('redirectPath', currentPath);
}
// 跳转到登录页
window.location.href = '/login';
}
}
/**
* 401
* @param message -
*/
export function handleUnauthorized(message?: string): void {
console.warn('Unauthorized access:', message || 'Token expired or invalid');
// 清理认证信息并跳转登录页
handleLogout(true);
}
/**
* 403
* @param message -
*/
export function handleForbidden(message?: string): void {
console.warn('Forbidden access:', message || 'Insufficient permissions');
// 可以跳转到无权限提示页,或者显示错误提示
// 这里简单处理,直接返回首页
window.location.href = '/';
}
// ==================== 登录后重定向 ====================
/**
*
* @returns
*/
export function getRedirectPath(): string {
const redirectPath = sessionStorage.getItem('redirectPath');
sessionStorage.removeItem('redirectPath');
return redirectPath || '/';
}
/**
*
*/
export function redirectAfterLogin(): void {
const redirectPath = getRedirectPath();
window.location.href = redirectPath;
}

242
src/utils/request.ts Normal file
View File

@ -0,0 +1,242 @@
/**
* Axios HTTP
* @description Axios
*/
import axios, {
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
AxiosError,
} from 'axios';
import { Message } from '@arco-design/web-react';
import {
SUCCESS_CODE,
UNAUTHORIZED_CODE,
FORBIDDEN_CODE,
NOT_FOUND_CODE,
SERVER_ERROR_CODE,
} from '@/constants';
import type { R } from '@/types/api';
import { getToken } from './storage';
import { handleUnauthorized } from './auth';
// ==================== 创建 Axios 实例 ====================
/**
* Axios
*/
const instance: AxiosInstance = axios.create({
// API 基础地址(从环境变量读取)
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080',
// 请求超时时间(从环境变量读取,默认 30 秒)
timeout: Number(import.meta.env.VITE_API_TIMEOUT) || 30000,
// 默认请求头
headers: {
'Content-Type': 'application/json',
},
});
// ==================== 请求拦截器 ====================
/**
*
*
*/
instance.interceptors.request.use(
(config) => {
// 获取 Token
const token = getToken();
// 如果存在 Token添加到请求头带 Bearer 前缀)
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = `Bearer ${token}`;
}
// 开发环境打印请求日志
if (import.meta.env.DEV) {
console.log(
`[Request] ${config.method?.toUpperCase()} ${config.url}`,
config.data || config.params
);
}
return config;
},
(error: AxiosError) => {
// 请求错误处理
console.error('[Request Error]', error);
Message.error('请求配置错误');
return Promise.reject(error);
}
);
// ==================== 响应拦截器 ====================
/**
*
*
*/
instance.interceptors.response.use(
(response: AxiosResponse<R>) => {
const { data } = response;
// 开发环境打印响应日志
if (import.meta.env.DEV) {
console.log(
`[Response] ${response.config.method?.toUpperCase()} ${
response.config.url
}`,
data
);
}
// 特殊处理如果是文件下载Blob直接返回完整响应
if (response.config.responseType === 'blob') {
return response;
}
// 判断业务响应码(使用宽松相等,兼容字符串和数字)
if (data.code == SUCCESS_CODE) {
// 业务成功,返回完整的响应数据(包含 code, msg, data
return response;
} else {
// 业务失败,显示错误消息
const errorMsg = data.msg || '请求失败';
Message.error(errorMsg);
// 抛出错误,方便调用方捕获
return Promise.reject(new Error(errorMsg));
}
},
(error: AxiosError<R>) => {
// HTTP 错误处理
if (error.response) {
const { status, data } = error.response;
// 开发环境打印错误日志
if (import.meta.env.DEV) {
console.error('[Response Error]', status, data);
}
// 根据 HTTP 状态码处理
switch (status) {
case UNAUTHORIZED_CODE:
// 401 未认证Token 过期或无效)
Message.error('登录已过期,请重新登录');
handleUnauthorized();
break;
case FORBIDDEN_CODE:
// 403 无权限
Message.error('没有权限访问该资源');
break;
case NOT_FOUND_CODE:
// 404 资源不存在
Message.error('请求的资源不存在');
break;
case SERVER_ERROR_CODE:
// 500 服务器错误
Message.error('服务器错误,请稍后重试');
break;
default:
// 其他错误
const errorMsg = data?.msg || '请求失败';
Message.error(errorMsg);
}
} else if (error.request) {
// 请求已发送但未收到响应(网络错误)
console.error('[Network Error]', error.request);
Message.error('网络连接失败,请检查网络');
} else {
// 请求配置错误
console.error('[Request Setup Error]', error.message);
Message.error('请求配置错误');
}
return Promise.reject(error);
}
);
// ==================== 封装请求方法 ====================
/**
*
*/
const request = {
/**
* GET
* @param url - URL
* @param config -
* @returns Promise<R<T>>
*/
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>> {
return instance.get<R<T>>(url, config).then((response) => response.data);
},
/**
* POST
* @param url - URL
* @param data -
* @param config -
* @returns Promise<R<T>>
*/
post<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<R<T>> {
return instance
.post<R<T>>(url, data, config)
.then((response) => response.data);
},
/**
* PUT
* @param url - URL
* @param data -
* @param config -
* @returns Promise<R<T>>
*/
put<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<R<T>> {
return instance
.put<R<T>>(url, data, config)
.then((response) => response.data);
},
/**
* DELETE
* @param url - URL
* @param config -
* @returns Promise<R<T>>
*/
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>> {
return instance.delete<R<T>>(url, config).then((response) => response.data);
},
/**
*
* @param config -
* @returns Promise<R<T>>
*/
request<T = any>(config: AxiosRequestConfig): Promise<R<T>> {
return instance.request<R<T>>(config).then((response) => response.data);
},
/**
* Axios
*/
instance,
};
// ==================== 导出 ====================
export default request;

258
src/utils/storage.ts Normal file
View File

@ -0,0 +1,258 @@
/**
*
* @description localStorage
*/
import {
TOKEN_KEY,
USERINFO_KEY,
USER_STATUS_KEY,
USER_ROLE_KEY,
LANG_KEY,
THEME_KEY,
} from '@/constants';
import type { LocalUserInfo } from '@/types/auth';
// ==================== 通用存储方法 ====================
/**
* localStorage
* @param key -
* @param value - JSON
*/
export function setItem<T = any>(key: string, value: T): void {
try {
const serializedValue = JSON.stringify(value);
localStorage.setItem(key, serializedValue);
} catch (error) {
console.error(`Failed to set item "${key}" to localStorage:`, error);
}
}
/**
* localStorage
* @param key -
* @returns JSON
*/
export function getItem<T = any>(key: string): T | null {
try {
const serializedValue = localStorage.getItem(key);
if (serializedValue === null) {
return null;
}
return JSON.parse(serializedValue) as T;
} catch (error) {
console.error(`Failed to get item "${key}" from localStorage:`, error);
return null;
}
}
/**
* localStorage
* @param key -
*/
export function removeItem(key: string): void {
try {
localStorage.removeItem(key);
} catch (error) {
console.error(`Failed to remove item "${key}" from localStorage:`, error);
}
}
/**
* localStorage
*/
export function clear(): void {
try {
localStorage.clear();
} catch (error) {
console.error('Failed to clear localStorage:', error);
}
}
/**
* localStorage
* @param key -
* @returns
*/
export function hasItem(key: string): boolean {
return localStorage.getItem(key) !== null;
}
// ==================== Token 相关 ====================
/**
* Token
* @param token - Token
*/
export function setToken(token: string): void {
setItem(TOKEN_KEY, token);
}
/**
* Token
* @returns Token
*/
export function getToken(): string | null {
return getItem<string>(TOKEN_KEY);
}
/**
* Token
*/
export function removeToken(): void {
removeItem(TOKEN_KEY);
}
/**
* Token
* @returns
*/
export function hasToken(): boolean {
return hasItem(TOKEN_KEY);
}
// ==================== 用户信息相关 ====================
/**
*
* @param userInfo -
*/
export function setUserInfo(userInfo: LocalUserInfo): void {
setItem(USERINFO_KEY, userInfo);
}
/**
*
* @returns
*/
export function getUserInfo(): LocalUserInfo | null {
return getItem<LocalUserInfo>(USERINFO_KEY);
}
/**
*
*/
export function removeUserInfo(): void {
removeItem(USERINFO_KEY);
}
/**
*
* @returns
*/
export function hasUserInfo(): boolean {
return hasItem(USERINFO_KEY);
}
// ==================== 用户状态相关 ====================
/**
*
* @param status -
*/
export function setUserStatus(status: number): void {
setItem(USER_STATUS_KEY, status);
}
/**
*
* @returns
*/
export function getUserStatus(): number | null {
return getItem<number>(USER_STATUS_KEY);
}
/**
*
*/
export function removeUserStatus(): void {
removeItem(USER_STATUS_KEY);
}
// ==================== 用户角色相关 ====================
/**
*
* @param role -
*/
export function setUserRole(role: string): void {
setItem(USER_ROLE_KEY, role);
}
/**
*
* @returns
*/
export function getUserRole(): string | null {
return getItem<string>(USER_ROLE_KEY);
}
/**
*
*/
export function removeUserRole(): void {
removeItem(USER_ROLE_KEY);
}
// ==================== 语言设置相关 ====================
/**
*
* @param lang - zh-CN, en-US
*/
export function setLang(lang: string): void {
setItem(LANG_KEY, lang);
}
/**
*
* @returns
*/
export function getLang(): string | null {
return getItem<string>(LANG_KEY);
}
/**
*
*/
export function removeLang(): void {
removeItem(LANG_KEY);
}
// ==================== 主题设置相关 ====================
/**
*
* @param theme - light, dark
*/
export function setTheme(theme: string): void {
setItem(THEME_KEY, theme);
}
/**
*
* @returns
*/
export function getTheme(): string | null {
return getItem<string>(THEME_KEY);
}
/**
*
*/
export function removeTheme(): void {
removeItem(THEME_KEY);
}
// ==================== 清理所有认证信息 ====================
/**
*
*/
export function clearAuthInfo(): void {
removeToken();
removeUserInfo();
removeUserStatus();
removeUserRole();
}