feat(auth): 登录态改为滑动续期(后台 24h/7d,打手 7d/30d)

This commit is contained in:
yml2213
2026-10-25 13:28:58 +08:00
parent 81edf530db
commit 2fb7aae13f
31 changed files with 510 additions and 64 deletions
+42 -2
View File
@@ -46,11 +46,14 @@ import {
} from '@/services/admin'
import {
clearAdminSession,
expireAdminSession,
getAdminDisplayName,
getAdminRole,
getAdminSessionExpiryDelay,
getAdminToken,
getAdminTokenExpiresAt,
getAdminUsername,
isAdminSessionStorageKey,
setAdminDisplayName,
setAdminPermissions,
} from '@/utils/admin-auth'
@@ -77,7 +80,7 @@ export default function AdminLayout() {
)
const role = getAdminRole()
const username = getAdminUsername() || 'admin'
const expiresAt = getAdminTokenExpiresAt()
const [expiresAt, setExpiresAt] = useState(() => getAdminTokenExpiresAt())
const [soundEnabled, setSoundEnabled] = useState(
() => window.localStorage.getItem(NOTIFICATION_SOUND_KEY) === '1',
)
@@ -96,6 +99,43 @@ export default function AdminLayout() {
}
}, [adminSessionQuery.data])
useEffect(() => {
const navigateToLogin = () => {
if (!window.location.hash.startsWith('#/admin/login')) {
void navigate('/admin/login', { replace: true })
}
}
const handleSessionExpired = () => navigateToLogin()
const handleSessionRenewed = () => setExpiresAt(getAdminTokenExpiresAt())
const handleStorage = (event: StorageEvent) => {
if (!isAdminSessionStorageKey(event.key)) return
if (event.newValue === null) {
expireAdminSession()
navigateToLogin()
return
}
handleSessionRenewed()
}
window.addEventListener('admin-session-expired', handleSessionExpired)
window.addEventListener('admin-session-renewed', handleSessionRenewed)
window.addEventListener('storage', handleStorage)
return () => {
window.removeEventListener('admin-session-expired', handleSessionExpired)
window.removeEventListener('admin-session-renewed', handleSessionRenewed)
window.removeEventListener('storage', handleStorage)
}
}, [navigate])
useEffect(() => {
const delay = getAdminSessionExpiryDelay(expiresAt)
if (delay === null) return
const timer = window.setTimeout(() => {
expireAdminSession()
}, delay)
return () => window.clearTimeout(timer)
}, [expiresAt])
const displayName =
adminSessionQuery.data?.data.user?.displayName || getAdminDisplayName() || username
@@ -121,7 +161,7 @@ export default function AdminLayout() {
onEvent: (event) => applyAdminRealtimeEvent(event, queryClient),
// 会话过期时清登录态回登录页,避免 SSE 带着失效 token 无限重连刷 401。
onAuthExpired: () => {
clearAdminSession()
expireAdminSession()
void navigate('/admin/login', { replace: true, state: { from: location } })
},
})