Files
Yi.Framework/Yi.Ai.Vue3/src/utils/request.ts
2025-06-29 00:57:57 +08:00

156 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import FingerprintJS from '@fingerprintjs/fingerprintjs';
import { ElMessage } from 'element-plus';
import hookFetch from 'hook-fetch';
import { sseTextDecoderPlugin } from 'hook-fetch/plugins';
import router from '@/routers';
import { useUserStore } from '@/stores';
// 初始化指纹
const fpPromise = FingerprintJS.load();
async function getFingerprint(): Promise<string> {
const fp = await fpPromise;
return (await fp.get()).visitorId;
}
interface BaseResponse<T = any> {
code?: number; // 变为可选字段
data: T;
msg?: string;
}
declare module 'hook-fetch' {
interface HookFetchDefaults {
response: any;
}
}
// 刷新控制变量
let isRefreshing = false;
let pendingRequests: (() => void)[] = [];
export const request = hookFetch.create<BaseResponse>({
baseURL: import.meta.env.VITE_WEB_BASE_API,
headers: {
'Content-Type': 'application/json',
},
plugins: [
sseTextDecoderPlugin({ json: true, prefix: 'data:' }),
{
name: 'fingerprint-plugin',
beforeRequest: async (config) => {
try {
const fingerprint = await getFingerprint();
config.headers = new Headers(config.headers);
config.headers.set('X-Fingerprint', fingerprint);
}
catch (error) {
console.error('指纹生成失败:', error);
}
return config;
},
},
{
name: 'jwt-auth',
beforeRequest: (config) => {
const userStore = useUserStore();
if (userStore.token) {
config.headers = new Headers(config.headers);
config.headers.set('Authorization', `Bearer ${userStore.token}`);
}
return config;
},
afterResponse: async (response: any, config: any) => {
const userStore = useUserStore();
console.log('网络请求响应----', response);
// 成功请求HTTP状态码200-299
if (response.ok) {
// 兼容响应体可能没有code字段的情况
const data = await response.json();
return {
...response,
result: data.code ? data : { code: 200, data, msg: 'success' },
};
}
// 处理HTTP状态码401
if (response.status === 401) {
// 刷新Token接口自身失败
if (config.url?.includes('/account/refresh')) {
userStore.logout();
userStore.openLoginDialog();
throw new Error('刷新Token失败');
}
// 将请求加入队列
if (isRefreshing) {
return new Promise((resolve) => {
pendingRequests.push(() => resolve(request(config)));
});
}
// 开始刷新流程
isRefreshing = true;
try {
const refreshRes = await request.post('/account/refresh', {
refresh_token: userStore.refreshToken,
}).json();
// 更新Token
userStore.setToken(refreshRes.data.token, refreshRes.data.refreshToken);
// 重试原始请求
config.headers.set('Authorization', `Bearer ${refreshRes.data.token}`);
const retryResponse = await request(config);
// 执行等待中的请求
pendingRequests.forEach(cb => cb());
pendingRequests = [];
return retryResponse;
}
catch (error) {
// 刷新失败清除状态
userStore.logout();
userStore.openLoginDialog();
throw error;
}
finally {
isRefreshing = false;
}
}
// 处理HTTP状态码403
if (response.status === 403) {
router.replace({ name: '403' });
const errorMsg = (await response.json().catch(() => ({})))?.msg || '无权限访问';
ElMessage.error(errorMsg);
throw new Error('Forbidden');
}
// 其他错误状态码
const errorData = await response.json().catch(() => ({}));
ElMessage.error(errorData.msg || `请求失败: ${response.status}`);
throw errorData;
},
// 错误处理
async onError(error: any, config: any) {
console.log('网络请求错误----', error);
console.log('网络请求错误--config--', config);
// 可以处理或转换错误
if (error.status === 401) {
// 处理未授权错误
return new Error('Please login first');
}
return error;
},
},
],
});
// 保持原有导出
export const post = request.post;
export const get = request.get;
export const put = request.put;
export const del = request.delete;
export default request;