Files
Situation-Awareness-Platfor…/src/utils/request.ts
2025-06-27 18:13:20 +08:00

266 lines
8.3 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 axios, { type AxiosRequestConfig } from 'axios';
import { dealWarning } from './status';
import router from '@/router';
import { repoInfoStore } from '@/stores/Repo';
import { useGlobalInfoStore } from '@/stores/Global';
import { cutParamsInUrl, getSignUtmSource } from './index';
import { Message } from 'vue-devui/message';
import degradeInterceptor from './degradeInterceptor';
import { useAccountStore } from '@/stores/user';
const handleError = dealWarning();
import CryptoJS from 'crypto-js'; // 引入加密库
// 加密配置
const sKey = CryptoJS.enc.Utf8.parse('mXzfCSPBKmEA8aLq');
const iv = CryptoJS.enc.Utf8.parse('tbeJJLC6dZQXXtWr');
/**
* 解密函数
* @param text 待解密的Base64字符串
*/
const $Decrypt = (text) => {
if (!text) return text;
try {
// 处理Base64编码
let src = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Base64.parse(text));
// AES-CBC-PKCS7解密
let bytes = CryptoJS.AES.decrypt(src, sKey, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
let originalText = bytes.toString(CryptoJS.enc.Utf8);
// 尝试解析为JSON对象
return JSON.parse(originalText);
} catch (e) {
console.error('解密失败:', e);
return text; // 解密失败返回原始数据
}
};
/**
* 加密函数
* @param data 待加密的对象或字符串
*/
const $Encrypt = (data) => {
if (!data) return data;
try {
// 将数据转换为JSON字符串
const jsonData = typeof data === 'string' ? data : JSON.stringify(data);
// AES-CBC-PKCS7加密
const encrypted = CryptoJS.AES.encrypt(jsonData, sKey, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// 转换为Base64字符串
return encrypted.toString();
} catch (e) {
console.error('加密失败:', e);
return data; // 加密失败返回原始数据
}
};
/** 设置用户中心相关api迁移时的api前缀 */
const setPassportPrefix = (url: string, method: string) => {
const prefix = (import.meta as any).env.VITE_PASSPORT_PREFIX;
if (prefix) {
if (url?.includes('/api/v1/user/')) {
// 用户信息相关
return `${url}`;
}
if (url?.includes('/api/v1/oauth/')) {
// 用户权限
return `${prefix}${url}`;
}
if (url?.includes('/api/v1/internal/messages')) {
// 消息通知
return `${prefix}${url}`;
}
if (url?.includes('/api/v1/follow')) {
// 关注状态
return `${prefix}${url}`;
}
if (url?.includes('/api/v1/obs') && method === 'get') {
// 图片相关
return `${prefix}${url}`;
}
}
return url;
};
export const baseURL = (import.meta as any).env.VITE_API_HOST;
export const baseDevURL = (import.meta as any).env.VITE_DEV_API_HOST;
type CustomConfigs = {
// 是否使用自定义的错误处理
customError?: boolean;
// 是否需要加密请求数据
needEncrypt?: boolean;
// 是否需要解密响应数据
needDecrypt?: boolean;
};
interface PendingTask {
config: AxiosRequestConfig;
resolve: Function;
}
let refreshing = false;
const queue: PendingTask[] = [];
const refreshToken = async () => axios.request({
url: baseURL + `${(import.meta as any).env.VITE_PASSPORT_PREFIX || ''}` + '/api/v1/user/token/refresh',
method: 'post',
data: {
refresh_token: localStorage.getItem('refresh_token')
},
headers: {
Authorization: 'Bearer ' + localStorage.getItem('access_token'),
'content-type': 'application/x-www-form-urlencoded'
},
withCredentials: true
});
const proxyService = (params: any, customConfigs?: CustomConfigs) => {
const account = useAccountStore();
const service = axios.create({
baseURL: params.apiType === 'devApi' ? baseDevURL : baseURL,
timeout: 15000,
headers: {
'DP_TOKEN': account.accountInfo.access_token,
'DP_REFRESH_TOKEN': account.accountInfo.refresh_token,
},
withCredentials: true
});
/** user api相关请求白名单 */
const whiteList = ['/login', 'login/mobile', '/sendVeriCode', '/register', '/quickLogin', '/quickRegister', '/forgetCode', '/forgetPassword', '/checkSameUser', '/token'];
const notAuthorization = (url: string = '') => {
return whiteList.some((str) => url.includes(str));
};
// request 拦截器
service.interceptors.request.use(
(config) => {
// 配置请求头
const globalStore = useGlobalInfoStore();
const repoStore = repoInfoStore();
const currentRoute = router?.currentRoute?.value;
const repoId = currentRoute?.meta?.type === 'repo' ? repoStore.repoInfo?.id : undefined;
const title = config.headers['homeweb-page-title'] || currentRoute?.meta?.reportTitle;
delete config.headers['homeweb-page-title'];
if (title && params.apiType !== 'devApi') {
// devpress接口不需要上报
config.headers['page-title'] = encodeURIComponent(title);
}
if (currentRoute.name === 'homepage' && params.apiType !== 'devApi') {
const homeTitleConfig = {
0: '个人主页',
1: '组织主页'
};
const pageTitle = homeTitleConfig[globalStore.namespaceType as 0 | 1];
config.headers['page-title'] = encodeURIComponent(pageTitle);
}
if (repoId && params.apiType !== 'devApi') {
config.headers['page-repo-id'] = repoId;
}
if (params.apiType !== 'devApi') {
config.headers['page-ref'] = encodeURIComponent(cutParamsInUrl(window.page_ref || ''));
config.headers['page-uri'] = encodeURIComponent(cutParamsInUrl(window.location.href || ''));
config.headers['gitcode-utm-source'] = encodeURIComponent(getSignUtmSource());
}
const access_token = window.localStorage.getItem('access_token');
const { url } = config;
if (access_token && !notAuthorization(url)) {
config.headers.Authorization = `Bearer ${access_token}`;
}
/** 修改用户模块前缀名称 */
config.url = setPassportPrefix(config.url as string, config.method as string);
return config;
},
(error) => {
return Promise.reject(error);
}
);
service.interceptors.request.use(degradeInterceptor.onRequestFulfilled, degradeInterceptor.onRequestRejected);
// response 拦截器
service.interceptors.response.use(
(response) => {
response.data.data = $Decrypt(response.data.data)
return response;
},
async (error) => {
const { response } = error;
const { config } = response || {};
if (refreshing) {
return new Promise((resolve) => {
queue.unshift({
config,
resolve
});
});
}
if (response?.status === 401 && localStorage.getItem('access_token') && !config?.url.includes('/token/refresh')) {
refreshing = true;
try {
const res = await refreshToken();
refreshing = false;
if (res.status === 200) {
const { access_token, refresh_token } = res.data;
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
queue.forEach(({ config, resolve }) => {
if (config.headers) {
config.headers.Authorization = `Bearer ${access_token}`;
}
resolve(axios(config));
});
queue.splice(0);
config.headers.Authorization = `Bearer ${access_token}`;
return axios(config);
} else {
handleError({}, true);
return Promise.reject(res.data);
}
} catch (e) {
handleError({}, true);
}
} else if (response) {
// 请求已发出但是不在2xx的范围
// showMessage(response);
response.data.http_status = response.status;
if (!customConfigs || !customConfigs.customError) {
handleError(response);
}
return Promise.reject(response.data);
} else {
// 网络连接超时
const timeoutRegx = /\btimeout\b/;
if (error?.message && timeoutRegx.test(error.message)) {
// Message.error('网络连接超时');
}
return Promise.reject(error);
}
}
);
service.interceptors.response.use(degradeInterceptor.onResponseFulfilled, degradeInterceptor.onResponseRejected);
return service(params);
};
export default proxyService;