From af38e3ea7c16f738b4758926b19d9dc5d7ceb948 Mon Sep 17 00:00:00 2001 From: chaoszhu Date: Sat, 29 Aug 2026 22:42:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20token=20=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/store/index.js | 13 ++++++----- web/src/utils/axios.js | 51 +++++++++++++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/web/src/store/index.js b/web/src/store/index.js index dea7147..7e207d1 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -89,6 +89,13 @@ const useStore = defineStore('global', { localStorage.setItem('deviceId', deviceId) this.$patch({ user: username, deviceId }) }, + clearLoginInfo() { + localStorage.removeItem('token') + localStorage.removeItem('uid') + localStorage.removeItem('user') + localStorage.removeItem('deviceId') + this.$patch({ token: null, uid: null, user: null, deviceId: null }) + }, async removeLoginInfo(removeSession = false) { try { if (removeSession && this.deviceId) { @@ -97,11 +104,7 @@ const useStore = defineStore('global', { } catch (err) { console.error('注销登录凭证失败: ', err.message) } finally { - localStorage.removeItem('token') - localStorage.removeItem('uid') - localStorage.removeItem('user') - localStorage.removeItem('deviceId') - this.$patch({ token: null, uid: null, user: null, deviceId: null }) + this.clearLoginInfo() } }, async getMainData() { diff --git a/web/src/utils/axios.js b/web/src/utils/axios.js index e770114..20f18f0 100644 --- a/web/src/utils/axios.js +++ b/web/src/utils/axios.js @@ -8,8 +8,35 @@ axios.defaults.withCredentials = true axios.defaults.baseURL = process.env.serviceApiPrefix || '/api/v1' const instance = axios.create() +let authGeneration = 0 + +function handleAuthFailure(error, skipErrorMessage) { + const store = useStore() + const requestToken = error.config?.headers?.token || null + + // 重新登录后仍可能收到旧请求的失败响应,不能让旧响应清除新的登录态。 + if (store.token && requestToken !== store.token) return + + // 首个失败响应推进版本号,同一批并发请求及更早的响应会自动失效。 + if (error.config?.authGeneration !== authGeneration) return + authGeneration += 1 + + store.clearLoginInfo() + if (router.currentRoute.value.path === '/login') return + + if (!skipErrorMessage) { + ElMessage.closeAll() + ElMessage({ + message: error.response?.data?.msg || '登录状态已失效,请重新登录', + type: 'error', + center: true + }) + } + router.replace('/login') +} instance.interceptors.request.use((config) => { + config.authGeneration = authGeneration config.headers.token = useStore().token return config }, (error) => { @@ -20,25 +47,23 @@ instance.interceptors.request.use((config) => { instance.interceptors.response.use((response) => { if (response.status === 200) return response.data }, (error) => { - let { response } = error + const { response } = error const skipErrorMessage = error.config?.skipErrorMessage if (error?.message?.includes('timeout')) { if (!skipErrorMessage) ElMessage({ message: '请求超时', type: 'error', center: true }) return Promise.reject(error) } - switch (response?.data?.status) { - case 401: // token过期 - router.push('login') - return Promise.reject(error) - case 403: - if (response?.data?.data?.code === 'IP_ACCESS_DENIED') { - window.location.reload() - return Promise.reject(error) - } - if (!skipErrorMessage) ElMessage({ message: `${ response?.data?.msg || '登录错误' }`, type: 'error', center: true }) - router.push('login') - return Promise.reject(error) + + if (response?.data?.data?.code === 'IP_ACCESS_DENIED') { + window.location.reload() + return Promise.reject(error) } + + if ([401, 403,].includes(response?.data?.status)) { + handleAuthFailure(error, skipErrorMessage) + return Promise.reject(error) + } + switch (response?.status) { case 404: if (!skipErrorMessage) ElMessage({ message: '404 Not Found', type: 'error', center: true })