前端面试题(关于项目细节)

 一、说一下项目流程

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);
  }
)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值