axios拦截器封装http请求,刷新token重发请求

有时候要根据项目的具体需求从新封装http请求。 最近在公司作一个商城项目,由于我也是vue小白,在网上参考了不少资料,才把需求搞定。 以个人项目为例,需求:vue

  1. 全部对服务器的请求先访问 '/GetMaintenanceState'接口,若是服务器正在维护就拦截请求,跳转到维护页面并显示维护信息;若是服务器正在运行,再发起请求。
  2. 须要登陆后发送的请求:(登陆时请求接口'Token',将 access_tokenrefresh_token 保存在localStorage),每次请求都要带自定义请求头 Authorization。
  3. access_token 过时后,用 refresh_token 从新请求刷新token,若是refresh_token 过时跳转到登陆页面从新获取token。
  4. 由于咱们的全部接口除了网络问题,返回的 status 都是200(OK),请求成功 IsSuccesstrue,请求失败 IsSuccessfalse。请求失败会返回响应的错误码 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();
    });
}
复制代码

封装刷新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 实例(这里主要是最复杂的请求)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