vue项目中axios的封装

1,073 阅读3分钟

通过上一篇文章我们了解了axios,这次结合项目中的封装,具体讲解一下。

1 为什么要封装axios

对axios进行封装以及将API接口按业务模块统一管理,有助于我们简化代码,方便后期维护。

2 在项目中整体使用

image.png

3 逐句分析

import axios from 'axios';
import { Message, MessageBox } from 'element-ui';
import store from '@/store';
import router from '@/router';
import { getToken } from '@/utils/auth';


// 是否正在刷新的标记
let isRefreshing = false;
// 重试队列,每一项将是一个待执行的函数形式
let requests = [];
// 创建axios实例:service
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, //设置默认的URL前缀
  withCredentials: true, // send cookies when cross-domain requests
  timeout: 60000 // 设置请求超时。如果超过了60秒,就会告知用户当前请求超时,请刷新等
});
service.setToken = (token) => {
  service.defaults.headers['Authorization'] = 'Bearer ' + token;
  window.localStorage.setItem('Admin-Token', token);
};
// 请求拦截器
service.interceptors.request.use(
  config => {
    // do something before request is sent
    //每次请求之前判断vuex中是否存在token
    //如果存在,则统一在http请求的header上都加上token,以便后台根据token判断当前登陆情况
    //即使本地存在token,也有可能过期,所以需要对返回状态进行判断
     const token = store.getters.token;
    if token && (config.headers.Authorization = token) {
      config.headers['Authorization'] = 'Bearer ' + getToken(); 
    }


    if (['post', 'put', 'delete'].includes(config.method) && typeof config.data === 'object') {
      config.data = Object.keys(config.data).map(key => `${key}=${config.data[key]}`).join('&');
      config.data = encodeURI(config.data); // 解决参数中包含特殊字符无法保存的问题
      //设置请求头
      config.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
    }
    return config;
  },
  error => {
    // do something with request error
    console.log(error); // for debug
    return Promise.reject(error);
  }
);
/* 重新获取新的token*/
function refreshToken() {
  // service是当前request.js中已创建的axios实例
  return axios({
    method: 'post',
    url: 'api-auth/oauth/token',
    params: {
      client_id: 'webApp',
      client_secret: 'webApp',
      refresh_token: localStorage.getItem('refresh_token'),
      grant_type: 'refresh_token'
    }
  }).then(res => res);
}
// 响应拦截器
//在我们拿到服务器返回给我们的数据之前进行一些处理
//主要就是进行错误的统一处理和没登录 登录过期后调整登录页的一个操作
service.interceptors.response.use(
  /**
   * If you want to get information such as headers or status
   * Please return  response => response
   */


  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code.
   */
  response => {
    // const res = response.data


    // if the custom code is not 20000, it is judged as an error.
    if (response.status !== 200) {
      Message({
        message: response.message || 'error',
        type: 'error',
        duration: 60 * 1000,
        showClose: true
      });


       //具体状态码需要根据项目中的实际约定的错误状态吗进行判断
       //根据返回的状态吗进行一些操作,诸如 登录过期 错误提示 长时间停留失效等
      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (response.status === 50008 || response.status === 50012 || response.status === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload();
          });
        });
      }
      return Promise.reject(response.message || 'error');
    } else {
      //正常拿到数据
      return response.data;
    }
  },
  error => {
    let message = error.message;
    if (error.response && error.response.data && error.response.data['resp_code']) { // 登录提示
      message = error.response.data['rsp_msg'] || error.response.data['resp_msg'];
    }
    console.log('err' + error); // for debug


    if (error.response && error.response.data && error.response.data['resp_code'] && error.response.data['resp_msg']) {
      if (error.response.data['resp_code'] === '401' && error.response.data['resp_msg'].startsWith('Invalid access token')) {
        /* 401超时*/
        const config = error.response.config;
        if (!isRefreshing) {
          isRefreshing = true;
          return refreshToken().then(res => {
            const token = res.data.access_token;
            service.setToken(token);
            config.headers['Authorization'] = 'Bearer ' + token;
            config.baseURL = '';
            // 已经刷新了token,将所有队列中的请求进行重试
            requests.forEach(cb => cb(token));
            requests = [];
            return service(config); // 重新执行当前的接口
          }).catch(res => {
            const message = '会话超时,请重新登录';
            store.dispatch('user/logout');
            router.push({ path: `/login?redirect=${message}`, params: { errorMessage: message }});
          }).finally(() => {
            isRefreshing = false;
          });
        } else {
          // 正在刷新token,将返回一个未执行resolve的promise
          return new Promise((resolve) => {
            // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
            requests.push((token) => {
              config.baseURL = '';
              config.headers['Authorization'] = 'Bearer ' + token;
              resolve(service(config));
            });
          });
        }
      } else {
        Message({
          dangerouslyUseHTMLString: true,
          message: error.response.data['resp_msg'],
          type: 'error',
          duration: 5 * 1000,
          showClose: true
        });
      }
    } else {
      Message({
        dangerouslyUseHTMLString: true,
        message: `${message}${error.response && error.response.data && error.response.data.message ? '
' + error.response.data.message : ''}`,
        type: 'error',
        duration: 5 * 1000,
        showClose: true
      });
    }


    return Promise.reject(error);
  }
);


export default service;

3 token

token,一般是在登录完成之后,将用户的token通过localStorage或cookie存在本地,然后用户每次在进入页面的时候,会首先从本地存储中读取token,如果token存在说用用户已经登录过,则更新vuex中的token状态。 在每次请求接口的时候,都会在请求的header中携带token,后台人员就可以根据携带的token判断当前登录是否过期,如果请求header中没有token,则说明没有登录过。

参考这个更加简洁优美的axios封装 juejin.cn/post/684490…