处理vue页面登录失败问题

This commit is contained in:
andy 2026-08-04 14:50:51 +08:00
parent 296e6d74df
commit 174f8454c3
2 changed files with 54 additions and 42 deletions

View File

@ -238,8 +238,6 @@ const router = createRouter({
// 已添加的动态路由 // 已添加的动态路由
const addedRouteNames = new Set<string>() const addedRouteNames = new Set<string>()
/** /**
* *
*/ */

View File

@ -1,4 +1,7 @@
import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios' import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios'
import { useUserStore } from '@/stores/user'
// const BASE_API = import.meta.env.VITE_APP_BASE_API
// API响应结构 // API响应结构
interface ApiResponse<T = any> { interface ApiResponse<T = any> {
@ -11,24 +14,12 @@ interface ApiResponse<T = any> {
interface CryptoConfig { interface CryptoConfig {
enabled: boolean enabled: boolean
publicKey: string publicKey: string
aesKey: string aesKey: string // AES密钥的Base64编码
} }
// 加密配置缓存 // 加密配置缓存
let cryptoConfigCache: CryptoConfig | null = null let cryptoConfigCache: CryptoConfig | null = null
// 从 localStorage 安全读取 token避免循环依赖
function getToken(): string | null {
try {
const raw = localStorage.getItem('mes-user')
if (raw) {
const data = JSON.parse(raw)
return data?.token || null
}
} catch {}
return null
}
// 获取加密配置 // 获取加密配置
export async function fetchCryptoConfig(): Promise<CryptoConfig> { export async function fetchCryptoConfig(): Promise<CryptoConfig> {
if (cryptoConfigCache) { if (cryptoConfigCache) {
@ -51,7 +42,7 @@ export function clearCryptoConfigCache() {
cryptoConfigCache = null cryptoConfigCache = null
} }
// 判断是否是AES加密的响应数据 // 判断是否是AES加密的响应数据格式iv.encryptedData
function isAesEncryptedData(data: any): boolean { function isAesEncryptedData(data: any): boolean {
if (typeof data !== 'string') { if (typeof data !== 'string') {
return false return false
@ -60,9 +51,11 @@ function isAesEncryptedData(data: any): boolean {
if (parts.length !== 2) { if (parts.length !== 2) {
return false return false
} }
// 检查两部分是否都是有效的Base64且IV长度正确
try { try {
atob(parts[0]) atob(parts[0])
atob(parts[1]) atob(parts[1])
// IV是12字节Base64后是16字符
return parts[0].length === 16 && parts[1].length > 10 return parts[0].length === 16 && parts[1].length > 10
} catch { } catch {
return false return false
@ -80,6 +73,7 @@ async function aesDecrypt(encryptedData: string, aesKeyBase64: string): Promise<
const data = Uint8Array.from(atob(parts[1]), c => c.charCodeAt(0)) const data = Uint8Array.from(atob(parts[1]), c => c.charCodeAt(0))
const keyBytes = Uint8Array.from(atob(aesKeyBase64), c => c.charCodeAt(0)) const keyBytes = Uint8Array.from(atob(aesKeyBase64), c => c.charCodeAt(0))
// 导入AES密钥
const aesKey = await crypto.subtle.importKey( const aesKey = await crypto.subtle.importKey(
'raw', 'raw',
keyBytes, keyBytes,
@ -88,6 +82,7 @@ async function aesDecrypt(encryptedData: string, aesKeyBase64: string): Promise<
['decrypt'] ['decrypt']
) )
// 解密
const decrypted = await crypto.subtle.decrypt( const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv }, { name: 'AES-GCM', iv: iv },
aesKey, aesKey,
@ -110,24 +105,36 @@ async function decryptResponseData(data: string): Promise<any> {
return JSON.parse(decryptedStr) return JSON.parse(decryptedStr)
} catch (error) { } catch (error) {
console.error('响应解密失败', error) console.error('响应解密失败', error)
// 解密失败可能是密钥过期,清除缓存
cryptoConfigCache = null cryptoConfigCache = null
return data return data
} }
} }
//axios.defaults.headers["Content-Type"] = 'application/json;charset=utf-8' //"application/x-www-form-urlencoded;charset=utf-8"; //"application/json;charset=utf-8";
// 创建axios实例 // 创建axios实例
// 后端 API 统一使用 /api 前缀
const service: AxiosInstance = axios.create({ const service: AxiosInstance = axios.create({
baseURL: '/api', baseURL:'/api',
timeout: 30000 timeout: 30000
}) })
// 请求拦截器 - 直接从 localStorage 读取 token // 请求拦截器
service.interceptors.request.use( service.interceptors.request.use(
(config) => { (config) => {
const token = getToken() const userStore = useUserStore()
if (token) { if (userStore.token) {
config.headers['Authorization'] = token config.headers['Authorization'] = userStore.token
} }
if (config.data instanceof FormData) {
// 文件上传:不设置 JSON 请求头、不序列化,交给浏览器自动处理 multipart
return config
}
config.headers["Content-Type"] = 'application/json;charset=utf-8'
config.data = config.data instanceof Object ? JSON.stringify(config.data) : config.data
return config return config
}, },
(error) => { (error) => {
@ -141,25 +148,32 @@ let isLoggingOut = false
// 响应拦截器 // 响应拦截器
service.interceptors.response.use( service.interceptors.response.use(
async (response: AxiosResponse<ApiResponse>) => { async (response: AxiosResponse<ApiResponse>) => {
// blob 类型响应直接返回 // 如果是 blob 类型响应(文件下载),直接返回
if (response.config.responseType === 'blob') { if (response.config.responseType === 'blob') {
return response.data return response.data
} }
if (response.config.responseType === 'arraybuffer') {
// 二进制流不走code校验直接完整返回ArrayBuffer
return response.data
}
const res = response.data const res = response.data
if (res.code !== 200) { if (res.code !== 200) {
// logout 接口返回 401 时不显示错误消息(避免干扰)
const isLogoutRequest = response.config.url?.includes('/auth/logout') const isLogoutRequest = response.config.url?.includes('/auth/logout')
// 401 未授权 // 401 未授权,弹出明确提示并跳转登录(防止重复调用)
if (res.code === 401 && !isLoggingOut && !isLogoutRequest) { if (res.code === 401 && !isLoggingOut && !isLogoutRequest) {
window.$message?.warning('当前用户登录已过期,请重新登录')
isLoggingOut = true isLoggingOut = true
window.$message?.error('当前用户登录已过期,请重新登录')
// 清除本地 token const userStore = useUserStore()
localStorage.removeItem('mes-user') await userStore.logout()
isLoggingOut = false isLoggingOut = false
// 跳转到登录页
window.location.href = '/login'
return Promise.reject(new Error('登录已过期')) return Promise.reject(new Error('登录已过期'))
} }
@ -170,7 +184,7 @@ service.interceptors.response.use(
return Promise.reject(new Error(res.message || '请求失败')) return Promise.reject(new Error(res.message || '请求失败'))
} }
// AES 解密 // 检查响应数据是否是AES加密的自动解密
if (isAesEncryptedData(res.data)) { if (isAesEncryptedData(res.data)) {
try { try {
return await decryptResponseData(res.data) return await decryptResponseData(res.data)