Commit 4682e40f by Liu

fix:权限参数

parent daeee270
import http from '@/utils/request' import http from '@/utils/request'
import { safeSessionStorageGetItem } from '@/lib/utils' import { normalizeToolIdForApi, safeSessionStorageGetItem } from '@/lib/utils'
/** /**
* 查询用户历史会话列表 * 查询用户历史会话列表
...@@ -37,32 +37,31 @@ export function fetchDeleteUserConversation(conversationIdList: string[]) { ...@@ -37,32 +37,31 @@ export function fetchDeleteUserConversation(conversationIdList: string[]) {
/** /**
* 获取历史会话ID * 获取历史会话ID
* @params * 制度活化:不传 toolId/busiId(空字符串);工具模式:以 session 或入参为准。
* 若入参显式包含 toolId 或 busiId(含空串),优先于 session,避免轮询把「默认第 0 个工具」误写入后再带上。
*/ */
export async function fetchSessionConversationId<T>(data: T) { export async function fetchSessionConversationId<T>(data: T) {
const payload = (data || {}) as Record<string, any> const payload = (data || {}) as Record<string, any>
const fromSession = normalizeToolIdForApi(safeSessionStorageGetItem('currentToolId'))
// 首次进入时 `currentToolId` 可能还没被 `ChatEditor` 写入 sessionStorage。 const hasExplicitTool
// 这里做一个极短轮询,避免第一次请求 `toolId/busiId` 读到空字符串。 = Object.prototype.hasOwnProperty.call(payload, 'toolId')
let sessionToolId = safeSessionStorageGetItem('currentToolId') || '' || Object.prototype.hasOwnProperty.call(payload, 'busiId')
if (!sessionToolId) { const explicitRaw = Object.prototype.hasOwnProperty.call(payload, 'toolId')
const maxWaitMs = 600 ? payload.toolId
const intervalMs = 100 : Object.prototype.hasOwnProperty.call(payload, 'busiId')
const start = Date.now() ? payload.busiId
while (Date.now() - start < maxWaitMs) { : undefined
await new Promise(resolve => setTimeout(resolve, intervalMs))
sessionToolId = safeSessionStorageGetItem('currentToolId') || '' const resolved = hasExplicitTool
if (sessionToolId) { ? normalizeToolIdForApi(explicitRaw)
break : fromSession
}
}
}
const requestData = { const requestData = {
...payload, ...payload,
busiType: '01', busiType: payload.busiType ?? '01',
busiId: sessionToolId, busiId: resolved,
toolId: sessionToolId, toolId: resolved,
} }
return http.post('/conversation/api/conversation/mobile/v1/get_user_conversation_session', requestData) return http.post('/conversation/api/conversation/mobile/v1/get_user_conversation_session', requestData)
} }
...@@ -11,7 +11,7 @@ import { useAppDispatch, useAppSelector } from '@/store/hook' ...@@ -11,7 +11,7 @@ import { useAppDispatch, useAppSelector } from '@/store/hook'
import { clearCurrentToolId, setCurrentToolId } from '@/store/conversationSlice' import { clearCurrentToolId, setCurrentToolId } from '@/store/conversationSlice'
import { setIsAsking } from '@/store/chatSlice' import { setIsAsking } from '@/store/chatSlice'
import { fetchToolList } from '@/api/home' import { fetchToolList } from '@/api/home'
import { getUserRolesForApi, safeSessionStorageGetItem, safeSessionStorageRemoveItem, safeSessionStorageSetItem } from '@/lib/utils' import { getUserRolesForApi, normalizeToolIdForApi, safeSessionStorageGetItem, safeSessionStorageRemoveItem, safeSessionStorageSetItem } from '@/lib/utils'
import { fetchSessionConversationId, fetchUserQaRecordPage } from '@/api/conversation' import { fetchSessionConversationId, fetchUserQaRecordPage } from '@/api/conversation'
interface ChatEditorProps { interface ChatEditorProps {
...@@ -182,18 +182,19 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, ...@@ -182,18 +182,19 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
console.log('[chat-toolid-log] ChatEditor currentToolId:', currentToolId, '| sessionToolId:', sessionToolId, '| fromCollect:', fromCollect) console.log('[chat-toolid-log] ChatEditor currentToolId:', currentToolId, '| sessionToolId:', sessionToolId, '| fromCollect:', fromCollect)
// 关键修复:首次进入/切换路由时,Redux 里的 toolId 可能先到,但 sessionToolId 还没同步。 // 关键修复:首次进入/切换路由时,Redux 里的 toolId 可能先到,但 sessionToolId 还没同步。
// 此时不应该“清空 Redux / 写空缓存”,而是用 Redux 回填 sessionStorage,避免出现“高亮有值但缓存为空”。 // 此时不应该“清空 Redux / 写空缓存”,而是用 Redux 回填 sessionStorage,避免出现“高亮有值但缓存为空”。
if (currentToolId && !sessionToolId && !fromCollect) { const normalizedReduxToolId = normalizeToolIdForApi(currentToolId)
setSelectedToolId(currentToolId) if (normalizedReduxToolId && !sessionToolId && !fromCollect) {
setSelectedToolId(normalizedReduxToolId)
setIsToolBtnActive(false) setIsToolBtnActive(false)
// 用 Redux 回填缓存,确保后续接口/其他组件读取到一致的 toolId // 用 Redux 回填缓存,确保后续接口/其他组件读取到一致的 toolId
safeSessionStorageSetItem('currentToolId', String(currentToolId)) safeSessionStorageSetItem('currentToolId', normalizedReduxToolId)
if (toolList.length > 0) { if (toolList.length > 0) {
const currentTool = toolList.find(tool => String(tool.toolId) === String(currentToolId)) const currentTool = toolList.find(tool => String(tool.toolId) === String(normalizedReduxToolId))
if (currentTool?.toolName) { if (currentTool?.toolName) {
safeSessionStorageSetItem('currentToolName', currentTool.toolName) safeSessionStorageSetItem('currentToolName', currentTool.toolName)
} }
} }
setSessionToolId(String(currentToolId)) setSessionToolId(normalizedReduxToolId)
return return
} }
......
...@@ -8,6 +8,21 @@ export function cn(...inputs: ClassValue[]) { ...@@ -8,6 +8,21 @@ export function cn(...inputs: ClassValue[]) {
} }
/** /**
* 将 toolId 规范为合法字符串。
* 避免 `sessionStorage.setItem(k, undefined)`、Redux 误存、`?toolId=undefined` 等产生字面量 "undefined"。
*/
export function normalizeToolIdForApi(value: string | number | undefined | null): string {
if (value == null) {
return ''
}
const t = String(value).trim()
if (t === '' || t === 'undefined' || t === 'null') {
return ''
}
return t
}
/**
* 安全地访问 sessionStorage * 安全地访问 sessionStorage
* 在无痕模式或存储被禁用时返回 null,避免抛出错误 * 在无痕模式或存储被禁用时返回 null,避免抛出错误
*/ */
...@@ -16,6 +31,10 @@ export function safeSessionStorageGetItem(key: string): string | null { ...@@ -16,6 +31,10 @@ export function safeSessionStorageGetItem(key: string): string | null {
const result = sessionStorage.getItem(key) const result = sessionStorage.getItem(key)
if (key === 'currentToolId') { if (key === 'currentToolId') {
console.log('[chat-toolid-log] safeGetItem:', result, '| caller line') console.log('[chat-toolid-log] safeGetItem:', result, '| caller line')
if (result != null && normalizeToolIdForApi(result) === '') {
sessionStorage.removeItem(key)
return null
}
} }
return result return result
} }
...@@ -33,6 +52,13 @@ export function safeSessionStorageSetItem(key: string, value: string): void { ...@@ -33,6 +52,13 @@ export function safeSessionStorageSetItem(key: string, value: string): void {
try { try {
if (key === 'currentToolId') { if (key === 'currentToolId') {
console.log('[chat-toolid-log] safeSetItem:', value, '| caller line') console.log('[chat-toolid-log] safeSetItem:', value, '| caller line')
const normalized = normalizeToolIdForApi(value)
if (!normalized) {
sessionStorage.removeItem(key)
return
}
sessionStorage.setItem(key, normalized)
return
} }
sessionStorage.setItem(key, value) sessionStorage.setItem(key, value)
} }
......
...@@ -15,7 +15,7 @@ import { fetchSessionConversationId, fetchUserQaRecordPage } from '@/api/convers ...@@ -15,7 +15,7 @@ import { fetchSessionConversationId, fetchUserQaRecordPage } from '@/api/convers
import { fetchCheckTokenApi, fetchStreamResponse } from '@/api/chat' import { fetchCheckTokenApi, fetchStreamResponse } from '@/api/chat'
import { fetchToolList } from '@/api/home' import { fetchToolList } from '@/api/home'
import { clearCurrentToolId, clearShouldSendQuestion, fetchConversations, setCurrentToolId } from '@/store/conversationSlice' import { clearCurrentToolId, clearShouldSendQuestion, fetchConversations, setCurrentToolId } from '@/store/conversationSlice'
import { getUserRolesForApi, safeSessionStorageGetItem, waitForToken } from '@/lib/utils' import { getUserRolesForApi, normalizeToolIdForApi, safeSessionStorageGetItem, safeSessionStorageSetItem, waitForToken } from '@/lib/utils'
import type { RootState } from '@/store' import type { RootState } from '@/store'
import { useAppDispatch, useAppSelector } from '@/store/hook' import { useAppDispatch, useAppSelector } from '@/store/hook'
import ScrollBtoIcon from '@/assets/svg/scrollBto.svg?react' import ScrollBtoIcon from '@/assets/svg/scrollBto.svg?react'
...@@ -303,9 +303,9 @@ export const Chat: React.FC = () => { ...@@ -303,9 +303,9 @@ export const Chat: React.FC = () => {
workFlowSessionId?: string, workFlowSessionId?: string,
recordId?: string, recordId?: string,
) => { ) => {
// 优先读取缓存中的 toolId,再回退到传参或 Redux // 优先读取缓存中的 toolId,再回退到传参或 Redux(字面量 "undefined" 等与空等同)
const sessionToolId = sessionStorage.getItem('currentToolId') ?? undefined const sessionToolId = normalizeToolIdForApi(sessionStorage.getItem('currentToolId')) || undefined
const resolvedToolId = toolId ?? sessionToolId ?? currentToolId ?? undefined const resolvedToolId = normalizeToolIdForApi(toolId ?? sessionToolId ?? currentToolId ?? undefined) || undefined
// 判断是否为提质增效模式的自动调用(空问题) // 判断是否为提质增效模式的自动调用(空问题)
const isEmptyQuestion = !question.trim() const isEmptyQuestion = !question.trim()
const isQualityImprovement = resolvedToolId === '6712395743241' const isQualityImprovement = resolvedToolId === '6712395743241'
...@@ -716,7 +716,7 @@ export const Chat: React.FC = () => { ...@@ -716,7 +716,7 @@ export const Chat: React.FC = () => {
dispatch(setCurrentToolId(toolIdFromState)) dispatch(setCurrentToolId(toolIdFromState))
} }
// 无条件同步到 sessionStorage,确保 ChatEditor 和 handleSubmitQuestion 能获取到正确的 toolId // 无条件同步到 sessionStorage,确保 ChatEditor 和 handleSubmitQuestion 能获取到正确的 toolId
sessionStorage.setItem('currentToolId', toolIdFromState) safeSessionStorageSetItem('currentToolId', toolIdFromState)
console.log('[chat-toolid-log] Chat setItem:', toolIdFromState, '| line:676') console.log('[chat-toolid-log] Chat setItem:', toolIdFromState, '| line:676')
} }
else { else {
...@@ -737,14 +737,14 @@ export const Chat: React.FC = () => { ...@@ -737,14 +737,14 @@ export const Chat: React.FC = () => {
dispatch(setCurrentToolId(finalToolId)) dispatch(setCurrentToolId(finalToolId))
// 从收藏返回时,同步到 sessionStorage,避免 ChatEditor 清除 toolId // 从收藏返回时,同步到 sessionStorage,避免 ChatEditor 清除 toolId
if (fromCollect) { if (fromCollect) {
sessionStorage.setItem('currentToolId', finalToolId) safeSessionStorageSetItem('currentToolId', finalToolId)
console.log('[chat-toolid-log] Chat setItem:', finalToolId, '| line:702') console.log('[chat-toolid-log] Chat setItem:', finalToolId, '| line:702')
} }
} }
else { else {
// 从收藏返回时,确保 sessionStorage 中有值 // 从收藏返回时,确保 sessionStorage 中有值
if (fromCollect && !sessionStorage.getItem('currentToolId')) { if (fromCollect && !sessionStorage.getItem('currentToolId')) {
sessionStorage.setItem('currentToolId', finalToolId) safeSessionStorageSetItem('currentToolId', finalToolId)
console.log('[chat-toolid-log] Chat setItem:', finalToolId, '| line:708') console.log('[chat-toolid-log] Chat setItem:', finalToolId, '| line:708')
} }
} }
...@@ -760,7 +760,7 @@ export const Chat: React.FC = () => { ...@@ -760,7 +760,7 @@ export const Chat: React.FC = () => {
// 因为可能是刚点击历史记录但 API 还没返回,或者 location.state 传递失败但 Redux 中已有正确的值 // 因为可能是刚点击历史记录但 API 还没返回,或者 location.state 传递失败但 Redux 中已有正确的值
else if (!hasQaRecords && currentToolId) { else if (!hasQaRecords && currentToolId) {
if (fromCollect && !sessionStorage.getItem('currentToolId')) { if (fromCollect && !sessionStorage.getItem('currentToolId')) {
sessionStorage.setItem('currentToolId', currentToolId) safeSessionStorageSetItem('currentToolId', currentToolId)
console.log('[chat-toolid-log] Chat setItem:', currentToolId, '| line:722') console.log('[chat-toolid-log] Chat setItem:', currentToolId, '| line:722')
} }
} }
......
...@@ -8,13 +8,14 @@ import { QuestionList } from './components/QuestionList' ...@@ -8,13 +8,14 @@ import { QuestionList } from './components/QuestionList'
import HomeIcon2 from '@/assets/homeIcon2.png' import HomeIcon2 from '@/assets/homeIcon2.png'
import SmartIce from '@/assets/smart-ice.png' import SmartIce from '@/assets/smart-ice.png'
import { clearCurrentToolId, fetchConversations, setCurrentConversation, setCurrentToolId, setNavigationFlag } from '@/store/conversationSlice' import { clearCurrentToolId, fetchConversations, setCurrentConversation, setCurrentToolId, setNavigationFlag } from '@/store/conversationSlice'
import { store } from '@/store'
import { useAppDispatch, useAppSelector } from '@/store/hook' import { useAppDispatch, useAppSelector } from '@/store/hook'
import { fetchEfficiencyQuestionList, fetchToolList } from '@/api/home' import { fetchEfficiencyQuestionList } from '@/api/home'
import SdreamLoading from '@/components/SdreamLoading' 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 { getUserRolesForApi, getUserRolesFromRoute, safeLocalStorageGetItem, safeLocalStorageSetItem, safeSessionStorageGetItem, safeSessionStorageRemoveItem, safeSessionStorageSetItem, waitForToken } from '@/lib/utils' import { getUserRolesFromRoute, normalizeToolIdForApi, safeLocalStorageGetItem, safeLocalStorageSetItem, safeSessionStorageGetItem, safeSessionStorageRemoveItem, safeSessionStorageSetItem, waitForToken } from '@/lib/utils'
// 从 localStorage 获取 token 的辅助函数 // 从 localStorage 获取 token 的辅助函数
function getTokenFromStorage(): string | null { function getTokenFromStorage(): string | null {
...@@ -86,49 +87,6 @@ export const Home: React.FC = () => { ...@@ -86,49 +87,6 @@ export const Home: React.FC = () => {
defaultValue: '', defaultValue: '',
}) })
const ensureToolIdReady = useCallback(async (): Promise<string> => {
const storedToolId = safeSessionStorageGetItem('currentToolId') || ''
if (storedToolId) {
return storedToolId
}
const userRoles = getUserRolesForApi()
const cacheKey = `toolList_${JSON.stringify(userRoles)}`
let toolList: any[] = []
const cached = safeSessionStorageGetItem(cacheKey)
if (cached) {
try {
const parsed = JSON.parse(cached)
if (Date.now() - Number(parsed?.timestamp || 0) < 5 * 60 * 1000 && Array.isArray(parsed?.toolList)) {
toolList = parsed.toolList
}
}
catch {
toolList = []
}
}
if (toolList.length === 0) {
const res = await fetchToolList({ userRoles })
if (Array.isArray(res?.data)) {
toolList = res.data
safeSessionStorageSetItem(cacheKey, JSON.stringify({ toolList, timestamp: Date.now() }))
}
}
const defaultTool = toolList[0]
const defaultToolId = String(defaultTool?.toolId || '')
if (defaultToolId) {
safeSessionStorageSetItem('currentToolId', defaultToolId)
if (defaultTool?.toolName) {
safeSessionStorageSetItem('currentToolName', defaultTool.toolName)
}
dispatch(setCurrentToolId(defaultToolId))
}
return defaultToolId
}, [dispatch])
// 根据 toolId 获取对应的 recordType // 根据 toolId 获取对应的 recordType
const getRecordTypeByToolId = (toolId: string | null | undefined): string => { const getRecordTypeByToolId = (toolId: string | null | undefined): string => {
if (!toolId) { if (!toolId) {
...@@ -152,12 +110,15 @@ export const Home: React.FC = () => { ...@@ -152,12 +110,15 @@ export const Home: React.FC = () => {
try { try {
// 等待 token 就绪后再调用接口 // 等待 token 就绪后再调用接口
await waitForToken() await waitForToken()
// 首次进入时先确保有默认 toolId,再调用依赖 toolId 的业务接口 // 调用瞬间同步读取 session 与 Redux(避免 login 闭包陈旧导致漏读)
const toolId = await ensureToolIdReady() const fromSession = normalizeToolIdForApi(safeSessionStorageGetItem('currentToolId'))
const res = await fetchSessionConversationId({ const fromRedux = normalizeToolIdForApi(store.getState().conversation.currentToolId)
...(data || {}), const effectiveToolId = fromSession || fromRedux
...(toolId ? { toolId, busiId: toolId } : {}), const res = await fetchSessionConversationId(
}) effectiveToolId
? { ...(data || {}), toolId: effectiveToolId, busiId: effectiveToolId }
: (data || {}),
)
if (res?.data?.conversationId) { if (res?.data?.conversationId) {
const conversationId = res.data.conversationId const conversationId = res.data.conversationId
dispatch(setCurrentConversation(conversationId)) dispatch(setCurrentConversation(conversationId))
......
...@@ -3,7 +3,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' ...@@ -3,7 +3,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { processConversationData } from './conversationSlice.helper' import { processConversationData } from './conversationSlice.helper'
import type { Conversation, ConversationState } from '@/types/conversation' import type { Conversation, ConversationState } from '@/types/conversation'
import { fetchCreateConversation, fetchDeleteUserConversation, fetchQueryUserConversationPage } from '@/api/conversation' import { fetchCreateConversation, fetchDeleteUserConversation, fetchQueryUserConversationPage } from '@/api/conversation'
import { waitForToken } from '@/lib/utils' import { normalizeToolIdForApi, waitForToken } from '@/lib/utils'
const initialState: ConversationState = { const initialState: ConversationState = {
conversations: [], conversations: [],
...@@ -68,9 +68,10 @@ export const createConversation = createAsyncThunk< ...@@ -68,9 +68,10 @@ export const createConversation = createAsyncThunk<
async ({ conversationData, shouldNavigate, shouldSendQuestion }, { dispatch }) => { async ({ conversationData, shouldNavigate, shouldSendQuestion }, { dispatch }) => {
await waitForToken() await waitForToken()
// 从缓存中获取 toolId 作为 busiId // 从缓存中获取 toolId 作为 busiId
const cachedToolId = typeof window !== 'undefined' ? sessionStorage.getItem('currentToolId') : null const rawCached = typeof window !== 'undefined' ? sessionStorage.getItem('currentToolId') : null
const busiId = cachedToolId || '' const cachedToolId = normalizeToolIdForApi(rawCached)
const toolId = cachedToolId || '' const busiId = cachedToolId
const toolId = cachedToolId
// 排除 toolId 字段,只传递 busiId 和 busiType // 排除 toolId 字段,只传递 busiId 和 busiType
const { toolId: _, ...restConversationData } = conversationData const { toolId: _, ...restConversationData } = conversationData
const requestPayload = { const requestPayload = {
......
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