有时候要根据项目的具体需求重新封装http请求。 最近在公司做一个商城项目,因为我也是vue小白,在网上参考了很多资料,才把需求搞定。 以我的项目为例,需求:
- 所有对服务器的请求先访问 '/GetMaintenanceState'接口,如果服务器正在维护就拦截请求,跳转到维护页面并显示维护信息;如果服务器正在运行,再发起请求。
- 需要登录后发送的请求:(登录时请求接口'Token',将
access_token
和refresh_token
保存在localStorage),每次请求都要带自定义请求头 Authorization。 access_token
过期后,用refresh_token
重新请求刷新token,如果refresh_token
过期跳转到登录页面重新获取token。- 因为我们的所有接口除了网络问题,返回的
status
都是200(OK),请求成功IsSuccess
为true
,请求失败IsSuccess
为false
。请求失败会返回响应的错误码ErrorTypeCode
,10003 ——access_token
不存在或过期,10004 ——refresh_token
不存在或过期。
思路
有两种请求,一种需要Token,一种不需要Token。这里主要讲第一种。
设置 request 和 response 拦截器。为了减轻服务器的压力,发起请求的时候先获取服务器状态,储存在 localStorage,10分钟内如果再有请求,不再获取状态。 在request 拦截器中检测服务器是否运行,是否有 access_token
,没有就跳转到登录页面。 最重要的是,实现 access_token
过期时,刷新token重发请求,这个需要在 response 拦截器中设置。
服务器生成 token 的过程中,会有两个时间,一个是 token 失效时间(access_token
过期时间),一个是 token 刷新时间(refresh_token
过期时间)。refresh_token
过期时间肯定比 access_token
过期时间要长,当 access_token
过期时,可以用 refresh_token
刷新 token。
封装获取服务器维护状态的函数
import axios from 'axios';
function getUrl(url) {
if (url.indexOf(baseUrl) === 0) {
return url;
}
url = url.replace(/^\//, '');
url = baseUrl + '/' + url;
return url;
}
function checkMaintenance() {
let status = {};
let url = getUrl('/GetMaintenanceState');
return axios({
url,
method: 'get'
})
.then(res => {
if (res.data.IsSuccess) {
status = {
IsRun: res.data.Value.IsRun, // 服务器是否运行
errMsg: res.data.Value.MaintenanceMsg // 维护时的信息
};
// localStorageSet 为封装好的方法,储存字段的同时,储存时间戳
localStorageSet('maintenance', status);
// 传递获取的结果
return Promise.resolve(status);
}
})
.catch(() => {
return Promise.reject();
});
}
复制代码
封装刷新token的函数
function getRefreshToken() {
let url = getUrl('/Token');
// 登录时已经获取token储存在localStorage中
let token = JSON.parse(localStorage.getItem('token'));
return axios({
url,
method: 'post',
data: 'grant_type=refresh_token&refresh_token=' + token.refresh_token,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
// 开发者密钥
Authorization: 'Basic xxxxxxxxxxx'
}
})
.then(res => {
if (res.data.IsSuccess) {
var token_temp = {
access_token: res.data.access_token,
refresh_token: res.data.refresh_token
};
localStorage.setItem('token', JSON.stringify(token_temp));
// 将access_token储存在session中
sessionStorage.setItem('access_token', res.data.access_token);
return Promise.resolve();
}
})
.catch(() => {
return Promise.reject();
});
}
复制代码
设置拦截器
因为要封装不同需求的请求,最好创建 axios 实例(这里主要是最复杂的请求)
request 拦截器:
import router from '../router';
import { Message } from 'element-ui';
const instance = axios.create();
instance.interceptors.request.use(
config => {
// 获取储存中本地的维护状态,localStorageGet方法,超过10分钟返回false
let maintenance = localStorageGet('maintenance');
// 如果本地不存在 maintenance 或 获取超过10分钟,重新获取
if (!maintenance) {
return checkMaintenance()
.then(res => {
if (res.IsRun) {
// 获取session中的access_token
let access_token = sessionStorage.getItem('access_token');
// 如果不存在字段,则跳转到登录页面
if (!access_token) {
router.push({
path: '/login',
query: { redirect: router.currentRoute.fullPath }
});
// 终止这个请求
return Promise.reject();
} else {
config.headers.Authorization = `bearer ${access_token}`;
}
config.headers['Content-Type'] = 'application/json;charset=UTF-8';
// 这一步就是允许发送请求
return config;
} else {
// 如果服务器正在维护,跳转到维护页面,显示维护信息
router.push({
path: '/maintenance',
query: { redirect: res.errMsg }
});
return Promise.reject();
}
})
.catch(() => {
// 获取服务器运行状态失败
return Promise.reject();
});
} else { // 本地存在 maintenance
if (maintenance.IsRun) {
let access_token = sessionStorage.getItem('access_token');
if (!access_token) {
router.push({
path: '/login',
query: { redirect: router.currentRoute.fullPath }
});
return Promise.reject();
} else {
config.headers.Authorization = `bearer ${access_token}`;
}
config.headers['Content-Type'] = 'application/json;charset=UTF-8';
return config;
} else {
router.push({
path: '/maintenance',
query: { redirect: maintenance.errMsg }
});
return Promise.reject();
}
}
},
err => {
// err为错误对象,但是在我的项目中,除非网络问题才会出现
return Promise.reject(err);
}
);
复制代码
response 拦截器:
这只是针对我这个项目的情况,因为所有请求都是成功的,靠ErrorTypeCode错误码区分,所以在response回调中处理。
若是普通情况,token 过期返回状态码 401,应该中err回调中处理。
instance.interceptors.response.use(
response => {
// access_token不存在或过期
if (response.data.ErrorTypeCode === 10003) {
return getRefreshToken()
.then(() => {
// 重新设置
let access_token = sessionStorage.getItem('access_token');
config.headers.Authorization = `bearer ${access_token}`;
config.headers['Content-Type'] = 'application/json;charset=UTF-8';
// 重新请求
// 如果请求的时候refresh_token也过期
return instance(config).then(res => {
if (res.data.ErrorTypeCode === 10004) {
router.push({
path: '/login',
query: { redirect: router.currentRoute.fullPath }
});
return Promise.reject();
}
// 使响应结果省略data字段
return Promise.resolve(response.data);
});
})
.catch(() => {
// refreshtoken 获取失败就只能到登录页面
router.push({
path: '/login',
query: { redirect: router.currentRoute.fullPath }
});
return Promise.reject();
});
}
// refresh_token不存在或过期
if (response.data.ErrorTypeCode == 10004) {
router.push({
path: '/login',
query: { redirect: router.currentRoute.fullPath }
});
return Promise.reject();
}
// 使响应结果省略data字段
return response.data;
},
err => {
return Promise.reject(err);
}
);
复制代码
封装请求
function request({ url, method, Value = null }) {
url = getUrl(url);
method = method.toLowerCase() || 'get';
let obj = {
method,
url
};
if (Value !== null) {
if (method === 'get') {
obj.params = { Value };
} else {
obj.data = { Value };
}
}
return instance(obj)
.then(res => {
return Promise.resolve(res);
})
.catch(() => {
Message.error('请求失败,请检查网络连接');
return Promise.reject();
});
}
// 向外暴露成员
export function get(setting) {
setting.method = 'GET';
return request(setting);
}
export function post(setting) {
setting.method = 'POST';
return request(setting);
}
复制代码
使用
import { post, get } from '@/common/network';
post({
url: '/api/xxxxx',
Value: {
GoodsName,
GoodsTypeId
}
}).then(res => {
//.....
})
复制代码
以上封装只是针对这个项目的需求,希望能对你有所帮助