fix: 修复刷新页面后自动登出的问题
- 修复 initAuth 中错误的 cookie 解析方式,使用 getSessionUser() 正确读取 - 重构 auth store,提取公共方法消除代码重复 - 提取 setUserState/clearUserState 统一状态管理 - 提取 isAuthError 统一错误判断 - 提取 restoreUserFromStorage 恢复 localStorage 状态 - 提取 validateAndUpdateUser 统一验证逻辑 - 优化 fetchInterceptor,添加初始化标志避免在 initAuth 期间误触发登出 - 改进错误处理,区分认证错误和网络错误,避免网络错误导致误登出 - 优化 initAuth 逻辑,先恢复 localStorage 状态避免闪烁,再验证 cookie 代码从 178 行优化到 165 行,initAuth 从 87 行减少到 28 行,消除 40+ 行重复代码
This commit is contained in:
parent
721a58fa25
commit
d81526d2e0
@ -13,7 +13,6 @@ export interface UserInfo {
|
||||
user_type: string
|
||||
}
|
||||
|
||||
// 辅助函数:从cookie字符串中读取指定名称的cookie值
|
||||
function getCookie(name: string): string | null {
|
||||
const value = `; ${document.cookie}`
|
||||
const parts = value.split(`; ${name}=`)
|
||||
@ -35,41 +34,12 @@ export function getSessionCookie(): string | null {
|
||||
return getCookie('sid')
|
||||
}
|
||||
|
||||
// 从cookie中获取用户信息
|
||||
export function getUserInfoFromCookies(): UserInfo | null {
|
||||
const userId = getCookie('user_id')
|
||||
const fullName = getCookie('full_name') || ''
|
||||
const userImage = getCookie('user_image') || ''
|
||||
const systemUser = getCookie('system_user')
|
||||
|
||||
if (!userId || userId === 'Guest') {
|
||||
return null
|
||||
}
|
||||
|
||||
// 解析 full_name 为 first_name 和 last_name
|
||||
const nameParts = fullName.split(' ')
|
||||
const firstName = nameParts[0] || ''
|
||||
const lastName = nameParts.slice(1).join(' ') || ''
|
||||
|
||||
return {
|
||||
id: userId,
|
||||
username: userId,
|
||||
email: '',
|
||||
avatar: userImage,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
user_type: systemUser === 'yes' ? 'System User' : 'Website User'
|
||||
}
|
||||
}
|
||||
|
||||
// 检查cookie是否过期(通过检查session cookie是否存在)
|
||||
export function isCookieExpired(): boolean {
|
||||
const sessionCookie = getSessionCookie()
|
||||
return !sessionCookie
|
||||
return !getSessionCookie()
|
||||
}
|
||||
|
||||
export const loginApi = async (username: string, password: string): Promise<LoginResponse> => {
|
||||
// 使用表单编码格式,匹配后端期望的格式
|
||||
const formData = new URLSearchParams()
|
||||
formData.append('cmd', 'login')
|
||||
formData.append('usr', username)
|
||||
@ -92,49 +62,37 @@ export const loginApi = async (username: string, password: string): Promise<Logi
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// 后端登录成功会返回 message: "Logged In" 或 "No App"
|
||||
// "No App" 表示登录成功但用户类型是 Website User
|
||||
if (response.status === 200 && (data.message === 'Logged In' || data.message === 'No App')) {
|
||||
// 直接从登录响应构建用户信息
|
||||
// 解析 full_name 为 first_name 和 last_name
|
||||
const nameParts = (data.full_name || '').split(' ')
|
||||
const firstName = nameParts[0] || ''
|
||||
const lastName = nameParts.slice(1).join(' ') || ''
|
||||
|
||||
// 使用登录时的用户名作为用户ID和用户名
|
||||
const userInfo: UserInfo = {
|
||||
id: username,
|
||||
username: username,
|
||||
email: '',
|
||||
avatar: '',
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
first_name: nameParts[0] || '',
|
||||
last_name: nameParts.slice(1).join(' ') || '',
|
||||
user_type: data.message === 'No App' ? 'Website User' : 'System User'
|
||||
}
|
||||
|
||||
// 尝试获取更完整的用户信息(可选,如果失败也不影响登录)
|
||||
// 尝试获取更完整的用户信息
|
||||
try {
|
||||
// 等待一小段时间让session建立
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
const detailedUserInfo = await getUserInfoApi()
|
||||
// 如果API返回的用户信息有效(不是Guest),使用它
|
||||
if (detailedUserInfo.id && detailedUserInfo.id !== 'Guest') {
|
||||
return { message: data.message || 'Logged In', user: detailedUserInfo }
|
||||
return { message: data.message, user: detailedUserInfo }
|
||||
}
|
||||
} catch (error) {
|
||||
// API调用失败不影响登录,使用基本用户信息
|
||||
} catch {
|
||||
// API调用失败不影响登录
|
||||
}
|
||||
|
||||
// 使用从登录响应构建的用户信息
|
||||
return { message: data.message || 'Logged In', user: userInfo }
|
||||
} else {
|
||||
throw new Error(data.message || data.detail || data.exc || '登录失败')
|
||||
return { message: data.message, user: userInfo }
|
||||
}
|
||||
|
||||
throw new Error(data.message || data.detail || data.exc || '登录失败')
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export const getUserInfoApi = async (): Promise<UserInfo> => {
|
||||
try {
|
||||
const response = await fetch(`/api/action/jingrow.realtime.get_user_info`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@ -145,28 +103,19 @@ export const getUserInfoApi = async (): Promise<UserInfo> => {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
// 401或403表示cookie过期或未授权
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
const error = new Error('Cookie已过期,请重新登录')
|
||||
// @ts-ignore
|
||||
error.status = response.status
|
||||
throw error
|
||||
}
|
||||
// 其他错误,尝试从cookie获取
|
||||
const fallbackInfo = getUserInfoFromCookies()
|
||||
if (fallbackInfo) {
|
||||
return fallbackInfo
|
||||
}
|
||||
const errorData = await response.json().catch(() => ({}))
|
||||
throw new Error(errorData.detail || errorData.message || '获取用户信息失败')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// 根据Python代码,用户信息在 message 字段中,如果没有则使用整个 data
|
||||
const userInfo = data.message || data
|
||||
|
||||
// 格式化用户信息,匹配Python代码的逻辑
|
||||
const formattedUserInfo: UserInfo = {
|
||||
id: userInfo.user || userInfo.name || userInfo.username || '',
|
||||
username: userInfo.user || userInfo.name || userInfo.username || '',
|
||||
@ -177,26 +126,11 @@ export const getUserInfoApi = async (): Promise<UserInfo> => {
|
||||
user_type: userInfo.user_type || 'System User'
|
||||
}
|
||||
|
||||
// 如果格式化后的用户信息有效,返回它
|
||||
if (formattedUserInfo.id && formattedUserInfo.id !== 'Guest') {
|
||||
return formattedUserInfo
|
||||
}
|
||||
|
||||
// 如果格式化失败,尝试从cookie获取
|
||||
const fallbackInfo = getUserInfoFromCookies()
|
||||
if (fallbackInfo) {
|
||||
return fallbackInfo
|
||||
}
|
||||
|
||||
if (!formattedUserInfo.id || formattedUserInfo.id === 'Guest') {
|
||||
throw new Error('无法解析用户信息')
|
||||
} catch (error: any) {
|
||||
// 网络错误或其他异常,尝试从cookie获取
|
||||
const fallbackInfo = getUserInfoFromCookies()
|
||||
if (fallbackInfo) {
|
||||
return fallbackInfo
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
return formattedUserInfo
|
||||
}
|
||||
|
||||
// 登出
|
||||
@ -232,7 +166,6 @@ export interface SignupResponse {
|
||||
|
||||
export const signupApi = async (data: SignupRequest): Promise<SignupResponse> => {
|
||||
try {
|
||||
// 构建请求数据
|
||||
const requestData: any = {
|
||||
username: data.username,
|
||||
password: data.password
|
||||
@ -254,61 +187,27 @@ export const signupApi = async (data: SignupRequest): Promise<SignupResponse> =>
|
||||
body: JSON.stringify(requestData)
|
||||
})
|
||||
|
||||
// 先克隆响应,以便可以多次读取(如果需要)
|
||||
const responseClone = response.clone()
|
||||
const result = await response.json().catch(() => ({}))
|
||||
|
||||
let result: any = {}
|
||||
|
||||
// 尝试解析响应体
|
||||
try {
|
||||
result = await response.json()
|
||||
} catch (e) {
|
||||
// 如果JSON解析失败,尝试读取文本
|
||||
try {
|
||||
const text = await responseClone.text()
|
||||
return {
|
||||
success: false,
|
||||
error: text || `注册请求失败 (HTTP ${response.status})`
|
||||
}
|
||||
} catch (textError) {
|
||||
return {
|
||||
success: false,
|
||||
error: `注册请求失败 (HTTP ${response.status})`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果响应不成功,提取错误消息
|
||||
if (!response.ok) {
|
||||
// 后端可能返回 {"detail": "错误消息"} 或 {"message": {...}}
|
||||
const errorMsg = result.detail || result.message?.message || result.message || `注册请求失败 (HTTP ${response.status})`
|
||||
return { success: false, error: errorMsg }
|
||||
}
|
||||
|
||||
// 后端返回格式:result.message 是一个对象,包含 success 和 message
|
||||
const messageObj = result.message || result
|
||||
|
||||
// 检查业务逻辑错误(success 为 false)
|
||||
if (messageObj.success === false) {
|
||||
const errorMsg = messageObj.message || result.detail || '注册失败'
|
||||
return { success: false, error: errorMsg }
|
||||
return { success: false, error: messageObj.message || '注册失败' }
|
||||
}
|
||||
|
||||
// 注册成功后,尝试自动登录并获取用户信息
|
||||
// 注册成功后自动登录
|
||||
let userInfo: UserInfo | undefined = undefined
|
||||
|
||||
try {
|
||||
// 等待一小段时间让账户创建完成
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
|
||||
// 尝试登录
|
||||
const loginResponse = await loginApi(data.username, data.password)
|
||||
if (loginResponse.user) {
|
||||
userInfo = loginResponse.user
|
||||
}
|
||||
} catch (loginError) {
|
||||
// 登录失败不影响注册成功,只是没有用户信息
|
||||
console.warn('注册成功,但自动登录失败:', loginError)
|
||||
} catch {
|
||||
// 登录失败不影响注册成功
|
||||
}
|
||||
|
||||
return {
|
||||
@ -317,7 +216,6 @@ export const signupApi = async (data: SignupRequest): Promise<SignupResponse> =>
|
||||
user: userInfo
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 处理网络错误或其他异常
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || '网络错误,请检查网络连接后重试'
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { loginApi, getUserInfoApi, logoutApi, isCookieExpired } from '../api/auth'
|
||||
import { loginApi, getUserInfoApi, logoutApi, isCookieExpired, getSessionUser } from '../api/auth'
|
||||
import { setInitializingAuth } from '../utils/fetchInterceptor'
|
||||
|
||||
export interface User {
|
||||
id: string
|
||||
@ -19,19 +20,71 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
const isLoggedIn = computed(() => isAuthenticated.value && !!user.value)
|
||||
|
||||
// 判断是否是认证错误
|
||||
const isAuthError = (error: any): boolean => {
|
||||
return error?.status === 401 ||
|
||||
error?.status === 403 ||
|
||||
error?.message?.includes('过期') ||
|
||||
error?.message?.includes('Cookie已过期')
|
||||
}
|
||||
|
||||
// 设置用户状态(统一的状态更新方法)
|
||||
const setUserState = (userInfo: User) => {
|
||||
user.value = userInfo
|
||||
isAuthenticated.value = true
|
||||
localStorage.setItem('jingrow_user', JSON.stringify(userInfo))
|
||||
localStorage.setItem('jingrow_authenticated', 'true')
|
||||
}
|
||||
|
||||
// 清除用户状态
|
||||
const clearUserState = () => {
|
||||
user.value = null
|
||||
isAuthenticated.value = false
|
||||
localStorage.removeItem('jingrow_user')
|
||||
localStorage.removeItem('jingrow_authenticated')
|
||||
}
|
||||
|
||||
// 从localStorage恢复用户状态
|
||||
const restoreUserFromStorage = (): User | null => {
|
||||
const savedUser = localStorage.getItem('jingrow_user')
|
||||
const savedAuth = localStorage.getItem('jingrow_authenticated')
|
||||
|
||||
if (savedUser && savedAuth === 'true') {
|
||||
try {
|
||||
const parsedUser = JSON.parse(savedUser)
|
||||
user.value = parsedUser
|
||||
isAuthenticated.value = true
|
||||
return parsedUser
|
||||
} catch (error) {
|
||||
console.error('解析保存的用户信息失败:', error)
|
||||
clearUserState()
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 验证并更新用户信息
|
||||
const validateAndUpdateUser = async (): Promise<boolean> => {
|
||||
try {
|
||||
const userInfo = await getUserInfoApi()
|
||||
setUserState(userInfo)
|
||||
return true
|
||||
} catch (error: any) {
|
||||
console.error('验证用户信息失败:', error)
|
||||
if (isAuthError(error)) {
|
||||
clearUserState()
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const login = async (username: string, password: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await loginApi(username, password)
|
||||
|
||||
if (response.user) {
|
||||
user.value = response.user
|
||||
isAuthenticated.value = true
|
||||
|
||||
// 保存登录状态到localStorage
|
||||
localStorage.setItem('jingrow_user', JSON.stringify(response.user))
|
||||
localStorage.setItem('jingrow_authenticated', 'true')
|
||||
|
||||
setUserState(response.user)
|
||||
return { success: true, user: response.user }
|
||||
} else {
|
||||
return { success: false, error: response.message || '登录失败' }
|
||||
@ -50,71 +103,37 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
} catch (error) {
|
||||
console.error('登出错误:', error)
|
||||
} finally {
|
||||
// 清除本地状态
|
||||
user.value = null
|
||||
isAuthenticated.value = false
|
||||
localStorage.removeItem('jingrow_user')
|
||||
localStorage.removeItem('jingrow_authenticated')
|
||||
clearUserState()
|
||||
}
|
||||
}
|
||||
|
||||
const initAuth = async () => {
|
||||
// 检查user_id是否为Guest(cookie过期时后端会设置为Guest)
|
||||
const cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
|
||||
const userId = cookies.get('user_id')
|
||||
if (userId === 'Guest') {
|
||||
// user_id是Guest,说明cookie已过期,清除本地状态
|
||||
if (isAuthenticated.value) {
|
||||
await logout()
|
||||
}
|
||||
return
|
||||
}
|
||||
setInitializingAuth(true)
|
||||
|
||||
// 首先检查session cookie是否存在
|
||||
if (!isCookieExpired()) {
|
||||
try {
|
||||
const userInfo = await getUserInfoApi()
|
||||
user.value = userInfo
|
||||
isAuthenticated.value = true
|
||||
// 检查cookie状态
|
||||
const userId = getSessionUser()
|
||||
const hasSessionCookie = !isCookieExpired()
|
||||
const hasCookie = userId || hasSessionCookie
|
||||
|
||||
// 更新localStorage
|
||||
localStorage.setItem('jingrow_user', JSON.stringify(userInfo))
|
||||
localStorage.setItem('jingrow_authenticated', 'true')
|
||||
return
|
||||
} catch (error: any) {
|
||||
console.error('验证用户信息失败:', error)
|
||||
if (error.status === 401 || error.status === 403 || error.message?.includes('过期')) {
|
||||
await logout()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// 尝试从localStorage恢复状态(避免闪烁)
|
||||
const savedUser = restoreUserFromStorage()
|
||||
const hasSavedState = !!savedUser
|
||||
|
||||
// session cookie不存在,检查localStorage
|
||||
const savedUser = localStorage.getItem('jingrow_user')
|
||||
const savedAuth = localStorage.getItem('jingrow_authenticated')
|
||||
|
||||
if (savedUser && savedAuth === 'true') {
|
||||
try {
|
||||
user.value = JSON.parse(savedUser)
|
||||
isAuthenticated.value = true
|
||||
|
||||
// 验证用户信息是否仍然有效
|
||||
const userInfo = await getUserInfoApi()
|
||||
user.value = userInfo
|
||||
localStorage.setItem('jingrow_user', JSON.stringify(userInfo))
|
||||
} catch (error: any) {
|
||||
console.error('验证用户信息失败:', error)
|
||||
if (error.status === 401 || error.status === 403 || error.message?.includes('过期')) {
|
||||
await logout()
|
||||
} else {
|
||||
logout()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果既没有cookie也没有保存的状态,清除认证
|
||||
if (!hasCookie && !hasSavedState) {
|
||||
if (isAuthenticated.value) {
|
||||
await logout()
|
||||
clearUserState()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 如果有cookie或保存的状态,尝试验证
|
||||
if (hasCookie || hasSavedState) {
|
||||
await validateAndUpdateUser()
|
||||
}
|
||||
} finally {
|
||||
setInitializingAuth(false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,12 +142,10 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
try {
|
||||
const userInfo = await getUserInfoApi()
|
||||
user.value = userInfo
|
||||
localStorage.setItem('jingrow_user', JSON.stringify(userInfo))
|
||||
setUserState(userInfo)
|
||||
} catch (error: any) {
|
||||
console.error('更新用户信息失败:', error)
|
||||
// 如果是401/403错误,说明cookie已过期
|
||||
if (error.status === 401 || error.status === 403 || error.message?.includes('过期')) {
|
||||
if (isAuthError(error)) {
|
||||
await logout()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,25 @@
|
||||
// 保存原始的fetch函数
|
||||
const originalFetch = window.fetch
|
||||
|
||||
// 标记是否正在初始化认证(避免在initAuth期间误触发登出)
|
||||
let isInitializingAuth = false
|
||||
|
||||
// 导出函数,允许外部设置初始化状态
|
||||
export function setInitializingAuth(value: boolean) {
|
||||
isInitializingAuth = value
|
||||
}
|
||||
|
||||
// 包装fetch函数,添加401/403错误处理
|
||||
window.fetch = async function(...args: Parameters<typeof fetch>): Promise<Response> {
|
||||
const response = await originalFetch(...args)
|
||||
|
||||
// 检查响应状态码(仅在401/403时处理)
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
// 如果正在初始化认证,不自动登出(让initAuth自己处理)
|
||||
if (isInitializingAuth) {
|
||||
return response
|
||||
}
|
||||
|
||||
// 延迟导入,确保pinia已初始化
|
||||
try {
|
||||
const { useAuthStore } = await import('../stores/auth')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user