Files
mayfly-go/frontend/src/hooks/useRequest.ts
zongyangleo 1e1ded4db8 !153 fix:还原达梦驱动,修复数据库多级 ssh 跳 bug
* fix:还原达梦驱动,修复数据库多级 ssh 跳 bug
* feat: 支持milvus操作
2026-04-27 05:13:40 +00:00

267 lines
9.5 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 router from '@/router';
import { clearUser, getClientId, getRefreshToken, getToken, saveRefreshToken, saveToken } from '@/common/utils/storage';
import { templateResolve } from '@/common/utils/string';
import { ElMessage } from 'element-plus';
import { createFetch, UseFetchReturn } from '@vueuse/core';
import Api from '@/common/Api';
import { Result, ResultEnum } from '@/common/request';
import config from '@/common/config';
import { ref, unref } from 'vue';
import { URL_401 } from '@/router/staticRouter';
import openApi from '@/common/openApi';
import { useThemeConfig } from '@/store/themeConfig';
import JSONBig from 'json-bigint';
const baseUrl: string = config.baseApiUrl;
// 配置 JSONBig将大数int64/uint64转为字符串避免精度丢失
const JSONBigString = JSONBig({ storeAsString: true });
const useCustomFetch = createFetch({
baseUrl: baseUrl,
combination: 'chain',
options: {
immediate: false,
timeout: 600000,
// beforeFetch in pre-configured instance will only run when the newly spawned instance do not pass beforeFetch
async beforeFetch({ url, options }) {
const token = getToken();
const headers = new Headers(options.headers || {});
if (token) {
headers.set('Authorization', token);
headers.set('ClientId', getClientId());
}
const themeConfig = useThemeConfig().themeConfig;
// 如果不是 FormData才设置 Content-Type
if (!(options.body instanceof FormData)) {
headers.set('Content-Type', 'application/json');
}
headers.set('Accept-Language', themeConfig?.globalI18n);
options.headers = headers;
return { url, options };
},
async afterFetch(ctx: any) {
// 使用 json-bigint 解析响应数据,解决 int64/uint64 精度丢失问题
const responseText = await ctx.response.text();
try {
ctx.data = JSONBigString.parse(responseText);
} catch (err) {
// 如果解析失败,尝试使用原生 JSON.parse
try {
ctx.data = JSON.parse(responseText);
} catch {
ctx.data = responseText;
}
}
return ctx;
},
},
});
interface EsReq {
esProxyReq?: boolean;
}
export interface RequestOptions extends RequestInit, EsReq {}
export function useApiFetch<T, P = any>(api: Api, params?: P, reqOptions?: RequestOptions) {
const currentParam = ref(params);
const uaf: any = useCustomFetch<T>(api.url, {
async beforeFetch({ url, options }) {
options.method = api.method;
let paramsValue = unref(currentParam);
let apiUrl = url;
// 简单判断该url是否是restful风格
if (apiUrl.indexOf('{') != -1 && paramsValue) {
apiUrl = templateResolve(apiUrl, paramsValue);
}
if (api.beforeHandler) {
paramsValue = await api.beforeHandler(paramsValue);
}
// post和put使用json格式传参如果是FormData则直接使用
const method = options.method?.toLowerCase();
if ((method === 'post' || method === 'put') && paramsValue) {
if (paramsValue instanceof FormData) {
options.body = paramsValue;
// 对于 FormData删除 Content-Type header让浏览器自动设置 multipart/form-data 和 boundary
if (options.headers instanceof Headers) {
options.headers.delete('Content-Type');
} else if (options.headers && typeof options.headers === 'object') {
delete (options.headers as any)['Content-Type'];
}
} else {
options.body = JSON.stringify(paramsValue);
}
} else if (paramsValue && method !== 'post' && method !== 'put') {
const searchParam = new URLSearchParams();
Object.keys(paramsValue).forEach((key) => {
const val = paramsValue[key];
if (val) {
searchParam.append(key, val);
}
});
apiUrl = `${apiUrl}?${searchParam.toString()}`;
}
// 确保 FormData 的 body 不被 reqOptions 覆盖
const finalOptions = {
...options,
...reqOptions,
};
// 如果原始 options.body 是 FormData优先保留
if (options.body instanceof FormData) {
finalOptions.body = options.body;
// 对于 FormData不要设置 Content-Type让浏览器自动设置 multipart/form-data 和 boundary
if (finalOptions.headers instanceof Headers) {
finalOptions.headers.delete('Content-Type');
} else if (finalOptions.headers && typeof finalOptions.headers === 'object') {
delete (finalOptions.headers as any)['Content-Type'];
}
}
return {
url: apiUrl,
options: finalOptions,
};
},
onFetchError: (ctx: { data: any }) => {
if (reqOptions?.esProxyReq) {
// 使用 json-bigint 解析错误响应
try {
const errorText = typeof ctx.data === 'string' ? ctx.data : JSON.stringify(ctx.data);
uaf.data = { value: JSONBigString.parse(errorText) };
} catch {
uaf.data = { value: ctx.data };
}
return Promise.resolve(uaf.data);
}
return ctx;
},
});
// 统一处理后的返回结果如果直接使用uaf.data则数据会出现由{code: x, data: {}} -> data 的变化导致某些结果绑定报错
const data = ref<T | null>(null);
return {
execute: async function (executeParam?: P) {
if (executeParam !== undefined) {
currentParam.value = executeParam;
}
await execCustomFetch(uaf, reqOptions);
data.value = uaf.data.value;
},
isFetching: uaf.isFetching,
data: data,
abort: uaf.abort,
};
}
let refreshingToken = false;
let queue: any[] = [];
async function execCustomFetch(uaf: UseFetchReturn<any>, reqOptions?: RequestOptions) {
try {
await uaf.execute(true);
} catch (e: any) {
if (!reqOptions?.esProxyReq) {
const rejectPromise = Promise.reject(e);
if (e?.name == 'AbortError') {
console.log('请求已取消');
return rejectPromise;
}
const respStatus = uaf.response.value?.status;
if (respStatus == 404) {
ElMessage.error('url not found');
return rejectPromise;
}
if (respStatus == 500) {
ElMessage.error('server error');
return rejectPromise;
}
console.error(e);
ElMessage.error('network error');
return rejectPromise;
}
}
const result: Result & { error: any; status: number } = uaf.data.value as any;
if (!result) {
ElMessage.error('network request failed');
return Promise.reject(result);
}
// es代理请求
if (reqOptions?.esProxyReq) {
uaf.data.value = result;
return Promise.resolve(result);
}
const resultCode = result.code;
// 如果返回为成功结果则将结果的data赋值给响应式data
if (resultCode === ResultEnum.SUCCESS) {
uaf.data.value = result.data;
return;
}
// 如果是accessToken失效则使用refreshToken刷新token
if (resultCode == ResultEnum.ACCESS_TOKEN_INVALID) {
if (refreshingToken) {
// 请求加入队列等待, 防止并发多次请求refreshToken
return new Promise((resolve) => {
queue.push(() => {
resolve(execCustomFetch(uaf, reqOptions));
});
});
}
try {
refreshingToken = true;
const res = await openApi.refreshToken({ refresh_token: getRefreshToken() });
saveToken(res.token);
saveRefreshToken(res.refresh_token);
// 重新缓存后端用户权限code
await openApi.getPermissions();
// 执行accessToken失效的请求
queue.forEach((resolve: any) => {
resolve();
});
} catch (e: any) {
clearUser();
} finally {
refreshingToken = false;
queue = [];
}
await execCustomFetch(uaf, reqOptions);
return;
}
// 如果提示没有权限,则跳转至无权限页面
if (resultCode === ResultEnum.NO_PERMISSION) {
await router.push({
path: URL_401,
});
return Promise.reject(result);
}
// 如果返回的code不为成功则会返回对应的错误msg则直接统一通知即可。忽略登录超时或没有权限的提示直接跳转至401页面
if (result.msg && resultCode != ResultEnum.NO_PERMISSION) {
ElMessage.error(result.msg);
uaf.error.value = new Error(result.msg);
}
return Promise.reject(result);
}