import qs from 'qs' import { addErrorLog, needErrorLog } from '@vab/plugins/errorLog' import { gp } from '@gp' import { useUserStore } from '@/store/modules/user' import { baseURL, contentType, debounce, messageName, requestTimeout, statusName, successCode, } from '@/config' import router from '@/router' import { isArray } from '@/utils/validate' import { refreshToken } from '@/api/refreshToken' let loadingInstance: any let refreshToking = false let requests: (() => void)[] = [] // 操作正常Code数组 const codeVerificationArray = isArray(successCode) ? [...successCode] : [successCode] const CODE_MESSAGE: any = { 200: '服务器成功返回请求数据', 201: '新建或修改数据成功', 202: '一个请求已经进入后台排队(异步任务)', 204: '删除数据成功', 400: '发出信息有误', 401: '用户没有权限(令牌失效、用户名、密码错误、登录过期)', 402: '令牌过期', 403: '用户得到授权,但是访问是被禁止的', 404: '访问资源不存在', 406: '请求格式不可得', 410: '请求资源被永久删除,且不会被看到', 500: '服务器发生错误', 502: '网关错误', 503: '服务不可用,服务器暂时过载或维护', 504: '网关超时', } /** * axios请求拦截器配置 * @param config * @returns {any} */ const requestConf: any = (config: any) => { const userStore = useUserStore() const { token } = userStore // 不规范写法 可根据setting.config.js tokenName配置随意自定义headers // if (token) config.headers[tokenName] = token // 规范写法 不可随意自定义 if (token) config.headers['Authorization'] = `Bearer ${token}` if ( config.data && config.headers['Content-Type'] === 'application/x-www-form-urlencoded;charset=UTF-8' ) config.data = qs.stringify(config.data) if (debounce.some((item) => config.url.includes(item))) loadingInstance = gp.$baseLoading() return config } /** * 刷新刷新令牌 * @param config 过期请求配置 * @returns {any} 返回结果 */ const tryRefreshToken = async (config: any) => { if (!refreshToking) { refreshToking = true try { const { data: { token }, }: any = await refreshToken() if (token) { const { setToken } = useUserStore() setToken(token) // 已经刷新了token,将所有队列中的请求进行重试 requests.forEach((cb: any) => cb(token)) requests = [] return instance(requestConf(config)) } } catch (error) { console.error('refreshToken error =>', error) router.push({ path: '/login', replace: true }).then(() => {}) } finally { refreshToking = false } } else { return new Promise((resolve) => { // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行 requests.push(() => { resolve(instance(requestConf(config))) }) }) } } /** * axios响应拦截器 * @param config 请求配置 * @param data response数据 * @param status HTTP status * @param statusText HTTP status text * @returns {Promise<*|*>} */ const handleData = async ({ config, data, status, statusText }: any) => { const { resetAll } = useUserStore() if (loadingInstance) loadingInstance.close() // 若data.code存在,覆盖默认code let code = data && data[statusName] ? data[statusName] : status // 若code属于操作正常code,则status修改为200 if (codeVerificationArray.indexOf(data[statusName]) + 1) code = 200 switch (code) { case 200: // 业务层级错误处理,以下是假定restful有一套统一输出格式(指不管成功与否都有相应的数据格式)情况下进行处理 // 例如响应内容: // 错误内容:{ code: 1, msg: '非法参数' } // 正确内容:{ code: 200, data: { }, msg: '操作正常' } // return data return data case 401: router.push({ path: '/login', replace: true }).then(() => { resetAll().then(() => {}) }) break case 402: return await tryRefreshToken(config) case 403: router.push({ path: '/403' }).then(() => {}) break } // 异常处理 // 若data.msg存在,覆盖默认提醒消息 const errMsg = `${ data && data[messageName] ? data[messageName] : CODE_MESSAGE[code] ? CODE_MESSAGE[code] : statusText }` // 是否显示高亮错误(与errorHandler钩子触发逻辑一致) gp.$baseMessage(errMsg, 'error', 'vab-hey-message-error', false) if (needErrorLog()) addErrorLog({ message: errMsg, stack: data, isRequest: true }) throw data } /** * @description axios初始化 */ const instance = axios.create({ baseURL, timeout: requestTimeout, headers: { 'Content-Type': contentType, }, }) /** * @description axios请求拦截器 */ instance.interceptors.request.use(requestConf, (error) => { return Promise.reject(error) }) /** * @description axios响应拦截器 */ instance.interceptors.response.use( (response) => handleData(response), (error) => { const { response } = error if (response === undefined) { if (loadingInstance) loadingInstance.close() gp.$baseMessage( '连接后台接口失败,可能由以下原因造成:后端不支持跨域CORS、接口地址不存在、请求超时等,请联系管理员排查后端接口问题 ', 'error', 'vab-hey-message-error', false ) return {} } else return handleData(response) } ) export default instance