一、说一下项目流程
1、项目立项;
2、BA(business analyst 一般指产品经理)整理项目需求,定项目周期;
3、需求评审会(过项目需求);
4、后端设计数据库,ui设计原型图;
5、ui评审;
6、前端静态页面开发(mock数据测试),后端做接口,测试做测试用例;
7、前后端联调;
8、测试;
9、项目上线;
二、你是怎么对axios进行二次封装的,有什么作用?
1、通用配置封装
创建axios实例,设置baseURL(多个URL中共同的前缀)、timeout(请求超时)
const Axios = axios.create({ //创建axios实例 baseURL: '', timeout: 5000 })
2、添加请求拦截器
因为不是每个请求都需要拦截的,例如获取验证码,我们就可以把所有不需拦截的请求地址放在一个数组中;
需要拦截的话就应在请求发送前进行必要操作处理
通过数组的includes方法判断请求地址是否要拦截,要拦截就判断是否有token,一般将token存储在状态管理库里,有token就将token添加到请求头上,否则就返回一个错误状态的promise
const nointerceptUrls = [ //放入不需拦截的请求URL ] Axios.interceptors.request.use(config => { //判断是否需要拦截 if(nointerceptUrls.includes(config.url)) { return config } // 是否有token,有就注入到请求头 let token = useCommonStore().getToken if(!!token) { config.headers['X-Token'] = token return config } return Promise.reject({code: 999, message: '用户的token数据丢失'}) })
3、添加响应拦截器
同理,响应拦截器也是一样的功能,只是在请求得到响应后,对响应体的一些处理,通常是数据统一处理等,也常来判断登录失效等。
Axios.interceptors.response.use(response => { const {code} = response.data if(code === 1234) { //token过期了,需要刷新token return refreshToken().then() } })
4、async + await支持 二次封装axios
//封装 async+await 支持 export default function Ajax(req) { if(!req || req.url) throw new TypeError('请求对象没有传递或url没传') return new Promise(reslove => { Axios.request({ url: req.url, method: req.method || 'POST', data: req.data || {}, params: req.params || {} }).then( ({data}) => { resolve(data) }).catch(e => { resolve(e) }) }) }
三、用户token失效你是怎么处理的?(无感刷新token)
方式一
在请求拦截器中,根据接口响应的过期时间判断token是否过期,在token过期前进行刷新;缺点就是后端需要提供token过期时间字段,需要过期时间与本地时间进行判断,如果计算机时间被篡改,拦截就会失败。
方式二 (性能最优)
在响应拦截器中,根据返回状态判断token是否过期,过期则刷新token。
具体处理步骤:
1、创建一个flag(isRefreshing)来判断是否刷新中;
2、创建一个数组(retryRequests)来保存需要重新发起的请求;
3、判断token过期
(1)isRefreshing = false情况下发起刷新token的请求,刷新token后遍历retryRequests数组
(2)isRefreshing = true 表示正在刷新token,返回一个pending状态的Promise,并把请求信息保存到retryRequests中
// 定义一个flag 判断是否刷新Token中
let isRefreshing = false;
// 保存需要重新发起请求的队列
let retryRequests = [];
请求拦截器
Axios.interceptors.request.use(async function(config) { Store.commit("startLoading"); const userInfo = UserUtil.getLocalInfo(); if (userInfo) { //业务需要把Token信息放在 params 里面,一般来说都是放在 headers里面 config.params = Object.assign(config.params ? config.params : {}, { appkey: userInfo.AppKey, token: userInfo.Token }); } return config; });
响应后拦截
Axios.interceptors.response.use( async function(response) { Store.commit("finishLoading"); const res = response.data; if (res.errcode == 0) { return Promise.resolve(res); } else if ( res.errcode == 30001 || res.errcode == 40001 || res.errcode == 42001 || res.errcode == 40014 ) { // 需要刷新Token 的状态 30001 40001 42001 40014 // 拿到本次请求的配置 let config = response.config; // 进入登录页面的不做刷新Token 处理 if (Router.currentRoute.path !== "/login") { if (!isRefreshing) { // 改变flag状态,表示正在刷新Token中 isRefreshing = true; // 刷新Token return Store.dispatch("user/refreshToken") .then(res => { // 设置刷新后的Token config.params.token = res.Token; config.params.appkey = res.AppKey; // 遍历执行需要重新发起请求的队列 retryRequests.forEach(cb => cb(res)); // 清空队列 retryRequests = []; return Instance.request(config); }) .catch(() => { retryRequests = []; Message.error("自动登录失败,请重新登录"); const code = Store.state.user.info.CustomerCode || ""; // 刷新Token 失败 清空缓存的用户信息 并调整到登录页面 Store.dispatch("user/logout"); Router.replace({ path: "/login", query: { redirect: Router.currentRoute.fullPath, code: code } }); }) .finally(() => { // 请求完成后重置flag isRefreshing = false; }); } else { // 正在刷新token,返回一个未执行resolve的promise // 把promise 的resolve 保存到队列的回调里面,等待刷新Token后调用 // 原调用者会处于等待状态直到 队列重新发起请求,再把响应返回,以达到用户无感知的目的(无痛刷新) return new Promise(resolve => { // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行 retryRequests.push(info => { // 将新的Token重新赋值 config.params.token = info.Token; config.params.appkey = info.AppKey; resolve(Instance.request(config)); }); }); } } return new Promise(() => {}); } else { return Promise.reject(res); } }, function(error) { let err = {}; if (error.response) { err.errcode = error.response.status; err.errmsg = error.response.statusText; } else { err.errcode = -1; err.errmsg = error.message; } Store.commit("finishLoading"); return Promise.reject(err); } )