Axios封装
实现功能:针对不同请求设置token、code不为成功时的信息提示、code异常情况下的异常处理、屏蔽某些接口、简洁的数据结构响应
缺点:1.status为200时,code为401时不会进行无感刷新,在首页游客登录状态下缺失此功能是致命缺陷,对只可以登录状态才能进行首页的页面有效。
2.登录状态是不可以进行无感刷新的,token失效直接跳转到登录页(考虑需求,一般都不需要此功能)
import axios from 'axios' import { DialogAlert } from '@/components/ui/dialog/index' import { storage } from '@/utils/storage' //封装获取token import Router from '@/router' import store from '@/store' // const pendingMap = new Map() const dialogMsg = DialogAlert.Msg const dialogAlert = DialogAlert.Alert let showExpireLoginAlert = false // 无感刷新的参数 let isRefreshToken = false // 是否已经发出刷新请求 let overTimer = 0 // 最多请求5次 const requestList = [] // token过期的api列表 const noHandleResonseUrlList = [ //10.需要屏蔽的接口数组 '/outer/monitor/list/data', '/outer/monitor/view/data/' ] const service = axios.create({ 1.创建实例 baseURL: process.env.VUE_APP_BASE_URL, // 设置统一的请求前缀 timeout: 10000 // 设置统一的超时时长 }) // 自定义配置 let initCustomOptions = Object.assign({ //7.配置化功能需求 repeatRequestCancel: false, // 是否开启取消重复请求, 默认为 true loading: false, // 是否开启loading层效果, 默认为false reductDataFormat: true, // 是否开启简洁的数据结构响应, 默认为true errorMessageShow: true, // 是否开启接口错误信息展示,默认为true codeMessageShow: false, // 是否开启code不为成功时的信息提示, 默认为false dataCodeErrorHandle: true // 是否开启code异常情况下的异常处理 }) // 请求拦截 service.interceptors.request.use( 2.设置请求拦截 config => { // 每次请求前,先检查请求是否重复了,重复了就取消上一次的请求 // removePending(config) // 开启了重复请求,就把请求添加到队列中 // initCustomOptions.repeatRequestCancel && addPending(config) // 携带token settingToken(config) 3.设置token // 白名单处理,有些接口不携带token 4.针对某些接口不需要设置token的进行操作 if (!whiteListNoTokenApi(config.url)) { // 不需要携带token的接口 delete config.headers.Authorization } return config }, error => { return Promise.reject(error) 5.出现出错返回一个reject } ) // 响应拦截 service.interceptors.response.use( //6.设置响应拦截 response => { const { codeMessageShow, //8.开启对应的功能需求 dataCodeErrorHandle } = initCustomOptions // 请求完成之后要删除请求map中的请求key // removePending(response.config) if (codeMessageShow && response.status !== 200) { //9.针对所有失败的请求进行拦截,实际不会执行该段代码,status非200直接进行下面的error dialogMsg(response.data.msg, {icon: 3}) return Promise.reject(response.data) // status不等于200, 页面具体逻辑就不执行了 } const requestUrl = response.config.url // 请求是成功请求,但是结果不对 let hasPath = false noHandleResonseUrlList.forEach(item => { //11.屏蔽的某些接口 if (requestUrl.includes(item)) hasPath = true }) if (dataCodeErrorHandle && //12.针对status为200,但是实际后端code报错 response.status === 200 && response.data.code !== 200 && !hasPath) { const { code } = response.data if (code === 401) { //13.针对token失效情况 crushLoginTips() }else{
dialogMsg(response.data.msg, {icon: 3}) //15.针对其他code报错情况
} return Promise.reject(response.data) //16.code不等于200, 直接页面具体逻辑就不执行了 } return initCustomOptions.reductDataFormat ? response.data : response //17.简洁数据结构功能 }, error => { // error.config && removePending(error.config) initCustomOptions.errorMessageShow && httpErrorStatusHandle(error) //18.针对status报错,处理错误状态码 return Promise.reject(error) // 错误继续返回给到具体页面 } ) // 无感刷新请求 async function refreshVisiterToken(config, isRefreshMiddleToken) { if (!isRefreshToken) { //24.首次401则进入,如果在游客登录失败时则isRefreshToken不为false,进入添加request队列 // pendingMap.clear() isRefreshToken = true //25.token已经重新请求了 await store.dispatch('userInfo/visitorLogin') // 26.游客登录,如果此处登录失败则退出进去到response.error? while (requestList.length > 0) { requestList.shift()() // shift()返回的是被删除的数组最后一个元素,按顺序执行回调 } overTimer++ isRefreshToken = false return service(config) } else { return new Promise((resolve) => { requestList.push(() => { settingToken(config) // 给config加上token resolve(service(config)) }) }) } } // 属于不需要刷新的请求 const isNoRefresh = function(url) { const list = [] if (list.includes(url)) return true return false } // 不需要携带token的api const whiteListNoTokenApi = function(url) { const list = ['/api/captchaImage', '/api/login'] return !list.includes(url) } // 设置token function settingToken(config) { const token = storage.token || storage.visitor || '' if (token && typeof window !== 'undefined') { config.headers.Authorization = `bearer ${token}` } } // 401错误无感处理 function handleError401(errorConfig) { const { config } = errorConfig if (overTimer >= 30) return // 21.重复请求超过上限30次 if (overTimer < 2 && !isNoRefresh(config.url)) { //22.非不需要刷新的url if (!storage.token) { // 23.如果是原本没有token,则刷新游客的token return refreshVisiterToken(config) } if (storage.token) { // 已经登录的用户,弹窗提示 crushLoginTips() } } } // 挤登处理 function crushLoginTips() { //14.弹出token失效提示框 if (showExpireLoginAlert) return showExpireLoginAlert = true dialogAlert('登录过期,或此账号已在其他设备登录,请重新登录!', { closeModal: false, showClose: false, callback: function(cb) { Router.push({name: 'login'}) cb() showExpireLoginAlert = false } }) } /** * 生成唯一的每个请求的唯一key * @param {*} config * @returns */ // function getPendingKey(config) { // let {url, method, params, data} = config // if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象 // return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&') // } /** * 处理异常 * @param {*} error */ function httpErrorStatusHandle(error) { // 处理被取消的请求 // if (axios.isCancel(error)) return console.error('请求的重复请求:' + error.message) let message = '' if (error && error.response) { switch (error.response.status) { case 302: message = '接口重定向了'; break case 400: message = '参数不正确'; break // case 401: message = '您未登录,或者登录已经超时,请先登录'; break // 401不拦截,让外部自己处理 case 401: //20.针对401情况特殊处理 handleError401(error) break case 403: message = '您没有权限操作'; break case 404: message = `请求地址出错: ${error.response.config.url}`; break // 在正确域名下 case 408: message = '请求超时'; break case 409: message = '系统已存在相同数据'; break case 500: message = '服务器内部错误'; break case 501: message = '服务未实现'; break case 502: message = '网关错误'; break case 503: message = '服务不可用'; break case 504: message = '服务暂时无法访问,请稍后再试'; break case 505: message = 'HTTP版本不受支持'; break default: message = '异常问题,请联系管理员'; break } } if (error.message.includes('timeout')) message = '网络请求超时' if (error.message.includes('Network')) message = window.navigator.onLine ? '服务端异常' : '您断网了' dialogMsg(message, { icon: 3 }) } /** * 储存每个请求的唯一cancel回调, 以此为标识 * @param {*} config */ // function addPending(config) { // const pendingKey = getPendingKey(config) // config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => { // // 如果当前的请求是首次请求,那就把key添加到map中存储 // if (!pendingMap.has(pendingKey)) { // pendingMap.set(pendingKey, cancel) // } // }) // } /** * 删除重复的请求 * @param {*} config */ // function removePending(config) { // const pendingKey = getPendingKey(config) // if (pendingMap.has(pendingKey)) { // const cancelToken = pendingMap.get(pendingKey) // // 传入 pendingKey主要的作用是,让axios在错误的时候通过catch可以捕获到错误信息 // // 用于后续,使用者可以快速定位是哪个请求导致的 // cancelToken(pendingKey) // pendingMap.delete(pendingKey) // } // } export default service