import { ref, computed } from 'vue' import { defineStore } from 'pinia' import { useRouter } from 'vue-router' import axios from 'axios' import { login as authLogin, refreshAccessToken as authRefreshToken } from '@/api/authService.js' export const useAuthStore = defineStore('auth', () => { const router = useRouter() // 状态 const accessToken = ref(localStorage.getItem('accessToken') || null) const refreshToken = ref(localStorage.getItem('refreshToken') || null) const user = ref(null) const interceptorsInitialized = ref(false) const isRefreshing = ref(false) let refreshPromise = null // 计算属性 const isAuthenticated = computed(() => !!accessToken.value) // JWT 工具 const decodeJwt = (jwt) => { try { const payload = jwt.split('.')[1] const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/')) // 处理可能缺失的 padding const json = decodeURIComponent( decoded .split('') .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join('') ) return JSON.parse(json) } catch { return null } } const getAccessTokenExp = () => { if (!accessToken.value) return 0 const payload = decodeJwt(accessToken.value) return payload?.exp ? Number(payload.exp) : 0 } const isAccessTokenExpired = () => { const exp = getAccessTokenExp() if (!exp) return false const nowInSeconds = Math.floor(Date.now() / 1000) return nowInSeconds >= exp } const isAccessTokenExpiringSoon = (thresholdSeconds = 60) => { const exp = getAccessTokenExp() if (!exp) return false const nowInSeconds = Math.floor(Date.now() / 1000) return exp - nowInSeconds <= thresholdSeconds } const setAxiosAuthHeader = (token) => { if (token) { axios.defaults.headers.common.Authorization = `Bearer ${token}` } else { delete axios.defaults.headers.common.Authorization } } const refreshAccessToken = async () => { if (!refreshToken.value) throw new Error('NO_REFRESH_TOKEN') if (isRefreshing.value && refreshPromise) { return refreshPromise } isRefreshing.value = true refreshPromise = authRefreshToken(refreshToken.value) .then((data) => { const newAccess = data?.access if (!newAccess) throw new Error('NO_ACCESS_FROM_REFRESH') accessToken.value = newAccess localStorage.setItem('accessToken', newAccess) setAxiosAuthHeader(newAccess) return newAccess }) .finally(() => { isRefreshing.value = false refreshPromise = null }) return refreshPromise } const setupAxiosInterceptors = () => { if (interceptorsInitialized.value) return // 请求拦截:总是携带最新的访问令牌 axios.interceptors.request.use((config) => { if (accessToken.value) { config.headers = config.headers || {} config.headers.Authorization = `Bearer ${accessToken.value}` } return config }) // 响应拦截:遇到 401 尝试刷新一次并重试原请求 axios.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config || {} const status = error?.response?.status const isUnauthorized = status === 401 if (!isUnauthorized) return Promise.reject(error) if (originalRequest._retry) { // 已重试过,仍然 401,直接登出 logout() return Promise.reject(error) } originalRequest._retry = true try { await refreshAccessToken() originalRequest.headers = originalRequest.headers || {} originalRequest.headers.Authorization = `Bearer ${accessToken.value}` return axios(originalRequest) } catch (e) { logout() return Promise.reject(e) } } ) interceptorsInitialized.value = true } // 方法 const login = async (credentials) => { try { const { username, password } = credentials || {} // 使用新的认证服务进行登录 const { access, refresh } = await authLogin({ username, password, }) if (!access) { return { success: false, error: '未获取到访问令牌' } } // 保存认证信息 accessToken.value = access refreshToken.value = refresh || null user.value = { username } localStorage.setItem('accessToken', access) if (refresh) localStorage.setItem('refreshToken', refresh) localStorage.setItem('user', JSON.stringify(user.value)) // 设置全局请求头,方便后续接口调用 setAxiosAuthHeader(access) // 跳转到主页 router.push('/') return { success: true } } catch (error) { const message = error?.response?.data?.detail || error?.response?.data?.message || error.message || '登录失败' console.error('登录失败:', error) return { success: false, error: message } } } const logout = () => { // 清除认证信息 accessToken.value = null refreshToken.value = null user.value = null localStorage.removeItem('accessToken') localStorage.removeItem('refreshToken') localStorage.removeItem('user') // 清除全局请求头 setAxiosAuthHeader(null) // 跳转到登录页 router.push('/login') } const initializeAuth = () => { const savedToken = localStorage.getItem('accessToken') const savedRefreshToken = localStorage.getItem('refreshToken') const savedUser = localStorage.getItem('user') if (savedToken && savedUser) { accessToken.value = savedToken refreshToken.value = savedRefreshToken user.value = JSON.parse(savedUser) // 初始化时设置全局请求头 setAxiosAuthHeader(savedToken) } // 初始化 Axios 拦截器 setupAxiosInterceptors() } return { accessToken, user, isAuthenticated, isAccessTokenExpired, isAccessTokenExpiringSoon, refreshAccessToken, setupAxiosInterceptors, login, logout, initializeAuth, refreshToken, } })