原文出自:blog.csdn.net/halo1416/ar…
在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御XSRF等。所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护,直接推荐我们使用axios库。如果还对axios不了解的,可以移步axios文档。
安装及引用
安装
npm install axios; // 安装axios
引入
一般我会在项目的src目录中,新建一个server文件夹,然后在里面新建一个http.js和一个api.js文件。http.js文件用来封装我们的axios,api.js用来统一管理我们的接口。
http.js
import axios from 'axios';import store from '../store/index';import router from '../router/index';import { Toast } from 'vant';import Qs from 'qs';
axios的封装
基础配置
环境切换
我们的项目环境可能有开发环境、测试环境和生产环境。我们通过node的环境变量来匹配我们的默认的接口url前缀,后续会更新更好的方法。
处理重复请求
在实际开发中可能会出现重复请求的情况,所以这里也对这种情况做了一些拦截设置
基础配置
一些请求基础设置,包括请求超时、基地址、请求头的设置等
请求拦截器
这里我们主要是做了一个对重复请求的处理,和在请求头中携带了token
这里说一下token,一般是在登录完成之后,将用户的token通过localStorage或者cookie存在本地,然后用户每次在进入页面的时候(即在main.js中),会首先从本地存储中读取token,如果token存在说明用户已经登陆过,则更新vuex中的token状态。然后,在每次请求接口的时候,都会在请求的header中携带token,后台人员就可以根据你携带的token来判断你的登录是否过期,如果没有携带,则说明没有登录过。这时候或许有些小伙伴会有疑问了,就是每个请求都携带token,那么要是一个页面不需要用户登录就可以访问的怎么办呢?其实,你前端的请求可以携带token,但是后台可以选择不接收啊!
响应拦截器
响应拦截器很好理解,就是服务器返回给我们的数据,我们在拿到之前可以对他进行一些处理。例如上面的思想:如果后台返回的状态码是200,则正常返回数据,否则的根据错误的状态码类型进行一些我们需要的错误,其实这里主要就是进行了错误的统一处理和没登录或登录过期后调整登录页的一个操作。
响应结果处理
代码太长不好截图,只能将代码贴上
http.js
/** * 处理响应结果 * @param {Object} response 响应结果 */
const handleStatus = (response) => {
const status = response.status || -1000; // -1000 自己定义,连接错误的status
if ((status >= 200 && status < 300) || status === 304) {// 基本揽括了响应成功的状态码
// 正常状态码,直接返回数据
return response.data
} else {
let errorInfo = '';
switch (status) {
case -1:
errorInfo = '远程服务响应失败,请稍后重试';
break;
case 400:
errorInfo = '400:错误请求';
break;
case 401:
errorInfo = '401:请求身份验证';
break;
case 403:
errorInfo = '403:服务器拒绝访问';
break;
case 404:
errorInfo = '404:资源不存在';
break;
case 405:
errorInfo = '405:请求方法未允许';
break;
case 408:
errorInfo = '408:请求超时'
break;
case 500:
errorInfo = '500:服务器遇到错误,无法完成请求';
break;
case 501:
errorInfo = '501:未实现';
break;
case 502:
errorInfo = '502:无效网关';
break;
case 503:
errorInfo = '503:服务不可用';
break;
case 504:
errorInfo = '503:网关超时';
break;
default:
errorInfo = `连接错误`;
}
Toast({
message: errorInfo,
duration: 1500,
forbidClick: true,
// 关闭时的回调函数
onClose: () => {
if (status == '401') {
//未登录,跳转登录页面,并且携带当前页面的路径,以便在登录返回后当前页面
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
} else if (status == '403') {
// token过期,清除本地token和清空vuex中的token,再跳转登录页面
localStorage.removeItem('token');
store.commit('loginSuccess', null);
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
}
}
});
return {
errorInfo,
status
}
}}
全部代码整理
最后将全部代码贴上,方便一览无余
import axios from 'axios';
import store from '../store/index';
import router from '../router/index';
import { Toast } from 'vant';
import Qs from 'qs';
// 环境切换
var baseURL = '';
if (process.env.NODE_ENV == 'development') {
baseURL = 'https://www.baidu.com';}else if (process.env.NODE_ENV == 'debug') {
baseURL = 'https://www.ceshi.com';}else if (process.env.NODE_ENV == 'production') {
baseURL = 'https://www.production.com';}//正在请求的请求url数组let pending = [];/** * 是否已经在请求队列中 * @param {String} url 当前请求路径 */const isPending = url => pending.includes(url);/** * 移除完成的请求 * @param {String} url 当前请求路径 */const removePending = url => { let index = pending.findIndex(item => item === url); pending.splice(index, 1);}/** * 处理响应结果 * @param {Object} response 响应结果 */const handleStatus = (response) => { const status = response.status || -1000; // -1000 自己定义,连接错误的status if ((status >= 200 && status < 300) || status === 304) {// 基本揽括了响应成功的状态码 // 正常状态码,直接返回数据 return response.data } else { let errorInfo = ''; switch (status) { case -1: errorInfo = '远程服务响应失败,请稍后重试'; break; case 400: errorInfo = '400:错误请求'; break; case 401: errorInfo = '401:请求身份验证'; break; case 403: errorInfo = '403:服务器拒绝访问'; break; case 404: errorInfo = '404:资源不存在'; break; case 405: errorInfo = '405:请求方法未允许' break; case 408: errorInfo = '408:请求超时' break; case 500: errorInfo = '500:服务器遇到错误,无法完成请求'; break; case 501: errorInfo = '501:未实现'; break; case 502: errorInfo = '502:无效网关'; break; case 503: errorInfo = '503:服务不可用' break; case 504: errorInfo = '503:网关超时' break; default: errorInfo = `连接错误` } Toast({ message: errorInfo, duration: 1500, forbidClick: true, // 关闭时的回调函数 onClose: () => { if (status == '401') { //未登录,跳转登录页面,并且携带当前页面的路径,以便在登录返回后当前页面 router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }) } else if (status == '403') { // token过期,清除本地token和清空vuex中的token,再跳转登录页面 localStorage.removeItem('token'); store.commit('loginSuccess', null); router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }) } } }); return { errorInfo, status } }}// 实例化的axios对象const instance = axios.create({ timeout: 1000 * 30, //设置请求超时 withCredentials: true, //允许请求时携带cookie的配置 baseURL: baseURL, //设置基地址 // 允许在向服务器发送前,修改请求数据 transformRequest: [data => { // 对data进行任意转换处理 return data }], // 在传递给then/catch前,允许修改响应数据 transformResponse: [data => { // 对data进行任意转换处理 return JSON.parse(data) }]})/** * 请求拦截:主要是做了一个对重复请求的处理,和在请求头中携带了token */// interceptors:添加拦截器instance.interceptors.request.use( config => { console.log('=========config', config); // 判断这个请求的url是否存在于正在请求的数组中,如果存在,则拦截并抛出错误 if (!config.hearders.noIntercept && isPending(config.url)) { console.log(config.url); // 终止请求,抛出错误 return Promise.reject(new Error('重复请求已被拦截')); } // 不存在重复请求,则把当前请求的url放到正在请求的数组中 pending.push(config.url); // 在发送请求之前根据开发实际情况做处理,非必要 config.hearders = Object.assign(config.method === 'get' ? { 'Accept': 'application/json', 'Content-Type': 'application/json; charset=UTF-8' } : { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, config.hearders); if (config.method === 'post') { const contentType = config.headers['Content-Type']; // 根据Content-Type转换data格式 if (contentType) { if (contentType.includes('multipart')) { // 类型 'multipart/form-data;' // config.data = data; } else if (contentType.includes('json')) { // 类型 'application/json;' // 服务器收到的raw body(原始数据) "{name:"nowThen",age:"18"}"(普通字符串) config.data = JSON.stringify(config.data); } else { // 类型 'application/x-www-form-urlencoded;' // 服务器收到的raw body(原始数据) name=nowThen&age=18 config.data = Qs.stringify(config.data); } } }; // 请求头带上token var token = store.state.token; token && (config.hearders['token'] = token); // 把配置返回出去 return Promise.resolve(config) }, error => { return Promise.reject(error) });/** * 响应拦截: */instance.interceptors.response.use( response => { console.log('============response', response) // 从数组中移除已经完成的请求 removePending(response.config.url); // 处理响应结果 return Promise.resolve(handleStatus(response)) }, error => { // 从数组中移除已经完成的请求 removePending(response.config.url); //对响应错误做处理,根据实际数据结构改动 if (error.response) { return Promise.reject(handleStatus(error.response)) } else if (error.code == "ECONNABORTED" && error.message.indexOf("timeout") != -1) { Toast({ message: '请求超时', duration: 1500, forbidClick: true, }) return Promise.reject({ msg: "请求超时" }); } else { return Promise.reject({}); } });export default instance;