vue axios 静默刷新token

好记性不如烂笔头,记录下来,省的每次写都要回忆下。
老手参考这个,
新手参考这个。

说下核心思路,主要两点:
第一
先跟后端确认,token失效是http状态码报非200还是报200,私有属性设置。
非200,拦截响应,要在response.use error里拦截,
200要在response.use response里拦截。

axios.interceptors.response.use(
  response => {
   //200这里拦截
    return response
  },
  error => {
     //非200这里拦截
    return error.response
  }
)

第二
要有数据流,状态流的概念,只简单包层promise,加个resolve,reject是不行的。
token失效,拦截更新token后,要返回一个带着config 重新调用axios的闭包函数,
而不是执行axios请求函数.
因为重新调用axios请求,就是另外一个promise,会导致业务代码拿不到返回值

需求
假设token失效,
接口返回状态码401,
token和refreshToken都存在localstorage,
axios又被promise封装了一层

解决代码(看注释 刷新token那一段即可)

import Vue from 'vue'
import axios from 'axios'
import vueAxios from 'vue-axios'
import {
  updateToken
} from '@/axios/api/login'
import {
  message
} from 'ant-design-vue'
Vue.use(vueAxios, axios)

/*
 *  这里写一个读取本地配置文件的方法,原生ajax,同步读取文件
 */
const getConfigration = function () {
  let ajax = null
  // 判断ajax对浏览器支持情况
  if (window.XMLHttpRequest) {
    ajax = new XMLHttpRequest()
  } else if (window.ActiveXObject) {
    ajax = new window.ActiveXObject()
  } else {
    alert('您的浏览器不支持ajax')
  }
  if (ajax != null) {
    ajax.open('GET', '/config.json?t=' + Date.now(), false)
    ajax.send(null)
    ajax.onreadystatechange = function () {}
    return JSON.parse(ajax.responseText)
  }
}
export const configFile = getConfigration()

/**
 * [http request 拦截器]
 * @return
 */
axios.interceptors.request.use(
  config => {
    if (config.headers) {
      config.headers.Authorization = localStorage.token
    } else {
      config.headers = {
        Authorization: localStorage.token
      }
    }

    // 用户权限
    if (config.params) {
      // config.params.token = '0000'
    } else {
      config.params = {
        // token: '0000'
      }
    }
    // 判断网络是否连接
    if (window.navigator.onLine) {
      return config
    } else {
      message.warning({
        content: '请检查网络或重新登录系统!',
        duration: 3
      })
    }
  },
  error => {
    // 对请求错误的操作
    message.error({
      content: '后台服务异常!',
      duration: 3
    })
    return Promise.reject(error)
  }
)




let isRefreshing = false
let requests = []
let save = ''
axios.interceptors.response.use(
  response => {
    save = response
    return response
  },
  error => {
  //刷新token
    if (error && error.response&&error.response.status == 401) {
          const config = error.config
          if (localStorage.getItem('refreshToken')) {
            if (!isRefreshing) {
                  isRefreshing = true
                  
              return updateToken({
                    refreshToken: localStorage.getItem('refreshToken')
                  }).then(res => {
                    if (res.returnCode === 200) {

                      localStorage.setItem('token', res.data)
                      config.baseURL = ''
                      config.headers['Authorization'] = localStorage.getItem('token')
                      
                      requests.forEach((item) => {
                       item()
                      })
                      requests = []

                      return axios(config)
                    }
                  }).finally(() => {               
                    isRefreshing = false
                  })
            }else {
                  return new Promise((resolve) => {
                    requests.push(() => {
                      config.baseURL = ''
                      config.headers['Authorization'] = localStorage.getItem('token')
                      resolve(axios(config))
                    })
                  })
            }       
          } else {
            Vue.prototype.$bus.$emit('gologin')
          }
    }
    return error.response
  }
)



// 处理http 请求响应码
function successState (response) {
  switch (response && response.status) {
    case 400:
      if (response.data.returnCode === 400) {
        message.warning({
          content: response.data.message || '接口传参异常',
          duration: 3
        })
      }
      if (response.data.returnCode === 400009) {
        // 用户未注册
        break
      }
      if (response.data.returnCode === 400001) {
        // 验证码错误
        break
      }
      if (response.data.returnCode === 400005) {
        // 账号锁定
        break
      }
      if (response.data.returnCode === 400006) {
        // 账号密码错误
        break
      }
      if (response.data.returnCode === 400002) {
        localStorage.setItem('userinfo', '')
        localStorage.setItem('token', '')
        localStorage.setItem('refreshToken', '')
        Vue.prototype.$bus.$emit('gologin')
        break
      }

      // message.warning({
      //   content: response.data.message || '接口错误',
      //   duration: 3
      // })
      break
    case 401:
      if (localStorage.refreshToken) {
        // 刷新token
        Vue.prototype.$bus.$emit('uptoken')
      } else {
        Vue.prototype.$bus.$emit('gologin')
      }
      break
    case 403:
      message.warning({
        content: response.data.message || '未授权,权限不够',
        duration: 3
      })
      break
    case 500:
      message.warning({
        content: '内部服务错误',
        duration: 3
      })
      break
    case undefined:
      console.log(response.data.message)
      // message.error({
      //   content: response.data.message || '接口请求失败!',
      //   duration: 3
      // })
      break
    default:
      return response && response.data
  }
}

function errorState (err) {
  message.error({
    content: '请求错误!',
    duration: 3
  })
}




const httpServer = (opts, data) => {
  let baseUrl = '' // 这里是一个默认的url,可以没有
  switch (process.env.NODE_ENV) {
    case 'development':
      baseUrl = configFile.devUrl
      break
    case 'huawei':
      baseUrl = configFile.huaweiUrl
      break
    case 'production':
      baseUrl = configFile.prodUrl
      break
  }
  // http默认配置
  let httpDefaultOpts = {
    method: opts.method, // 必填
    baseURL: baseUrl,
    url: opts.url, // 必填
    timeout: 10 * 1000,
    params: data,
    headers: opts.headers
  }

  let promise = new Promise((resolve, reject) => {
    axios(httpDefaultOpts)
      .then(res => {
        successState(res)
        // 添加响应头
        res.data.headers = save
        resolve(res && res.data)
      })
      .catch(err => {
        errorState(err)
        reject(err)
      })
  })

  return promise
}



export default httpServer

你可能感兴趣的:(vue axios 静默刷新token)