import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' import qs from 'qs' import { config } from '@/config/axios/config' import { getAccessToken, getRefreshToken, getTenantId } from '@/utils/auth' import errorCode from './errorCode' import { useI18n } from '@/hooks/web/useI18n' const { t } = useI18n() const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE const BASE_URL = import.meta.env.VITE_BASE_URL const BASE_API = import.meta.env.VITE_API_URL const { result_code, base_url } = config // 需要忽略的提示。忽略后,自动 Promise.reject('error') const ignoreMsgs = [ '无效的刷新令牌', // 刷新令牌被删除时,不用提示 '刷新令牌已过期' // 使用刷新令牌,刷新获取新的访问令牌时,结果因为过期失败,此时需要忽略。否则,会导致继续 401,无法跳转到登出界面 ] // 是否显示重新登录 export const isRelogin = { show: false } // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现 // 请求队列 // const requestList = [] // 是否正在刷新中 let isRefreshToken = false export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH] // 创建axios实例 const service: AxiosInstance = axios.create({ baseURL: BASE_URL + BASE_API, // api 的 base_url timeout: config.request_timeout // 请求超时时间 }) // request拦截器 service.interceptors.request.use( (config: AxiosRequestConfig) => { // 是否需要设置 token const isToken = (config!.headers || {}).isToken === false if (getAccessToken() && !isToken) { ;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token } // 设置租户 if (tenantEnable) { const tenantId = getTenantId() if (tenantId) (config as Recordable).headers.common['tenant-id'] = tenantId } if ( config.method === 'post' && config!.headers!['Content-Type'] === 'application/x-www-form-urlencoded' ) { config.data = qs.stringify(config.data) } // get参数编码 if (config.method === 'get' && config.params) { let url = config.url as string url += '?' const keys = Object.keys(config.params) for (const key of keys) { if (config.params[key] !== void 0 && config.params[key] !== null) { url += `${key}=${encodeURIComponent(config.params[key])}&` } } // 给 get 请求加上时间戳参数,避免从缓存中拿数据 // const now = new Date().getTime() // url = url.substring(0, url.length - 1) + `?_t=${now}` config.params = {} config.url = url } return config }, (error: AxiosError) => { // Do something with request error console.log(error) // for debug Promise.reject(error) } ) // response 拦截器 service.interceptors.response.use( async (response: AxiosResponse) => { const { data } = response if (!data) { // 返回“[HTTP]请求没有返回值”; throw new Error() } // 未设置状态码则默认成功状态 const code = data.code || result_code // 获取错误信息 const msg = data.msg || errorCode[code] || errorCode['default'] if (ignoreMsgs.indexOf(msg) !== -1) { // 如果是忽略的错误码,直接返回 msg 异常 return Promise.reject(msg) } else if (code === 401) { // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 if (!isRefreshToken) { isRefreshToken = true // 1. 如果获取不到刷新令牌,则只能执行登出操作 if (!getRefreshToken()) { return handleAuthorized() } // 2. 进行刷新访问令牌 // TODO: 引入refreshToken会循环依赖报错 } } else if (code === 500) { ElMessage.error(t('sys.api.errMsg500')) return Promise.reject(new Error(msg)) } else if (code === 901) { ElMessage.error( '
' + t('sys.api.errMsg901') + '
' + '
 
' + '
参考 https://doc.iocoder.cn/ 教程
' + '
 
' + '
5 分钟搭建本地环境
' ) return Promise.reject(new Error(msg)) } else if (code !== 200) { if (msg === '无效的刷新令牌') { // hard coding:忽略这个提示,直接登出 console.log(msg) } else { ElNotification.error({ title: msg }) } return Promise.reject('error') } else { return data } }, (error: AxiosError) => { console.log('err' + error) // for debug let { message } = error if (message === 'Network Error') { message = t('sys.api.errorMessage') } else if (message.includes('timeout')) { message = t('sys.api.apiTimeoutMessage') } else if (message.includes('Request failed with status code')) { message = t('sys.api.apiRequestFailed') + message.substr(message.length - 3) } ElMessage.error(message) return Promise.reject(error) } ) function handleAuthorized() { if (!isRelogin.show) { isRelogin.show = true ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), { confirmButtonText: t('login.relogin'), cancelButtonText: t('common.cancel'), type: 'warning' }) .then(() => { isRelogin.show = false }) .catch(() => { isRelogin.show = false }) } return Promise.reject(t('sys.api.timeoutMessage')) } export { service }