智慧园区前端大屏
shuishen
2024-11-21 084067dfb5281500e90b872667389465c96e81eb
token失效处理
4 files modified
158 ■■■■■ changed files
src/api/user.js 27 ●●●●● patch | view | raw | blame | history
src/pages/layout/index.vue 4 ●●●● patch | view | raw | blame | history
src/store/login.js 33 ●●●●● patch | view | raw | blame | history
src/utils/http.js 94 ●●●●● patch | view | raw | blame | history
src/api/user.js
@@ -1,3 +1,13 @@
/*
 * @Author: shuishen 1109946754@qq.com
 * @Date: 2024-10-30 15:34:08
 * @LastEditors: shuishen 1109946754@qq.com
 * @LastEditTime: 2024-11-21 12:18:06
 * @FilePath: \bigScreen\src\api\user.js
 * @Description:
 *
 * Copyright (c) 2024 by shuishen, All Rights Reserved.
 */
import request from 'utils/http'
export const loginByUsername = (tenantId, deptId, roleId, username, password, type, key, code) =>
@@ -26,4 +36,21 @@
    url: '/blade-auth/oauth/logout',
    method: 'get',
    authorization: false,
  })
export const refreshToken = (refresh_token, tenantId, deptId, roleId) =>
  request({
    url: '/blade-auth/oauth/token',
    method: 'post',
    headers: {
      'Tenant-Id': tenantId,
      'Dept-Id': deptId || '',
      'Role-Id': roleId || '',
    },
    params: {
      tenantId,
      refresh_token,
      grant_type: 'refresh_token',
      scope: 'all',
    },
  })
src/pages/layout/index.vue
@@ -2,8 +2,8 @@
 * @Author: shuishen 1109946754@qq.com
 * @Date: 2022-08-18 16:18:24
 * @LastEditors: shuishen 1109946754@qq.com
 * @LastEditTime: 2024-11-11 18:36:37
 * @FilePath: \bigScreen\src\views\layout\index.vue
 * @LastEditTime: 2024-11-21 12:29:12
 * @FilePath: \bigScreen\src\pages\layout\index.vue
 * @Description:
 *
 * Copyright (c) 2022 by shuishen 1109946754@qq.com, All Rights Reserved.
src/store/login.js
@@ -1,13 +1,15 @@
// src/stores/useUserStore.js
import {
  loginByUsername,
  logout
  logout,
  refreshToken,
} from '@/api/user'
import { defineStore } from 'pinia'
import md5 from 'js-md5'
import {
  ElMessage
} from 'element-plus'
import { validatenull } from '@/utils/validate'
export const useLogin = defineStore('login', {
  // 存储状态的地方,相当于 Vuex 的 state
@@ -91,6 +93,35 @@
          })
      })
    },
    RefreshToken (userInfo = {}) {
      return new Promise((resolve, reject) => {
        refreshToken(
          this.refreshToken,
          this.tenantId,
          !validatenull(userInfo) ? userInfo.deptId : this.userInfo.dept_id,
          !validatenull(userInfo) ? userInfo.roleId : this.userInfo.role_id
        )
          .then(res => {
            const data = res.data
            this.SET_TOKEN(data.access_token)
            this.SET_REFRESH_TOKEN(data.refresh_token)
            this.SET_USER_INFO(data)
            resolve()
          })
          .catch(error => {
            reject(error)
          })
      })
    },
    removeToken () {
      this.SET_TOKEN('')
    },
    removeRefreshToken () {
      this.SET_REFRESH_TOKEN('')
    }
  },
  persist: {
src/utils/http.js
@@ -6,7 +6,7 @@
 * isToken是否需要token
 */
import axios from 'axios'
// import router from '@/router/'
import router from '@/router/index'
import {
  isURL,
@@ -27,6 +27,10 @@
// 全局未授权错误提示状态,只提示一次
let isErrorShown = false
// 全局锁机制相关变量
let isRefreshing = false // 标记当前是否正在刷新token
let refreshTokenPromise = null // 刷新token的Promise,避免重复请求
// 超时时间设置为10分钟,部分接口上传比较慢,如固件上传
axios.defaults.timeout = 600000
@@ -103,6 +107,8 @@
//http response拦截
axios.interceptors.response.use(
  res => {
    const store = useLogin()
    NProgress.done()
    const status = res.data.error_code || res.data.code || res.status
    const statusWhiteList = []
@@ -112,6 +118,92 @@
    //如果在白名单里则自行catch逻辑处理
    if (statusWhiteList.includes(status)) return Promise.reject(res)
    // 如果是401并且没有重试过,尝试刷新token
    if (status === 401 && !config._retry) {
      config._retry = true
      // 如果当前已经在刷新token,等待刷新完成
      if (isRefreshing) {
        return refreshTokenPromise.then(() => {
          const meta = config.meta || {}
          const isToken = meta.isToken === false
          const cryptoToken = config.cryptoToken === true
          const token = store.token
          if (token && !isToken) {
            config.headers['Blade-Auth'] = cryptoToken
              ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey)
              : 'bearer ' + token
          }
          return axios(config)
        })
      }
      // 开始刷新token
      isRefreshing = true
      // 调用RefreshToken action来刷新token
      refreshTokenPromise = store.RefreshToken().then(() => {
        const meta = config.meta || {}
        const isToken = meta.isToken === false
        const cryptoToken = config.cryptoToken === true
        // 获取刷新后的token
        const token = store.token
        if (token && !isToken) {
          config.headers['Blade-Auth'] = cryptoToken
            ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey)
            : 'bearer ' + token
        }
        return axios(config)
      }).catch(() => {
        isRefreshing = false // 重置刷新标志
        // 首次报错时提示
        if (!isErrorShown) {
          isErrorShown = true
          ElMessage({
            message: '用户令牌过期,请重新登录',
            type: 'error',
          })
        }
        // 清除token信息
        store.removeToken()
        store.removeRefreshToken()
        // 重定向到登录页
        store.LogOut().then(res => {
          router.push({
            path: '/login'
          })
        })
        return Promise.reject(new Error(message))
      })
      return refreshTokenPromise
    }
    // 如果是401并且已经重试过,直接跳转到登录页面
    if (status === 401 && config._retry) {
      if (!isErrorShown) {
        isErrorShown = true
        ElMessage({
          message: '用户令牌过期,请重新登录',
          type: 'error',
        })
      }
      // 清除token信息
      store.removeToken()
      store.removeRefreshToken()
      // 重定向到登录页
      store.LogOut().then(res => {
        router.push({
          path: '/login'
        })
      })
      return Promise.reject(new Error(message))
    }
    // 如果请求为oauth2错误码则首次报错时提示
    if (status > 2000 && !validatenull(res.data.error_description)) {
      // 首次报错时提示