axios封装
// 服务层 , import默认会找该目录下index.js的文件,这个可能有小伙伴不知道 // 可以去了解npm的引入和es6引入的理论概念 import axiosPlugin from "./server"; Vue.use(axiosPlugin);
对axios的封装(AXIOS:index.js) import axios from "axios"; import qs from "qs"; import { Message } from "element-ui"; import router from "../router"; const Axios = axios.create({ baseURL: "/", // 因为我本地做了反向代理 timeout: 10000, responseType: "json", withCredentials: true, // 是否允许带cookie这些 headers: { "Content-Type": "application/x-www-form-urlencoded;charset=utf-8" } }); //POST传参序列化(添加请求拦截器) Axios.interceptors.request.use( config => { // 在发送请求之前做某件事 if ( config.method === "post" || config.method === "put" || config.method === "delete" ) { // 序列化 config.data = qs.stringify(config.data); } // 若是有做鉴权token , 就给头部带上token if (localStorage.token) { config.headers.Authorization = localStorage.token; } return config; }, error => { Message({ // 饿了么的消息弹窗组件,类似toast showClose: true, message: error, type: "error.data.error.message" }); return Promise.reject(error.data.error.message); } ); //返回状态判断(添加响应拦截器) Axios.interceptors.response.use( res => { //对响应数据做些事 if (res.data && !res.data.success) { Message({ // 饿了么的消息弹窗组件,类似toast showClose: true, message: res.data.error.message.message ? res.data.error.message.message : res.data.error.message, type: "error" }); return Promise.reject(res.data.error.message); } return res; }, error => { // 用户登录的时候会拿到一个基础信息,比如用户名,token,过期时间戳 // 直接丢localStorage或者sessionStorage if (!window.localStorage.getItem("loginUserBaseInfo")) { // 若是接口访问的时候没有发现有鉴权的基础信息,直接返回登录页 router.push({ path: "/login" }); } else { // 若是有基础信息的情况下,判断时间戳和当前的时间,若是当前的时间大于服务器过期的时间 // 乖乖的返回去登录页重新登录 let lifeTime = JSON.parse(window.localStorage.getItem("loginUserBaseInfo")).lifeTime * 1000; let nowTime = (new Date()).getTime(); // 当前时间的时间戳 if (nowTime > lifeTime) { Message({ showClose: true, message: "登录状态信息过期,请重新登录", type: "error" }); router.push({ path: "/login" }); } } // 下面是接口回调的status ,因为我做了一些错误页面,所以都会指向对应的报错页面 if (error.response.status === 403) { router.push({ path: "/error/403" }); } if (error.response.status === 500) { router.push({ path: "/error/500" }); } if (error.response.status === 502) { router.push({ path: "/error/502" }); } if (error.response.status === 404) { router.push({ path: "/error/404" }); } // 返回 response 里的错误信息 return Promise.reject(error.data.error.message); } ); // 对axios的实例重新封装成一个plugin ,方便 Vue.use(xxxx) export default { install: function(Vue, Option) { Object.defineProperty(Vue.prototype, "$http", { value: Axios }); } }; 路由钩子的调整(Router:index.js) import Vue from "vue"; import Router from "vue-router"; import layout from "@/components/layout/layout"; // 版块有点多,版块独立路由管理,里面都是懒加载引入 import customerManage from "./customerManage"; // 客户管理 import account from "./account"; //登录 import adManage from "./adManage"; // 广告管理 import dataStat from "./dataStat"; // 数据统计 import logger from "./logger"; // 日志 import manager from "./manager"; // 管理者 import putonManage from "./putonManage"; // 投放管理 import error from "./error"; // 服务端错误 import { Message } from "element-ui"; Vue.use(Router); // 请跳过这一段,看下面的 const router = new Router({ hashbang: false, mode: "history", routes: [ { path: "/", redirect: "/adver", component: layout, children: [ ...customerManage, ...adManage, ...dataStat, ...putonManage, ...manager, ...logger ] }, ...account, ...error ] }); // 路由拦截 // 差点忘了说明,不是所有版块都需要鉴权的 // 所以需要鉴权,我都会在路由meta添加添加一个字段requireLogin,设置为true的时候 // 这货就必须走鉴权,像登录页这些不要,是可以直接访问的!!! router.beforeEach((to, from, next) => { if (to.matched.some(res => res.meta.requireLogin)) { // 判断是否需要登录权限 if (window.localStorage.getItem("loginUserBaseInfo")) { // 判断是否登录 let lifeTime = JSON.parse(window.localStorage.getItem("loginUserBaseInfo")).lifeTime * 1000; let nowTime = (new Date()).getTime(); // 当前时间的时间戳 if (nowTime < lifeTime) { next(); } else { Message({ showClose: true, message: "登录状态信息过期,请重新登录", type: "error" }); next({ path: "/login" }); } } else { // 没登录则跳转到登录界面 next({ path: "/login" }); } } else { next(); } }); export default router; axios可配置的一些选项,其他的具体看官网说明哈 export default { // 请求地址 url: "/user", // 请求类型 method: "get", // 请根路径 baseURL: "http://www.mt.com/api", // 请求前的数据处理 transformRequest: [function(data) {}], // 请求后的数据处理 transformResponse: [function(data) {}], // 自定义的请求头 headers: { "x-Requested-With": "XMLHttpRequest" }, // URL查询对象 params: { id: 12 }, // 查询对象序列化函数 paramsSerializer: function(params) {}, // request body data: { key: "aa" }, // 超时设置s timeout: 1000, // 跨域是否带Token withCredentials: false, // 自定义请求处理 adapter: function(resolve, reject, config) {}, // 身份验证信息 auth: { uname: "", pwd: "12" }, // 响应的数据格式 json / blob /document /arraybuffer / text / stream responseType: "json", // xsrf 设置 xsrfCookieName: "XSRF-TOKEN", xsrfHeaderName: "X-XSRF-TOKEN", // 下传和下载进度回调 onUploadProgress: function(progressEvent) { Math.round(progressEvent.loaded * 100 / progressEvent.total); }, onDownloadProgress: function(progressEvent) {}, // 最多转发数,用于node.js maxRedirects: 5, // 最大响应数据大小 maxContentLength: 2000, // 自定义错误状态码范围 validateStatus: function(status) { return status >= 200 && status < 300; }, // 用于node.js httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ keepAlive: true }), // 用于设置跨域请求代理 proxy: { host: "127.0.0.1", port: 8080, auth: { username: "aa", password: "2123" } }, // 用于取消请求 cancelToken: new CancelToken(function(cancel) {}) };
import axios, { AxiosRequestConfig } from "axios"; const pending = {}; const CancelToken = axios.CancelToken; const removePending = (key: string, isRequest = false) => { if (Reflect.get(pending, key) && isRequest) { Reflect.get(pending, key)("取消重复请求"); } Reflect.deleteProperty(pending, key); }; const getRequestIdentify = (config: AxiosRequestConfig, isReuest = false) => { let url = config.url; const suburl = config.url?.substring(1, config.url?.length) ?? ""; if (isReuest) { url = config.baseURL + suburl; } return config.method === "get" ? encodeURIComponent(url + JSON.stringify(config.params)) : encodeURIComponent(config.url + JSON.stringify(config.data)); }; // 创建一个AXIOS实例 const service = axios.create({ baseURL: import.meta.env.VITE_BASE_API, // url = base url + request url // withCredentials: true, // send cookies when cross-domain requests timeout: 16000, // 请求超时 }); // 请求拦截器 service.interceptors.request.use( (config: AxiosRequestConfig) => { // 拦截重复请求(即当前正在进行的相同请求) const requestData = getRequestIdentify(config, true); removePending(requestData, true); config.cancelToken = new CancelToken((c: any) => { Reflect.set(pending, requestData, c); }); // 请求发送前的预处理(如:获取token等) // if (store.getters.token) { // // let each request carry token // // ['X-AUTH-TOKEN'] is a custom headers key // // please modify it according to the actual situation // config.headers['X-AUTH-TOKEN'] = getToken() // } return config; }, (error: any) => { // do something with request error console.log(error); // for debug return Promise.reject(error); } ); // response interceptor service.interceptors.response.use( (response: { config: AxiosRequestConfig; data: any }) => { // 把已经完成的请求从 pending 中移除 const requestData = getRequestIdentify(response.config); removePending(requestData); const res = response.data; return res; }, (error: { message: string; config: { showLoading: any }; response: { status: any }; request: any; }) => { console.log(error.message); if (error) { if (error.response) { switch (error.response.status) { case 400: error.message = "错误请求"; break; case 401: error.message = "未授权,请重新登录"; break; default: error.message = `连接错误${error.response.status}`; } const errData = { code: error.response.status, message: error.message, }; console.log("统一错误处理: ", errData); } else if (error.request) { console.log("统一错误处理: ", "网络出错,请稍后重试"); } } return Promise.reject(error); } ); export default service;
import { Dialog } from "vant"; import "vant/es/dialog/style"; import { Toast } from "vant"; import "vant/es/toast/style"; import axios, { AxiosRequestConfig } from "axios"; const pending = {}; const CancelToken = axios.CancelToken; const removePending = (key: string, isRequest = false) => { if (Reflect.get(pending, key) && isRequest) { Reflect.get(pending, key)("取消重复请求"); } Reflect.deleteProperty(pending, key); }; const getRequestIdentify = (config: AxiosRequestConfig, isReuest = false) => { let url = config.url; const suburl = config.url?.substring(1, config.url?.length) ?? ""; if (isReuest) { url = config.baseURL + suburl; } return config.method === "get" ? encodeURIComponent(url + JSON.stringify(config.params)) : encodeURIComponent(config.url + JSON.stringify(config.data)); }; // 创建一个AXIOS实例 const service = axios.create({ baseURL: import.meta.env.VITE_BASE_API, // url = base url + request url // withCredentials: true, // send cookies when cross-domain requests timeout: 16000, // 请求超时 }); // 请求拦截器 service.interceptors.request.use( (config: AxiosRequestConfig) => { // 拦截重复请求(即当前正在进行的相同请求) const requestData = getRequestIdentify(config, true); removePending(requestData, true); config.cancelToken = new CancelToken((c: any) => { Reflect.set(pending, requestData, c); }); // 是否开启loading if (config.showLoading) { Toast.loading({ duration: 0, mask: true, forbidClick: true, message: "加载中...", loadingType: "spinner", }); } // 请求发送前的预处理(如:获取token等) // if (store.getters.token) { // // let each request carry token // // ['X-AUTH-TOKEN'] is a custom headers key // // please modify it according to the actual situation // config.headers['X-AUTH-TOKEN'] = getToken() // } return config; }, (error: any) => { // do something with request error console.log(error); // for debug Toast.loading({ message: "网络出错,请重试", duration: 1500, type: "fail", }); return Promise.reject(error); } ); // response interceptor service.interceptors.response.use( (response: { config: AxiosRequestConfig; data: any }) => { // 把已经完成的请求从 pending 中移除 const requestData = getRequestIdentify(response.config); removePending(requestData); if (response.config.showLoading) { Toast.clear(); } const res = response.data; return res; }, (error: { message: string; config: { showLoading: any }; response: { status: any }; request: any; }) => { console.log(error.message); if (error) { if (error.config && error.config.showLoading) { Toast.clear(); } if (error.response) { switch (error.response.status) { case 400: error.message = "错误请求"; break; case 401: error.message = "未授权,请重新登录"; break; default: error.message = `连接错误${error.response.status}`; } const errData = { code: error.response.status, message: error.message, }; console.log("统一错误处理: ", errData); Dialog({ title: "提示", message: errData.message || "Error" }); } else if (error.request) { Toast.loading({ message: "网络出错,请稍后重试", duration: 1500, type: "fail", }); } } return Promise.reject(error); } ); export default service;
import axios from 'axios' import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import { setToken, getToken, getRefreshToken, removeToken, removeConferenceToken, getCompany, accountKick, isAccountBeKicked } from '@/utils/auth' import { message } from 'ant-design-vue'; import { loginApi } from './account'; const instance = axios.create({ timeout: 60000, }); const goLogin = ()=>{ removeToken() removeConferenceToken() } instance.interceptors.request.use(function (config) { if(config.url.indexOf('/sms/send/code') > 0){ //处理win端跳到忘记密码页面时,如果携带错误token会报错 config.headers['Authorization'] = ''; }else{ config.headers['Authorization'] = getToken() ? `Bearer ${getToken()}` : ''; } // console.error('全局请求拦截器 error', config); // 添加 token // 在发送请求之前做些什么 if(config.url.indexOf('/class') > -1) { // class-pro 添加 remote-host 后端要求 try { config.headers['remote-host'] = JSON.parse(getCompany())?.url } catch (error) { } } return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 响应拦截器 instance.interceptors.response.use( // 因为我们接口的数据都在res.data下,所以我们直接返回res.data (res) => { const code = res.data.code || res.status; if(code == 200 || code == 0){ return res.data.data }else if( // token 过期 (code == 15001) && (res as any).config.url.indexOf('login/oauth2/token') < 0 ) { return refreshToken(res) }else if(code == 401 && !_checkAccountKick((res as any).config.url) || code == 402){ // token 重置 (用户重复登录) accountKick(); return Promise.reject(401); }else if(code == 11011 && (res as any).config.url.indexOf('login/oauth2/token') > 0){ return Promise.reject(res.data); }else{ message.destroy(); res.data && res.data.msg && message.warning(res.data.msg); return Promise.reject(res.data); } }, (error: any) => { const status = error?.response?.status; console.error('全局响应拦截器 error', error); const errorJSON = error.toJSON(); // 登录过期 if( status == 401 && errorJSON?.config?.url && errorJSON.config.url.indexOf('login/oauth2/token') < 0 ) { return refreshToken(errorJSON) }; if (error.message && error.message === 'Network Error') { if(isAccountBeKicked()) return; message.destroy(); return message.error('网络连接异常,请检查后重试') } return Promise.reject(error); }, ); // 不包含互踢检测的API接口 let _checkAccountKick = (url: string)=> { let status = false; let unIncludeKickApi = [ 'login/oauth2/token', 'ybshare-signal/share' ]; for (let index = 0; index < unIncludeKickApi.length; index++) { const element = unIncludeKickApi[index]; if(url.includes(element)){ status = true; break } } return status }; let isRefreshing = false, requests:any = []; function refreshToken (res: AxiosResponse) { const config = res.config; if(!isRefreshing) { // 改变标记状态 isRefreshing = true const params = { refresh_token: getRefreshToken(), "grant_type": "refresh_token" } return loginApi(params) .then((loginRes: any) => { const code = loginRes.code; if(loginRes.accessToken) { setToken(loginRes.accessToken) // 刷新完成 后执行存储的 请求 setTimeout(() => { requests.forEach((cb:any) => cb()); requests = []; },100) return instance(config) } else if(code == 401) { goLogin() console.error('刷新token失败了---') // TODO: 向上抛 异常 退出登录 isRefreshing = false return Promise.reject(res.data) } }) .catch(() => { goLogin() console.error('刷新token失败了---') // TODO: 向上抛 异常 退出登录 isRefreshing = false return Promise.reject(res.data) }) .finally(() => { // 请求回 token 处理完成 一定要改变标记状态 否则会一直重新请求 token isRefreshing = false }) } else { // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行 return new Promise(resolve => { requests.push(() => { config.headers['Authorization'] = getToken() ? `Bearer ${getToken()}` : ''; resolve(instance(config)) }) }) } } export default instance;
import axios from 'axios' import qs from 'qs' // create an axios instance const Request = axios.create({ // withCredentials: true, // send cookies when cross-domain requests timeout: 10000, // request timeout 10000 }) // request interceptor Request.interceptors.request.use( config => { const conferenceToken = getConferenceToken() const channelToken = getChannelToken() let conferenceNo = '' const searchStr = window.location.search if (searchStr) { const parseQuery = qs.parse(searchStr, { ignoreQueryPrefix: true }) conferenceNo = parseQuery.conferenceNo } // do something before request is sent config.headers['Accept-Language'] = getAcceptLanguage() // 多语言 config.headers["Authorization"] = getToken() ? `Bearer ${getToken()}` : '' config.headers["deviceId"] = getUuid() || '' config.headers["X-Conference-Token"] = conferenceToken || '' config.headers["X-Channel-Token"] = channelToken || '' config.headers["X-Conference-No"] = conferenceNo || '' /** */ const url = config.url if (url.indexOf('/middle-auth') > -1) { config.url = middleServerUrl + url } else { config.url = meetServerUrl + url } return config }, error => { // do something with request error return Promise.reject(error) } ) let isRefreshing = false let subscribers = [] function onAccessTokenFetched(newToken) { subscribers.forEach((callback) => { callback(newToken) }) subscribers = [] } function addSubscriber(callback) { subscribers.push(callback) } // response interceptor Request.interceptors.response.use( response => { const resData = response.data const { url, isBlobType } = response.config // 优化,没有token的情况 if (resData.code === 401 && !url.includes('login/oauth2/token')) { // console.log('触发了401--------') if (!isRefreshing) { isRefreshing = true // 将刷新token的方法放在vuex中处理了, 可见下面区块代码 oauthLogin({ grant_type: 'refresh_token', client_id: getClientId(), client_secret: getClientSecret(), refresh_token: getRefreshToken() }).then((res) => { // console.log('restoken调用成功了----') // console.log(res) setToken(res.accessToken) onAccessTokenFetched(res.accessToken) isRefreshing = false }).catch(() => { // 刷新token报错了 console.error('刷新token失败了---') isRefreshing = false }) } // 将其他接口缓存起来 -- 这个Promise函数很关键 const retryOriginalRequest = new Promise((resolve) => { // 这里是将其他接口缓存起来的关键, 返回Promise并且让其状态一直为等待状态, // 只有当token刷新成功后, 就会调用通过addSubscriber函数添加的缓存接口, // 此时, Promise的状态就会变成resolve addSubscriber((newToken) => { // console.log('即将重新发起请求---') response.config.headers.Authorization = `Bearer ${newToken}`; if (url.indexOf('/middle-auth') > -1) { response.config.url = response.config.url.replace(middleServerUrl, '') } else { response.config.url = response.config.url.replace(meetServerUrl, '') } // console.log(response.config) // 用重新封装的config去请求, 就会将重新请求后的返回 resolve(Request(response.config)) }); }); return retryOriginalRequest; } if (isBlobType) { return resData } if (resData.code === 200) { // 请求成功 return resData.data } else { // 请求失败 return Promise.reject(response.data) } }, error => { return Promise.reject(error) } ) export default Request