双令牌

// router.js
router.beforeEach(async (to, from, next) => {
  NProgress.start()
  const token = getToken()
  if (to.path === '/login') {
    // 如果是访问登录界面,清空用户信息
    store.commit('logout')
    next()
  } else if (whiteList.includes(to.path)) {
    if (token && !store.state.uid) {
      await store.dispatch('getUserInfo')
    }
    next()
  } else {
    // 如果浏览器缓存中有用户信息,则表明已登录
    // 如果访问非登录界面,且户会话信息不存在,代表未登录,则跳转到登录界面
    if (!token) {
      next(`/login?redirect=${to.path}`)
    } else {
      if (store.state.uid) {
        next()
      } else {
        try {
          await store.dispatch('getUserInfo')
          next()
        } catch {
          // 清除数据,跳转到登录界面
          store.commit('logout')
          next(`/login?redirect=${to.path}`)
        }
      }
    }
  }
})
// utils/request.ts
import axios from 'axios'
import { ElMessage } from 'element-plus'
import type { AxiosRequestConfig } from 'axios'
import router from '@/router'
import store from '@/store'
import { getRefreshToken, getToken, setRefreshToken, setToken } from '@/utils/tools'
import { refreshToken } from '@/apis'

export interface ISuccessData<T> {
  code: number;
  message: string;
  data: T
}

// 创建axios实例
const service = axios.create({
  baseURL: '/',
  timeout: 20000,
  headers: { 'Content-Type': 'application/json;charset=utf-8' }
})

service.interceptors.request.use(
  config => {
    const token = getToken()
    if (token) {
      config.headers = { token }
    }
    return config
  }
)

service.interceptors.response.use(
  async res => {
    if (res.status === 200 && res.data) {
      // 拦截指定错误码 code=401 刷新令牌
      if (res.data.code && res.data.code === 401) {
        if (getRefreshToken()) {
          if (await refresh()) {
            // 重发当前请求
            const config = res.config
            const reRes = service(config)
            return reRes
          }
        }
        ElMessage.error('登录失效,请重新登录')
        setTimeout(() => {
          store.commit('logout')
          router.push('/login')
        }, 1000)
      } else if (res.data.code && res.data.code !== 200 && res.data.message) {
        ElMessage.error(res.data.message)
      }
    } else {
      ElMessage.error('服务器错误,请稍后再试')
    }
    return res.data
  },
  err => {
    ElMessage.error('服务器错误,请稍后再试')
    return Promise.reject(err)
  }
)

async function refresh () {
  const code = getRefreshToken()
  if (code) {
    const res = await refreshToken(code)
    if (res.code === 200) {
      setToken(res.data.token)
      setRefreshToken(res.data.refreshToken)
      return res.data.token
    } else {
      return false
    }
  } else {
    return false
  }
}

export class Request {
  static get<T> (url: string, params?: object): Promise<ISuccessData<T>> {
    return service.get(url, { params })
  }

  static post<T> (
    url: string,
    data?: object,
    config?: AxiosRequestConfig
  ): Promise<ISuccessData<T>> {
    return service.post(url, data, config)
  }

  static put<T> (
    url: string,
    data?: object,
    config?: AxiosRequestConfig
  ): Promise<ISuccessData<T>> {
    return service.put(url, data, config)
  }

  static patch<T> (
    url: string,
    data?: object,
    config?: AxiosRequestConfig
  ): Promise<ISuccessData<T>> {
    return service.patch(url, data, config)
  }

  static delete<T> (url: string, data?: object): Promise<ISuccessData<T>> {
    return service.delete(url, { data })
  }
}
// utils/tools.ts
const TOKEN_KEY = 'token'
const REFRESH_TOKEN_KEY = 'refresh-token'

export function getToken () {
  return localStorage.getItem(TOKEN_KEY)
}

export function setToken (token: string) {
  localStorage.setItem(TOKEN_KEY, token)
}

export function removeToken () {
  localStorage.removeItem(TOKEN_KEY)
}

export function getRefreshToken () {
  return localStorage.getItem(REFRESH_TOKEN_KEY)
}

export function setRefreshToken (token: string) {
  localStorage.setItem(REFRESH_TOKEN_KEY, token)
}

export function removeRefreshToken () {
  localStorage.removeItem(REFRESH_TOKEN_KEY)
}