有时候要根据项目的具体需求从新封装http请求。 最近在公司作一个商城项目,由于我也是vue小白,在网上参考了不少资料,才把需求搞定。 以个人项目为例,需求:vue
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。这里主要讲第一种。ios
设置 request 和 response 拦截器。为了减轻服务器的压力,发起请求的时候先获取服务器状态,储存在 localStorage,10分钟内若是再有请求,再也不获取状态。 在request 拦截器中检测服务器是否运行,是否有 access_token
,没有就跳转到登陆页面。 最重要的是,实现 access_token
过时时,刷新token重发请求,这个须要在 response 拦截器中设置。element-ui
服务器生成 token 的过程当中,会有两个时间,一个是 token 失效时间(access_token
过时时间),一个是 token 刷新时间(refresh_token
过时时间)。refresh_token
过时时间确定比 access_token
过时时间要长,当 access_token
过时时,能够用 refresh_token
刷新 token。json
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();
});
}
复制代码
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 实例(这里主要是最复杂的请求)axios
request 拦截器:api
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回调中处理。session
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 => {
//.....
})
复制代码
以上封装只是针对这个项目的需求,但愿能对你有所帮助app