Commit de73cb84 by Liu

refactor: 优化工具函数、首页和路由处理逻辑

parent 1084a561
...@@ -74,6 +74,19 @@ export function safeLocalStorageSetItem(key: string, value: string): void { ...@@ -74,6 +74,19 @@ export function safeLocalStorageSetItem(key: string, value: string): void {
} }
} }
/**
* 安全地清除 localStorage
* 在无痕模式或存储被禁用时静默失败
*/
export function safeLocalStorageClear(): void {
try {
localStorage.clear()
}
catch {
// 在无痕模式或存储被禁用时,静默失败
}
}
const USER_ROLES_STORAGE_KEY = '__USER_ROLES__' const USER_ROLES_STORAGE_KEY = '__USER_ROLES__'
/** /**
...@@ -294,9 +307,9 @@ export function getUserRolesForApi(): string[] { ...@@ -294,9 +307,9 @@ export function getUserRolesForApi(): string[] {
export async function waitForToken(maxWaitTime = 3000): Promise<boolean> { export async function waitForToken(maxWaitTime = 3000): Promise<boolean> {
const startTime = Date.now() const startTime = Date.now()
let initialToken = '' let initialToken = ''
// 先获取初始 token 值 // 先获取初始 token 值(使用安全函数)
try { try {
const initialTokenStr = window.localStorage.getItem('__TOKEN__') || '""' const initialTokenStr = safeLocalStorageGetItem('__TOKEN__') || '""'
initialToken = JSON.parse(initialTokenStr) || '' initialToken = JSON.parse(initialTokenStr) || ''
} }
catch { catch {
...@@ -306,7 +319,7 @@ export async function waitForToken(maxWaitTime = 3000): Promise<boolean> { ...@@ -306,7 +319,7 @@ export async function waitForToken(maxWaitTime = 3000): Promise<boolean> {
// 如果初始没有 token,等待 token 出现(从无到有,说明登录完成) // 如果初始没有 token,等待 token 出现(从无到有,说明登录完成)
if (!initialToken) { if (!initialToken) {
while (Date.now() - startTime < maxWaitTime) { while (Date.now() - startTime < maxWaitTime) {
const tokenStr = window.localStorage.getItem('__TOKEN__') || '""' const tokenStr = safeLocalStorageGetItem('__TOKEN__') || '""'
let token = '' let token = ''
try { try {
token = JSON.parse(tokenStr) || '' token = JSON.parse(tokenStr) || ''
...@@ -331,7 +344,7 @@ export async function waitForToken(maxWaitTime = 3000): Promise<boolean> { ...@@ -331,7 +344,7 @@ export async function waitForToken(maxWaitTime = 3000): Promise<boolean> {
let tokenChanged = false let tokenChanged = false
const checkTokenChange = () => { const checkTokenChange = () => {
try { try {
const currentTokenStr = window.localStorage.getItem('__TOKEN__') || '""' const currentTokenStr = safeLocalStorageGetItem('__TOKEN__') || '""'
const currentToken = JSON.parse(currentTokenStr) || '' const currentToken = JSON.parse(currentTokenStr) || ''
if (currentToken && currentToken !== initialToken) { if (currentToken && currentToken !== initialToken) {
tokenChanged = true tokenChanged = true
......
...@@ -14,7 +14,7 @@ import SdreamLoading from '@/components/SdreamLoading' ...@@ -14,7 +14,7 @@ import SdreamLoading from '@/components/SdreamLoading'
import { fetchLoginByToken, fetchLoginByUid } from '@/api/common' import { fetchLoginByToken, fetchLoginByUid } from '@/api/common'
import { fetchSessionConversationId } from '@/api/conversation' import { fetchSessionConversationId } from '@/api/conversation'
import { fetchCheckTokenApi } from '@/api/chat' import { fetchCheckTokenApi } from '@/api/chat'
import { getUserRolesFromRoute, safeLocalStorageGetItem, safeSessionStorageGetItem, safeSessionStorageRemoveItem, safeSessionStorageSetItem, waitForToken } from '@/lib/utils' import { getUserRolesFromRoute, safeLocalStorageGetItem, safeLocalStorageSetItem, safeSessionStorageGetItem, safeSessionStorageRemoveItem, safeSessionStorageSetItem, waitForToken } from '@/lib/utils'
// 从 localStorage 获取 token 的辅助函数 // 从 localStorage 获取 token 的辅助函数
function getTokenFromStorage(): string | null { function getTokenFromStorage(): string | null {
...@@ -281,12 +281,32 @@ export const Home: React.FC = () => { ...@@ -281,12 +281,32 @@ export const Home: React.FC = () => {
// 使用 replace 避免产生新的历史记录 // 使用 replace 避免产生新的历史记录
window.history.replaceState({}, '', currentUrl.toString()) window.history.replaceState({}, '', currentUrl.toString())
} }
setToken(res.data.token) // 尝试使用 useLocalStorageState 设置 token,如果失败则使用降级方案
try {
setToken(res.data.token)
}
catch (error) {
// localStorage 被阻止时,使用安全函数降级存储
console.warn('setToken 失败,使用降级方案:', error)
safeLocalStorageSetItem('__TOKEN__', JSON.stringify(res.data.token))
}
if (res.data.userName) { if (res.data.userName) {
setUserName(res.data.userName) try {
setUserName(res.data.userName)
}
catch {
// userName 存储失败时使用降级方案
safeLocalStorageSetItem('__USER_NAME__', JSON.stringify(res.data.userName))
}
} }
// 确保 token 已写入 localStorage(useLocalStorageState 是同步的,但为了保险起见,使用微任务确保写入完成) // 确保 token 已写入 localStorage(useLocalStorageState 是同步的,但为了保险起见,使用微任务确保写入完成)
await new Promise(resolve => setTimeout(resolve, 0)) await new Promise(resolve => setTimeout(resolve, 0))
// 验证 token 是否真正写入,如果没有则使用降级方案
const verifyToken = getTokenFromStorage()
if (!verifyToken) {
// 如果 useLocalStorageState 写入失败,使用安全函数再次尝试
safeLocalStorageSetItem('__TOKEN__', JSON.stringify(res.data.token))
}
window.dispatchEvent( window.dispatchEvent(
new StorageEvent('storage', { new StorageEvent('storage', {
key: '__TOKEN__', key: '__TOKEN__',
...@@ -358,12 +378,32 @@ export const Home: React.FC = () => { ...@@ -358,12 +378,32 @@ export const Home: React.FC = () => {
currentUrl.searchParams.delete('toolId') currentUrl.searchParams.delete('toolId')
window.history.replaceState({}, '', currentUrl.toString()) window.history.replaceState({}, '', currentUrl.toString())
} }
setToken(res.data.token) // 尝试使用 useLocalStorageState 设置 token,如果失败则使用降级方案
try {
setToken(res.data.token)
}
catch (error) {
// localStorage 被阻止时,使用安全函数降级存储
console.warn('setToken 失败,使用降级方案:', error)
safeLocalStorageSetItem('__TOKEN__', JSON.stringify(res.data.token))
}
if (res.data.userName) { if (res.data.userName) {
setUserName(res.data.userName) try {
setUserName(res.data.userName)
}
catch {
// userName 存储失败时使用降级方案
safeLocalStorageSetItem('__USER_NAME__', JSON.stringify(res.data.userName))
}
} }
// 确保 token 已写入 localStorage(useLocalStorageState 是同步的,但为了保险起见,使用微任务确保写入完成) // 确保 token 已写入 localStorage(useLocalStorageState 是同步的,但为了保险起见,使用微任务确保写入完成)
await new Promise(resolve => setTimeout(resolve, 0)) await new Promise(resolve => setTimeout(resolve, 0))
// 验证 token 是否真正写入,如果没有则使用降级方案
const verifyToken = getTokenFromStorage()
if (!verifyToken) {
// 如果 useLocalStorageState 写入失败,使用安全函数再次尝试
safeLocalStorageSetItem('__TOKEN__', JSON.stringify(res.data.token))
}
// 主动触发 storage 事件,确保其他组件能监听到变化 // 主动触发 storage 事件,确保其他组件能监听到变化
window.dispatchEvent( window.dispatchEvent(
new StorageEvent('storage', { new StorageEvent('storage', {
......
...@@ -5,7 +5,6 @@ import { clearCurrentTacticsConversation, setCurrentTacticsConversation } from ' ...@@ -5,7 +5,6 @@ import { clearCurrentTacticsConversation, setCurrentTacticsConversation } from '
import { useAppDispatch, useAppSelector } from '@/store/hook' import { useAppDispatch, useAppSelector } from '@/store/hook'
import { setIsAsking } from '@/store/chatSlice' import { setIsAsking } from '@/store/chatSlice'
import type { RootState } from '@/store' import type { RootState } from '@/store'
import { safeLocalStorageGetItem } from '@/lib/utils'
export function withRouteChangeHandler(WrappedComponent: React.ComponentType) { export function withRouteChangeHandler(WrappedComponent: React.ComponentType) {
let beforeLocationPathName = '' let beforeLocationPathName = ''
...@@ -38,7 +37,7 @@ export function withRouteChangeHandler(WrappedComponent: React.ComponentType) { ...@@ -38,7 +37,7 @@ export function withRouteChangeHandler(WrappedComponent: React.ComponentType) {
if (location.pathname === '/') { if (location.pathname === '/') {
// 如果 localStorage 中有 conversationId,不清除,让恢复逻辑处理 // 如果 localStorage 中有 conversationId,不清除,让恢复逻辑处理
// 这样可以保留上一次的历史记录 // 这样可以保留上一次的历史记录
const savedConversationId = safeLocalStorageGetItem('currentConversationId') const savedConversationId = window.localStorage.getItem('currentConversationId')
if (!savedConversationId) { if (!savedConversationId) {
dispatch(clearCurrentConversation()) dispatch(clearCurrentConversation())
} }
......
...@@ -7,6 +7,7 @@ import type { ...@@ -7,6 +7,7 @@ import type {
} from 'axios' } from 'axios'
import type { EnvConfKey } from '@/config/env' import type { EnvConfKey } from '@/config/env'
import { envConf } from '@/config/env' import { envConf } from '@/config/env'
import { safeLocalStorageClear, safeLocalStorageGetItem } from '@/lib/utils'
export interface Response<T> { export interface Response<T> {
data: T data: T
...@@ -31,7 +32,8 @@ service.interceptors.request.use( ...@@ -31,7 +32,8 @@ service.interceptors.request.use(
(config: any) => { (config: any) => {
let token = '' let token = ''
try { try {
const tokenStr = window.localStorage.getItem('__TOKEN__') // 使用安全函数获取 token,避免在第三方页面中访问 localStorage 被阻止
const tokenStr = safeLocalStorageGetItem('__TOKEN__')
if (tokenStr) { if (tokenStr) {
// useLocalStorageState 会将值序列化为 JSON,需要解析 // useLocalStorageState 会将值序列化为 JSON,需要解析
token = JSON.parse(tokenStr) || '' token = JSON.parse(tokenStr) || ''
...@@ -70,8 +72,8 @@ service.interceptors.response.use( ...@@ -70,8 +72,8 @@ service.interceptors.response.use(
return response.data return response.data
} }
else if (code === '00000005') { else if (code === '00000005') {
// 处理登录失效 // 处理登录失效(使用安全函数,避免在第三方页面中被阻止)
window.localStorage.clear() safeLocalStorageClear()
} }
else { else {
return Promise.reject(new Error(message)) return Promise.reject(new Error(message))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment