ruoyi-plus-vben5/apps/web-antd/src/api/request.ts

277 lines
8.9 KiB
TypeScript
Raw Normal View History

2024-06-02 20:50:51 +08:00
/**
*
*/
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
2024-08-07 08:57:56 +08:00
import { $t } from '@vben/locales';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
errorMessageResponseInterceptor,
RequestClient,
stringify,
} from '@vben/request';
2024-07-30 21:10:28 +08:00
import { useAccessStore } from '@vben/stores';
2024-06-02 20:50:51 +08:00
2024-08-07 08:57:56 +08:00
import { message, Modal } from 'ant-design-vue';
import { isEmpty, isNull } from 'lodash-es';
2024-06-02 20:50:51 +08:00
2024-07-30 21:10:28 +08:00
import { useAuthStore } from '#/store';
2024-08-07 08:57:56 +08:00
import {
decryptBase64,
decryptWithAes,
encryptBase64,
encryptWithAes,
generateAesKey,
} from '#/utils/encryption/crypto';
import * as encryptUtil from '#/utils/encryption/jsencrypt';
2024-06-02 20:50:51 +08:00
2024-08-07 08:57:56 +08:00
const { apiURL, clientId, enableEncrypt } = useAppConfig(
import.meta.env,
import.meta.env.PROD,
);
/**
*
* api 401
*/
let isLogoutProcessing = false;
function createRequestClient(baseURL: string) {
2024-06-02 20:50:51 +08:00
const client = new RequestClient({
2024-08-07 08:57:56 +08:00
// 后端地址
baseURL,
2024-08-07 08:57:56 +08:00
// 消息提示类型
errorMessageMode: 'message',
2024-10-18 15:02:39 +08:00
// 是否返回原生响应 比如:需要获取响应头时使用该属性
2024-08-07 08:57:56 +08:00
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
});
/**
*
*/
async function doReAuthenticate() {
console.warn('Access token or refresh token is invalid or expired. ');
const accessStore = useAccessStore();
const authStore = useAuthStore();
accessStore.setAccessToken(null);
if (
preferences.app.loginExpiredMode === 'modal' &&
accessStore.isAccessChecked
) {
accessStore.setLoginExpired(true);
} else {
await authStore.logout();
}
}
/**
* token逻辑
*/
async function doRefreshToken() {
// 不需要
// 保留此方法只是为了合并方便
return '';
}
function formatToken(token: null | string) {
return token ? `Bearer ${token}` : null;
}
client.addRequestInterceptor({
fulfilled: (config) => {
const accessStore = useAccessStore();
// 添加token
config.headers.Authorization = formatToken(accessStore.accessToken);
2024-08-07 08:57:56 +08:00
/**
* locale跟后台不一致
*/
const language = preferences.app.locale.replace('-', '_');
config.headers['Accept-Language'] = language;
2024-11-23 17:56:03 +08:00
config.headers['Content-Language'] = language;
2024-10-18 15:02:39 +08:00
// 添加全局clientId
config.headers.clientId = clientId;
2024-08-07 08:57:56 +08:00
/**
* get/delete参数
* paramsSerializer则不走此逻辑
*/
if (
['DELETE', 'GET'].includes(config.method?.toUpperCase() || '') &&
config.params &&
!config.paramsSerializer
) {
/**
* 1. (params Map类型参数)key
* 2. arr=1&arr=2&arr=3
*/
config.paramsSerializer = (params) =>
stringify(params, { arrayFormat: 'repeat' });
}
const { encrypt } = config;
2024-10-18 15:02:39 +08:00
// 全局开启请求加密功能 && 该请求开启 && 是post/put请求
if (
enableEncrypt &&
encrypt &&
['POST', 'PUT'].includes(config.method?.toUpperCase() || '')
) {
const aesKey = generateAesKey();
config.headers['encrypt-key'] = encryptUtil.encrypt(
encryptBase64(aesKey),
);
2024-08-07 08:57:56 +08:00
config.data =
typeof config.data === 'object'
? encryptWithAes(JSON.stringify(config.data), aesKey)
: encryptWithAes(config.data, aesKey);
}
return config;
},
2024-08-07 08:57:56 +08:00
});
2024-08-20 10:48:51 +08:00
// 通用的错误处理, 如果没有进入上面的错误处理逻辑,就会进入这里
// 主要处理http状态码不为200的情况 必须放在在下面的响应拦截器之前
client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
);
client.addResponseInterceptor<HttpResponse>({
fulfilled: async (response) => {
const encryptKey = (response.headers || {})['encrypt-key'];
if (encryptKey) {
/** RSA私钥解密 拿到解密秘钥的base64 */
const base64Str = encryptUtil.decrypt(encryptKey);
/** base64 解码 得到请求头的 AES 秘钥 */
const aesSecret = decryptBase64(base64Str.toString());
/** 使用aesKey解密 responseData */
const decryptData = decryptWithAes(
response.data as unknown as string,
aesSecret,
);
/** 赋值 需要转为对象 */
response.data = JSON.parse(decryptData);
}
2024-08-07 08:57:56 +08:00
const { isReturnNativeResponse, isTransformResponse } = response.config;
2024-10-18 15:02:39 +08:00
// 是否返回原生响应 比如:需要获取响应时使用该属性
if (isReturnNativeResponse) {
return response;
}
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取codedatamessage这些信息时开启
if (!isTransformResponse) {
/**
* json
* type为blob且content-type为application/json时
* blob的类型 json类型和报错的类型是一致的
*/
if (
response.config.responseType === 'blob' &&
response.headers['content-type']?.includes?.('application/json')
) {
// 这时候的data为blob类型
const blob = response.data as unknown as Blob;
// 拿到字符串转json对象
response.data = JSON.parse(await blob.text());
// 然后按正常逻辑执行下面的代码(判断业务状态码)
} else {
// 不为blob 直接返回
return response.data;
}
}
2024-08-07 08:57:56 +08:00
const axiosResponseData = response.data;
if (!axiosResponseData) {
throw new Error($t('fallback.http.apiRequestFailed'));
}
2024-08-07 08:57:56 +08:00
// ruoyi-plus没有采用严格的{code, msg, data}模式
const { code, data, msg, ...other } = axiosResponseData;
2024-08-07 08:57:56 +08:00
// 这里逻辑可以根据项目进行修改
const hasSuccess = Reflect.has(axiosResponseData, 'code') && code === 200;
if (hasSuccess) {
let successMsg = msg;
2024-08-07 08:57:56 +08:00
if (isNull(successMsg) || isEmpty(successMsg)) {
successMsg = $t(`fallback.http.operationSuccess`);
}
2024-06-02 20:50:51 +08:00
if (response.config.successMessageMode === 'modal') {
Modal.success({
content: successMsg,
title: $t('fallback.http.successTip'),
});
} else if (response.config.successMessageMode === 'message') {
message.success(successMsg);
}
// 如果有data 直接返回data 没有data将剩余参数(...other)封装为data返回
// 需要考虑data为null的情况(比如查询为空)
if (data !== undefined) {
return data;
}
return other;
2024-08-07 08:57:56 +08:00
}
// 在此处根据自己项目的实际情况对不同的code执行不同的操作
// 如果不希望中断当前请求请return数据否则直接抛出异常即可
let timeoutMsg = '';
switch (code) {
case 401: {
// 已经在登出过程中 不再执行
if (isLogoutProcessing) {
return;
}
isLogoutProcessing = true;
const _msg = '登录超时, 请重新登录';
const userStore = useAuthStore();
userStore.logout().finally(() => {
message.error(_msg);
isLogoutProcessing = false;
});
// 不再执行下面逻辑
return;
}
default: {
if (msg) {
timeoutMsg = msg;
2024-08-07 08:57:56 +08:00
}
}
}
// errorMessageMode='modal'的时候会显示modal错误弹窗而不是消息提示用于一些比较重要的错误
// errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
if (response.config.errorMessageMode === 'modal') {
Modal.error({
content: timeoutMsg,
title: $t('fallback.http.errorTip'),
});
} else if (response.config.errorMessageMode === 'message') {
message.error(timeoutMsg);
}
2024-08-07 08:57:56 +08:00
throw new Error(timeoutMsg || $t('fallback.http.apiRequestFailed'));
},
});
// token过期的处理
client.addResponseInterceptor(
authenticateResponseInterceptor({
client,
doReAuthenticate,
doRefreshToken,
enableRefreshToken: preferences.app.enableRefreshToken,
formatToken,
}),
);
return client;
2024-06-02 20:50:51 +08:00
}
export const requestClient = createRequestClient(apiURL);
export const baseRequestClient = new RequestClient({ baseURL: apiURL });