参考axios官方文档

// 响应拦截器
// Add a response interceptor
request.interceptors.response.use(
  // 在2xx范围内的任何状态代码都会触发此函数,这里主要用于处理响应数据
  response => {
    return response
  },
  // 任何超出2xx范围的状态码都会触发此函数,这里主要用于处理响应错误
  error => {
    const { status } = error.response
    if (status === 401) { // 未授权
    } else if (status === 403) { // 没有权限
    } else if (status === 404) { // 资源不存在
      Toast.fail({
        message: '请求资源不存在',
        forbidClick: true
      })
    } else if (status >= 500) { // 服务端异常
      Toast.fail({
        message: '服务端异常,请稍后重试',
        forbidClick: true
      })
    }

    // 将未处理的异常往外抛
    return Promise.reject(error)
  })

处理 token 过期

token过期后,如果发现存在user和token则发送 刷新token的请求,请求头中携带上refresh_token的值,
请求成功后,把获取到的最新的token设置给vuex容器中的user。
因为每次请求时,请求拦截器会从vuex容器中拿token。
这样即使用户token过期了,也会自动去发送刷新token的请求来获取最新的token,而不需要用户手动去登录!
在这里插入图片描述

/*
* 请求模块
* */
import axios from 'axios'
// 在非组件模块中获取store 必须采用这种方式
import store from '../store/index.js'
import JSONbig from 'json-bigint'
import { Toast } from 'vant'
import router from '../router'

const request = axios.create({
  baseURL: '/api', // 基础路径
  transformResponse: [function (data) {
    // Do whatever you want to transform the data
    // console.log(data)

    // 后端返回的数据可能不是 JSON 格式字符串
    // 如果不是的话,那么 JSONbig.parse 调用就会报错
    // 所以我们使用 try-catch 来捕获异常,处理异常的发生
    try {
      // 如果转换成功,则直接把结果返回
      return JSONbig.parse(data)
    } catch (err) {
      console.log('转换失败', err)
      // 如果转换失败了,则进入这里
      // 我们在这里把数据原封不动的直接返回给请求使用
      return data
    }
    // axios 默认在内部使用 JSON.parse 来转换处理原始数据
    // return JSON.parse(data)
  }]
})

const refreshTokenReq = axios.create({
  baseURL: '/api' // 基础路径
})
// 请求拦截器
request.interceptors.request.use(function (config) {
  const { user } = store.state
  // 如果用户已登录,统一给接口设置token
  if (user) {
    config.headers.Authorization = `Bearer ${user.token}`
  }
  // 处理完之后一定要把config 返回,否则请求发不出去
  return config
}, function (err) {
  return Promise.reject(err)
})

// 响应拦截器
request.interceptors.response.use(function (response) {
  // 响应成功进入这里
  return response
}, async function (error) {
  // 响应失败进入这里
  const status = error.response.status
  if (status === 400) {
    // 客户端请求参数错误
    Toast.fail('客户端请求参数错误')
  } else if (status === 401) {
    // token无效
    // 如果没有 user 或者 user.token, 直接去登录
    const { user } = store.state
    if (!user || !user.token) {
      // 直接跳转到登录页面
      return redirectLogin()
    }
    // 使用refresh_token,则请求获取新的token
    try {
      const { data } = await refreshTokenReq({
        method: 'PUT',
        url: '/app/v1_0/authorizations',
        headers: {
          Authorization: `Bearer ${user.refresh_token}`
        }
      })
      // 拿到新的token之后把它更新到容器中
      user.token = data.data.token
      store.commit('setUser', user)
      // 把失败的请求重新发送出去
      // error.config 是本次请求的相关配置项
      // 注意: 这里使用request发请求,它会走自己的请求拦截器,它的请求
      //  拦截器中通过store容器访问token数据
      return request(error.config)
      // console.log(data)
    } catch (err) {
      // 刷新token都失败了,直接跳转到登录页
      redirectLogin()
    }
  } else if (status === 403) {
    // 没有权限操作
    Toast.fail('没有权限操作')
  } else if (status >= 500) {
    // 服务端异常
    Toast.fail('服务端异常,请稍后重试!')
  }
  // console.dir(error)
  return Promise.reject(error)
})

function redirectLogin () {
  router.replace('/login')
}

// 导出
export default request

登录成功跳转回原来页面

首先在响应拦截器中:
在这里插入图片描述
注意:
router.currentRoute 和 我们在组件中获取的this.$route是一个东西
在这里插入图片描述
在这里插入图片描述

// 响应拦截器
request.interceptors.response.use(
  // 响应成功进入第1个函数
  // 该函数的参数是响应对象
  function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response
  },
  // 响应失败进入第2个函数,该函数的参数是错误对象
  async function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    // 如果响应码是 401 ,则请求获取新的 token

    // 响应拦截器中的 error 就是那个响应的错误对象
    console.dir(error)
    if (error.response && error.response.status === 401) {
      // 校验是否有 refresh_token
      const user = store.state.user

      if (!user || !user.refresh_token) {
        // router.push('/login')
+        redirectLogin()

        // 代码不要往后执行了
        return
      }

      // 如果有refresh_token,则请求获取新的 token
      try {
        const res = await axios({
          method: 'PUT',
          url: 'http://ttapi.research.itcast.cn/app/v1_0/authorizations',
          headers: {
            Authorization: `Bearer ${user.refresh_token}`
          }
        })

        // 如果获取成功,则把新的 token 更新到容器中
        console.log('刷新 token  成功', res)
        store.commit('setUser', {
          token: res.data.data.token, // 最新获取的可用 token
          refresh_token: user.refresh_token // 还是原来的 refresh_token
        })

        // 把之前失败的用户请求继续发出去
        // config 是一个对象,其中包含本次失败请求相关的那些配置信息,例如 url、method 都有
        // return 把 request 的请求结果继续返回给发请求的具体位置
        return request(error.config)
      } catch (err) {
        // 如果获取失败,直接跳转 登录页
        console.log('请求刷线 token 失败', err)
        // router.push('/login')
+        redirectLogin()
      }
    }

    return Promise.reject(error)
  }
)

+ function redirectLogin () {
+   // router.currentRoute 当前路由对象,和你在组件中访问的 this.$route 是同一个东西
    // query 参数的数据格式就是:?key=value&key=value
+   router.push('/login?redirect=' + router.currentRoute.fullPath)
+ }

然后在登录成功以后:

const redirect = this.$route.query.redirect || "/";
this.$router.push(redirect);

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐