|
|
@@ -7,6 +7,12 @@ export function useAuth() {
|
|
|
const [token, setToken] = useState<string>('')
|
|
|
const [currentUserId, setCurrentUserId] = useState<number | null>(null)
|
|
|
const [currentUserName, setCurrentUserName] = useState<string>('')
|
|
|
+ /** 启动时本地有 auth_token,正在请求 /users/me 校验;校验结束前不展示登录表单 */
|
|
|
+ const [isRestoringSession, setIsRestoringSession] = useState(
|
|
|
+ () => typeof localStorage !== 'undefined' && !!localStorage.getItem('auth_token')
|
|
|
+ )
|
|
|
+ /** 会话恢复失败(过期/无效)时在登录页展示的提示 */
|
|
|
+ const [sessionExpiredMessage, setSessionExpiredMessage] = useState('')
|
|
|
|
|
|
// 退出登录函数
|
|
|
const handleLogout = useCallback(() => {
|
|
|
@@ -22,95 +28,109 @@ export function useAuth() {
|
|
|
setIsLoggedIn(false)
|
|
|
setCurrentUserId(null)
|
|
|
setCurrentUserName('')
|
|
|
+ setSessionExpiredMessage('')
|
|
|
|
|
|
logger.info('App: User logged out')
|
|
|
}, [])
|
|
|
|
|
|
- // 应用启动时检查是否有保存的 token,尝试自动登录
|
|
|
+ // 应用启动时仅执行一次:若有保存的 token,用 /users/me 校验后再决定进入主界面或登录页
|
|
|
useEffect(() => {
|
|
|
const savedToken = localStorage.getItem('auth_token')
|
|
|
const savedUserId = localStorage.getItem('user_id')
|
|
|
const savedUserName = localStorage.getItem('user_name')
|
|
|
-
|
|
|
- logger.info('useAuth: Checking for saved token on app start', {
|
|
|
- hasToken: !!savedToken,
|
|
|
- tokenLength: savedToken?.length || 0,
|
|
|
- savedUserId,
|
|
|
- savedUserName,
|
|
|
- isLoggedIn
|
|
|
- })
|
|
|
-
|
|
|
- if (savedToken && !isLoggedIn) {
|
|
|
- const validateAndLogin = async () => {
|
|
|
- logger.info('useAuth: Starting auto login validation', {
|
|
|
- tokenLength: savedToken.length,
|
|
|
- tokenPrefix: savedToken.substring(0, 20) + '...'
|
|
|
- })
|
|
|
-
|
|
|
- try {
|
|
|
- const userInfo = await api.getCurrentUserInfo(savedToken)
|
|
|
- if (userInfo) {
|
|
|
- logger.info('useAuth: Auto login validation successful', {
|
|
|
- userId: userInfo.id,
|
|
|
- hasUserInfo: true
|
|
|
- })
|
|
|
-
|
|
|
- setToken(savedToken)
|
|
|
- setIsLoggedIn(true)
|
|
|
-
|
|
|
- if (userInfo.id) {
|
|
|
- setCurrentUserId(userInfo.id)
|
|
|
- logger.info('useAuth: Set user ID from userInfo', { userId: userInfo.id })
|
|
|
- } else if (savedUserId) {
|
|
|
- const userId = parseInt(savedUserId, 10)
|
|
|
- setCurrentUserId(userId)
|
|
|
- logger.info('useAuth: Set user ID from saved value', { userId })
|
|
|
- }
|
|
|
-
|
|
|
- const userName = userInfo.name || userInfo.full_name || userInfo.username || userInfo.english_name || savedUserName || ''
|
|
|
- if (userName) {
|
|
|
- setCurrentUserName(userName)
|
|
|
- logger.info('useAuth: Set user name', { userName })
|
|
|
- }
|
|
|
-
|
|
|
- logger.info('useAuth: Auto login successful', {
|
|
|
- userId: userInfo.id || savedUserId,
|
|
|
- userName
|
|
|
- })
|
|
|
|
|
|
- if (window.electron?.ipcRenderer) {
|
|
|
- window.electron.ipcRenderer.send('login-success')
|
|
|
- logger.info('useAuth: Sent login-success to main process (auto login)')
|
|
|
- }
|
|
|
- } else {
|
|
|
- logger.warn('useAuth: Auto login failed, token invalid - no user info returned')
|
|
|
- localStorage.removeItem('auth_token')
|
|
|
- localStorage.removeItem('user_id')
|
|
|
- localStorage.removeItem('user_name')
|
|
|
+ if (!savedToken) {
|
|
|
+ logger.info('useAuth: No saved token on mount, skip session restore')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let cancelled = false
|
|
|
+
|
|
|
+ const validateAndLogin = async () => {
|
|
|
+ logger.info('useAuth: Starting session restore (validate token via /users/me)', {
|
|
|
+ tokenLength: savedToken.length,
|
|
|
+ tokenPrefix: savedToken.substring(0, 20) + '...'
|
|
|
+ })
|
|
|
+
|
|
|
+ try {
|
|
|
+ const userInfo = await api.getCurrentUserInfo(savedToken)
|
|
|
+ if (cancelled) return
|
|
|
+
|
|
|
+ if (userInfo) {
|
|
|
+ setSessionExpiredMessage('')
|
|
|
+ logger.info('useAuth: Session restore validation successful', {
|
|
|
+ userId: userInfo.id,
|
|
|
+ hasUserInfo: true
|
|
|
+ })
|
|
|
+
|
|
|
+ setToken(savedToken)
|
|
|
+ setIsLoggedIn(true)
|
|
|
+
|
|
|
+ if (userInfo.id) {
|
|
|
+ setCurrentUserId(userInfo.id)
|
|
|
+ logger.info('useAuth: Set user ID from userInfo', { userId: userInfo.id })
|
|
|
+ } else if (savedUserId) {
|
|
|
+ const userId = parseInt(savedUserId, 10)
|
|
|
+ setCurrentUserId(userId)
|
|
|
+ logger.info('useAuth: Set user ID from saved value', { userId })
|
|
|
}
|
|
|
- } catch (error) {
|
|
|
- logger.warn('useAuth: Auto login failed, token invalid or expired', {
|
|
|
- error: error instanceof Error ? {
|
|
|
- name: error.name,
|
|
|
- message: error.message,
|
|
|
- stack: error.stack
|
|
|
- } : String(error)
|
|
|
+
|
|
|
+ const userName =
|
|
|
+ userInfo.name ||
|
|
|
+ userInfo.full_name ||
|
|
|
+ userInfo.username ||
|
|
|
+ userInfo.english_name ||
|
|
|
+ savedUserName ||
|
|
|
+ ''
|
|
|
+ if (userName) {
|
|
|
+ setCurrentUserName(userName)
|
|
|
+ logger.info('useAuth: Set user name', { userName })
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info('useAuth: Session restore successful', {
|
|
|
+ userId: userInfo.id || savedUserId,
|
|
|
+ userName
|
|
|
})
|
|
|
+
|
|
|
+ if (window.electron?.ipcRenderer) {
|
|
|
+ window.electron.ipcRenderer.send('login-success')
|
|
|
+ logger.info('useAuth: Sent login-success to main process (session restore)')
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ logger.warn('useAuth: Session restore failed, token invalid - no user info returned')
|
|
|
localStorage.removeItem('auth_token')
|
|
|
localStorage.removeItem('user_id')
|
|
|
localStorage.removeItem('user_name')
|
|
|
+ setSessionExpiredMessage('身份已过期,请重新登录')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (cancelled) return
|
|
|
+ logger.warn('useAuth: Session restore failed, token invalid or expired', {
|
|
|
+ error:
|
|
|
+ error instanceof Error
|
|
|
+ ? {
|
|
|
+ name: error.name,
|
|
|
+ message: error.message,
|
|
|
+ stack: error.stack
|
|
|
+ }
|
|
|
+ : String(error)
|
|
|
+ })
|
|
|
+ localStorage.removeItem('auth_token')
|
|
|
+ localStorage.removeItem('user_id')
|
|
|
+ localStorage.removeItem('user_name')
|
|
|
+ setSessionExpiredMessage('身份已过期,请重新登录')
|
|
|
+ } finally {
|
|
|
+ if (!cancelled) {
|
|
|
+ setIsRestoringSession(false)
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- validateAndLogin()
|
|
|
- } else {
|
|
|
- if (!savedToken) {
|
|
|
- logger.info('useAuth: No saved token found, skipping auto login')
|
|
|
- } else if (isLoggedIn) {
|
|
|
- logger.info('useAuth: Already logged in, skipping auto login')
|
|
|
}
|
|
|
}
|
|
|
- }, [isLoggedIn])
|
|
|
+
|
|
|
+ validateAndLogin()
|
|
|
+ return () => {
|
|
|
+ cancelled = true
|
|
|
+ }
|
|
|
+ }, [])
|
|
|
|
|
|
// 全局错误处理:当 API 返回 401 时自动退出登录
|
|
|
useEffect(() => {
|
|
|
@@ -129,6 +149,7 @@ export function useAuth() {
|
|
|
setIsLoggedIn(false)
|
|
|
setCurrentUserId(null)
|
|
|
setCurrentUserName('')
|
|
|
+ setSessionExpiredMessage('登录已过期,请重新登录')
|
|
|
}
|
|
|
|
|
|
return response
|
|
|
@@ -141,6 +162,8 @@ export function useAuth() {
|
|
|
|
|
|
// 登录处理函数
|
|
|
const handleLogin = useCallback(async (accessToken: string, user?: { id: number; [key: string]: any }) => {
|
|
|
+ setSessionExpiredMessage('')
|
|
|
+
|
|
|
logger.info('useAuth: handleLogin called', {
|
|
|
hasToken: !!accessToken,
|
|
|
tokenLength: accessToken.length,
|
|
|
@@ -263,6 +286,8 @@ export function useAuth() {
|
|
|
currentUserId,
|
|
|
currentUserName,
|
|
|
handleLogin,
|
|
|
- handleLogout
|
|
|
+ handleLogout,
|
|
|
+ isRestoringSession,
|
|
|
+ sessionExpiredMessage
|
|
|
}
|
|
|
}
|