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

相關文章
相關標籤/搜索