2024-08-16 16:33:11 +08:00
import type { AxiosResponse , InternalAxiosRequestConfig } from 'axios' ;
2025-05-13 22:28:54 +08:00
import { BACKEND_ERROR_CODE , REQUEST_CANCELED_CODE , createFlatRequest } from '@sa/axios' ;
2024-03-24 05:11:30 +08:00
import { useAuthStore } from '@/store/modules/auth' ;
2024-08-16 16:33:11 +08:00
import { localStg , sessionStg } from '@/utils/storage' ;
2024-03-02 12:22:10 +08:00
import { getServiceBaseURL } from '@/utils/service' ;
2024-08-16 16:33:11 +08:00
import { decryptBase64 , decryptWithAes , encryptBase64 , encryptWithAes , generateAesKey } from '@/utils/crypto' ;
import { decrypt , encrypt } from '@/utils/jsencrypt' ;
2024-09-07 11:10:58 +08:00
import { getAuthorization , handleExpiredRequest , showErrorMsg } from './shared' ;
2024-04-27 02:29:39 +08:00
import type { RequestInstanceState } from './type' ;
2022-07-28 13:19:50 +08:00
2024-08-16 16:33:11 +08:00
const encryptHeader = 'encrypt-key' ;
2024-03-04 12:15:00 +08:00
const isHttpProxy = import . meta . env . DEV && import . meta . env . VITE_HTTP_PROXY === 'Y' ;
2024-08-16 16:33:11 +08:00
const { baseURL } = getServiceBaseURL ( import . meta . env , isHttpProxy ) ;
2022-02-07 23:21:30 +08:00
2024-04-27 02:29:39 +08:00
export const request = createFlatRequest < App.Service.Response , RequestInstanceState > (
2024-01-16 01:50:12 +08:00
{
2024-03-02 12:22:10 +08:00
baseURL ,
2024-08-16 16:33:11 +08:00
'axios-retry' : {
retries : 0
2024-01-16 01:50:12 +08:00
}
2023-11-17 08:45:00 +08:00
} ,
2024-01-16 01:50:12 +08:00
{
async onRequest ( config ) {
2024-08-16 16:33:11 +08:00
const isToken = config . headers ? . isToken === false ;
2024-01-16 01:50:12 +08:00
// set token
2023-11-17 08:45:00 +08:00
const token = localStg . get ( 'token' ) ;
2024-08-16 16:33:11 +08:00
if ( token && ! isToken ) {
2024-09-27 10:28:46 +08:00
const Authorization = getAuthorization ( ) ;
Object . assign ( config . headers , { Authorization } ) ;
2024-08-16 16:33:11 +08:00
}
2024-09-27 10:28:46 +08:00
// 客户端 ID
config . headers . Clientid = import . meta . env . VITE_APP_CLIENT_ID ;
// 对应国际化资源文件后缀
2025-04-23 17:28:03 +08:00
config . headers [ 'Content-Language' ] = ( localStg . get ( 'lang' ) || 'zh-CN' ) . replace ( '-' , '_' ) ;
2024-09-27 10:28:46 +08:00
2024-08-16 16:33:11 +08:00
handleRepeatSubmit ( config ) ;
handleEncrypt ( config ) ;
// FormData数据去请求头Content-Type
if ( config . data instanceof FormData ) {
delete config . headers [ 'Content-Type' ] ;
}
2024-01-16 01:50:12 +08:00
return config ;
} ,
isBackendSuccess ( response ) {
2024-03-24 06:23:56 +08:00
// when the backend response code is "0000"(default), it means the request is success
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
2024-04-27 02:29:39 +08:00
return String ( response . data . code ) === import . meta . env . VITE_SERVICE_SUCCESS_CODE ;
2024-01-16 01:50:12 +08:00
} ,
2024-03-24 05:11:30 +08:00
async onBackendFail ( response , instance ) {
const authStore = useAuthStore ( ) ;
2024-07-30 15:12:45 +08:00
const responseCode = String ( response . data . code ) ;
2024-03-24 05:11:30 +08:00
2024-03-24 06:23:56 +08:00
function handleLogout() {
2024-09-07 18:27:39 +08:00
authStore . resetStore ( ) ;
2024-03-24 06:23:56 +08:00
}
function logoutAndCleanup() {
handleLogout ( ) ;
window . removeEventListener ( 'beforeunload' , handleLogout ) ;
2024-04-27 02:29:39 +08:00
request . state . errMsgStack = request . state . errMsgStack . filter ( msg = > msg !== response . data . msg ) ;
2024-03-24 06:23:56 +08:00
}
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
2024-09-07 18:27:39 +08:00
// const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
// if (logoutCodes.includes(responseCode)) {
// handleLogout();
// return null;
// }
2024-03-24 05:11:30 +08:00
2024-03-24 06:23:56 +08:00
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
const modalLogoutCodes = import . meta . env . VITE_SERVICE_MODAL_LOGOUT_CODES ? . split ( ',' ) || [ ] ;
2024-09-07 18:27:39 +08:00
if ( modalLogoutCodes . includes ( responseCode ) ) {
2024-04-27 02:29:39 +08:00
request . state . errMsgStack = [ . . . ( request . state . errMsgStack || [ ] ) , response . data . msg ] ;
2024-03-24 06:23:56 +08:00
// prevent the user from refreshing the page
window . addEventListener ( 'beforeunload' , handleLogout ) ;
2025-06-19 09:26:46 +08:00
if ( ! window . location . pathname ? . startsWith ( '/login' ) ) {
window . $dialog ? . warning ( {
title : '系统提示' ,
content : '登录状态已过期,您可以继续留在该页面,或者重新登录' ,
positiveText : '重新登录' ,
negativeText : '取消' ,
maskClosable : false ,
closeOnEsc : false ,
onPositiveClick() {
logoutAndCleanup ( ) ;
}
} ) ;
request . cancelAllRequest ( ) ;
}
2025-06-19 09:33:46 +08:00
return null ;
2024-03-24 06:23:56 +08:00
}
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
// the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
const expiredTokenCodes = import . meta . env . VITE_SERVICE_EXPIRED_TOKEN_CODES ? . split ( ',' ) || [ ] ;
2024-09-07 11:10:58 +08:00
if ( expiredTokenCodes . includes ( responseCode ) ) {
const success = await handleExpiredRequest ( request . state ) ;
if ( success ) {
const Authorization = getAuthorization ( ) ;
Object . assign ( response . config . headers , { Authorization } ) ;
2024-03-24 05:11:30 +08:00
2024-09-07 11:10:58 +08:00
return instance . request ( response . config ) as Promise < AxiosResponse > ;
2024-03-24 05:11:30 +08:00
}
}
return null ;
2024-01-16 01:50:12 +08:00
} ,
transformBackendResponse ( response ) {
2024-09-02 09:34:34 +08:00
if ( import . meta . env . VITE_APP_ENCRYPT === 'Y' ) {
2024-08-16 16:33:11 +08:00
// 加密后的 AES 秘钥
const keyStr = response . headers [ encryptHeader ] ;
// 加密
if ( keyStr && keyStr !== '' ) {
const data = String ( response . data ) ;
// 请求体 AES 解密
const base64Str = decrypt ( keyStr ) ;
// base64 解码 得到请求头的 AES 秘钥
const aesKey = decryptBase64 ( base64Str . toString ( ) ) ;
// aesKey 解码 data
const decryptData = decryptWithAes ( data , aesKey ) ;
// 将结果 (得到的是 JSON 字符串) 转为 JSON
response . data = JSON . parse ( decryptData ) ;
}
}
// 二进制数据则直接返回
if ( response . request . responseType === 'blob' || response . request . responseType === 'arraybuffer' ) {
return response . data ;
}
if ( response . data . rows ) {
return response . data ;
}
2025-04-24 17:36:38 +08:00
2024-01-16 01:50:12 +08:00
return response . data . data ;
} ,
onError ( error ) {
// when the request is fail, you can show error message
2025-05-13 22:28:54 +08:00
if ( error . code === REQUEST_CANCELED_CODE ) {
return ;
}
2024-01-16 01:50:12 +08:00
let message = error . message ;
2025-05-13 22:28:54 +08:00
2024-03-24 06:23:56 +08:00
let backendErrorCode = '' ;
2022-01-03 22:20:10 +08:00
2024-03-24 06:23:56 +08:00
// get backend error message and code
2024-01-16 01:50:12 +08:00
if ( error . code === BACKEND_ERROR_CODE ) {
2024-02-04 00:14:17 +08:00
message = error . response ? . data ? . msg || message ;
2024-07-30 15:12:45 +08:00
backendErrorCode = String ( error . response ? . data ? . code || '' ) ;
2024-03-24 06:23:56 +08:00
}
// the error message is displayed in the modal
const modalLogoutCodes = import . meta . env . VITE_SERVICE_MODAL_LOGOUT_CODES ? . split ( ',' ) || [ ] ;
if ( modalLogoutCodes . includes ( backendErrorCode ) ) {
return ;
}
// when the token is expired, refresh token and retry request, so no need to show error message
const expiredTokenCodes = import . meta . env . VITE_SERVICE_EXPIRED_TOKEN_CODES ? . split ( ',' ) || [ ] ;
if ( expiredTokenCodes . includes ( backendErrorCode ) ) {
return ;
2024-01-16 01:50:12 +08:00
}
2023-11-17 08:45:00 +08:00
2024-04-27 02:29:39 +08:00
showErrorMsg ( request . state , message ) ;
2023-11-17 08:45:00 +08:00
}
}
2024-01-16 01:50:12 +08:00
) ;
2023-11-17 08:45:00 +08:00
2024-08-16 16:33:11 +08:00
function handleRepeatSubmit ( config : InternalAxiosRequestConfig ) {
// 是否需要防止数据重复提交
const isRepeatSubmit = config . headers ? . repeatSubmit === false ;
if ( ! isRepeatSubmit && ( config . method === 'post' || config . method === 'put' ) ) {
const requestObj = {
url : config.url ! ,
data : typeof config . data === 'object' ? JSON . stringify ( config . data ) : config . data ,
time : new Date ( ) . getTime ( )
} ;
const sessionObj = sessionStg . get ( 'sessionObj' ) ;
if ( ! sessionObj ) {
sessionStg . set ( 'sessionObj' , requestObj ) ;
} else {
const s_url = sessionObj . url ; // 请求地址
const s_data = sessionObj . data ; // 请求数据
const s_time = sessionObj . time ; // 请求时间
const interval = 500 ; // 间隔时间(ms),小于此时间视为重复提交
if ( s_data === requestObj . data && requestObj . time - s_time < interval && s_url === requestObj . url ) {
const message = '数据正在处理,请勿重复提交' ;
2024-09-02 09:34:34 +08:00
// eslint-disable-next-line no-console
2024-08-16 16:33:11 +08:00
console . warn ( ` [ ${ s_url } ]: ${ message } ` ) ;
throw new Error ( message ) ;
2024-01-16 01:50:12 +08:00
}
2024-08-16 16:33:11 +08:00
sessionStg . set ( 'sessionObj' , requestObj ) ;
2024-01-16 01:50:12 +08:00
}
}
2024-08-16 16:33:11 +08:00
}
function handleEncrypt ( config : InternalAxiosRequestConfig ) {
// 是否需要加密
const isEncrypt = config . headers ? . isEncrypt === 'true' ;
2024-09-02 09:34:34 +08:00
if ( import . meta . env . VITE_APP_ENCRYPT === 'Y' ) {
2024-08-16 16:33:11 +08:00
// 当开启参数加密
if ( isEncrypt && ( config . method === 'post' || config . method === 'put' ) ) {
// 生成一个 AES 密钥
const aesKey = generateAesKey ( ) ;
config . headers [ encryptHeader ] = encrypt ( encryptBase64 ( aesKey ) ) ;
config . data =
typeof config . data === 'object'
? encryptWithAes ( JSON . stringify ( config . data ) , aesKey )
: encryptWithAes ( config . data , aesKey ) ;
}
}
}