From 3645075473d7d30073b0387011f920cd717f15da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Tue, 26 Aug 2025 16:18:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(websocket):=20=E4=BF=AE=E5=A4=8DWebSocket?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=92=8C=E8=A7=86=E9=A2=91=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 显式指定WebSocket URL确保连接到正确服务器 - 添加本地地址连接拦截逻辑防止错误连接 - 优化视频消息处理逻辑,包括加载状态管理和下载响应处理 - 使用时间戳作为唯一序列号避免重复 - 修复消息订阅逻辑,仅在有待处理视频请求时监听 --- Cunkebao/src/components/WebSocketExample.tsx | 1 + .../pc/ckbox/components/ChatWindow/index.tsx | 175 ++++++++++++------ Cunkebao/src/pages/pc/ckbox/main.ts | 22 ++- Cunkebao/src/store/module/websocket.ts | 17 +- 4 files changed, 148 insertions(+), 67 deletions(-) diff --git a/Cunkebao/src/components/WebSocketExample.tsx b/Cunkebao/src/components/WebSocketExample.tsx index 060bd98b..67867431 100644 --- a/Cunkebao/src/components/WebSocketExample.tsx +++ b/Cunkebao/src/components/WebSocketExample.tsx @@ -116,6 +116,7 @@ const WebSocketExample: React.FC = () => { color="primary" onClick={() => connect({ + url: "wss://kf.quwanzhi.com:9993", // 显式指定WebSocket URL,确保使用正确的服务器地址 client: "kefu-client", autoReconnect: true, }) diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index f7fe94f7..39434565 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -47,7 +47,7 @@ import dayjs from "dayjs"; import { ChatRecord, ContractData } from "@/pages/pc/ckbox/data"; import { clearUnreadCount, getMessages } from "@/pages/pc/ckbox/api"; import styles from "./ChatWindow.module.scss"; -import { useWebSocketStore } from "@/store/module/websocket"; +import { useWebSocketStore, WebSocketMessage } from "@/store/module/websocket"; const { Header, Content, Footer, Sider } = Layout; const { TextArea } = Input; @@ -76,6 +76,7 @@ const ChatWindow: React.FC = ({ useEffect(() => { clearUnreadCount([contract.id]).then(() => { + setLoading(true); getMessages({ wechatAccountId: contract.wechatAccountId, wechatFriendId: contract.id, @@ -83,73 +84,106 @@ const ChatWindow: React.FC = ({ To: +new Date() + 1000, Count: 100, olderData: true, - }).then(msg => { - setMessages(msg); - }); + }) + .then(msg => { + setMessages(msg); + }) + .finally(() => { + setLoading(false); + }); }); }, [contract.id]); useEffect(() => { - scrollToBottom(); + // 只有在非视频加载操作时才自动滚动到底部 + // 检查是否有视频正在加载中 + const hasLoadingVideo = messages.some(msg => { + try { + const content = + typeof msg.content === "string" + ? JSON.parse(msg.content) + : msg.content; + return content.isLoading === true; + } catch (e) { + return false; + } + }); + + if (!hasLoadingVideo) { + scrollToBottom(); + } }, [messages]); - // 添加 WebSocket 消息订阅 + // 添加 WebSocket 消息订阅 - 监听视频下载响应消息 useEffect(() => { + // 只有当有待处理的视频请求时才订阅WebSocket消息 + if (Object.keys(pendingVideoRequests).length === 0) { + return; + } + + console.log("开始监听视频下载响应,当前待处理请求:", pendingVideoRequests); + // 订阅 WebSocket 消息变化 - const unsubscribe = useWebSocketStore.subscribe( - state => state.messages, - (messages, previousMessages) => { - // 只处理新消息 - if (messages.length > previousMessages.length) { - // 获取最新的消息 - const newMessages = messages.slice(previousMessages.length); + const unsubscribe = useWebSocketStore.subscribe(state => { + // 只处理新增的消息 + const messages = state.messages as WebSocketMessage[]; - // 处理新消息 - newMessages.forEach(message => { - const content = message.content; + // 筛选出视频下载响应消息 + messages.forEach(message => { + if (message?.content?.cmdType === "CmdDownloadVideoResult") { + console.log("收到视频下载响应:", message.content); - // 检查是否是视频下载响应 - if (content && content.cmdType === "CmdDownloadVideoResponse") { - // 获取视频URL - const videoUrl = content.videoUrl; - const requestId = content.requestId || content.seq; + // 检查是否是我们正在等待的视频响应 + const messageId = Object.keys(pendingVideoRequests).find( + id => pendingVideoRequests[id] === message.content.friendMessageId, + ); - // 查找对应的消息ID - const messageId = pendingVideoRequests[requestId]; - if (messageId && videoUrl) { - // 更新消息内容,将预览图替换为实际视频 - setMessages(prevMessages => { - return prevMessages.map(msg => { - if (msg.id === messageId) { - return { - ...msg, - content: videoUrl, - }; - } - return msg; - }); - }); + if (messageId) { + console.log("找到对应的消息ID:", messageId); - // 从待处理请求中移除 - setPendingVideoRequests(prev => { - const newRequests = { ...prev }; - delete newRequests[requestId]; - return newRequests; - }); + // 从待处理队列中移除 + setPendingVideoRequests(prev => { + const newRequests = { ...prev }; + delete newRequests[messageId]; + return newRequests; + }); - messageApi.success("视频加载成功"); - } - } - }); + // 更新消息内容,将视频URL添加到对应的消息中 + setMessages(prevMessages => { + return prevMessages.map(msg => { + if (msg.id === Number(messageId)) { + try { + const msgContent = + typeof msg.content === "string" + ? JSON.parse(msg.content) + : msg.content; + + // 更新消息内容,添加视频URL并移除加载状态 + return { + ...msg, + content: JSON.stringify({ + ...msgContent, + videoUrl: message.content.url, + isLoading: false, + }), + }; + } catch (e) { + console.error("解析消息内容失败:", e); + } + } + return msg; + }); + }); + } } - }, - ); + }); + }); // 组件卸载时取消订阅 return () => { unsubscribe(); }; - }, [pendingVideoRequests, messageApi]); + }, [pendingVideoRequests]); // 依赖于pendingVideoRequests,当队列变化时重新设置订阅 const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); @@ -237,21 +271,24 @@ const ChatWindow: React.FC = ({ }; // 处理视频播放请求,发送socket请求获取真实视频地址 - const handleVideoPlayRequest = (tencentUrl: string, messageId: string) => { - // 生成请求ID (可以使用 seq 或其他唯一标识) + const handleVideoPlayRequest = (tencentUrl: string, messageId: number) => { + // 生成请求ID (使用当前时间戳作为唯一标识) + const requestSeq = `${+new Date()}`; + console.log("发送视频下载请求:", { messageId, requestSeq }); // 构建socket请求数据 useWebSocketStore.getState().sendCommand("CmdDownloadVideo", { - chatroomMessageId: contract.chatroomId ? contract.chatroomId : 0, - friendMessageId: contract.chatroomId ? 0 : contract.id, - seq: 9, // 使用唯一的请求ID + chatroomMessageId: contract.chatroomId ? messageId : 0, + friendMessageId: contract.chatroomId ? 0 : messageId, + seq: requestSeq, // 使用唯一的请求ID tencentUrl: tencentUrl, wechatAccountId: contract.wechatAccountId, }); - // 记录待处理的视频请求 + // 将消息ID和请求序列号添加到待处理队列 setPendingVideoRequests(prev => ({ ...prev, + [messageId]: messageId, })); // 更新消息状态为加载中 @@ -313,6 +350,31 @@ const ChatWindow: React.FC = ({ handleVideoPlayRequest(videoData.tencentUrl, msg.id); }; + // 检查是否已下载视频URL + if (videoData.videoUrl) { + // 已获取到视频URL,显示视频播放器 + return ( + + ); + } + // 检查是否处于加载状态 if (videoData.isLoading) { return ( @@ -336,6 +398,7 @@ const ChatWindow: React.FC = ({ ); } + // 默认显示预览图和播放按钮 return (
@@ -709,7 +772,7 @@ const ChatWindow: React.FC = ({ }; const renderMessage = (msg: ChatRecord) => { - const isOwn = msg.senderId === "me"; + const isOwn = msg.isSend; return (
{ const accountId = getAccountId(); //发起链接 - connect({ - accessToken: String(Token), - accountId: accountId, - client: "kefu-client", - cmdType: "CmdSignIn", - seq: 1, - }); + if (Token && accountId) { + connect({ + url: "wss://kf.quwanzhi.com:9993", // 显式指定WebSocket URL,确保使用正确的服务器地址 + accessToken: String(Token), + accountId: accountId, + client: "kefu-client", + cmdType: "CmdSignIn", + seq: +new Date(), + }); + console.log("WebSocket连接已初始化"); + } else { + console.error("WebSocket连接初始化失败:缺少Token或accountId"); + } //获取联系人列表 const contractList = await getAllContactList(); //获取群列表 @@ -137,7 +143,7 @@ export const getChatInfo = () => { //获取UserId sendCommand("CmdRequestWechatAccountsAliveStatus", { wechatAccountIds: ["300745", "4880930", "32686452"], - seq: 2, + seq: +new Date(), }); console.log("发送链接信息"); }; diff --git a/Cunkebao/src/store/module/websocket.ts b/Cunkebao/src/store/module/websocket.ts index 2d042aea..397733c9 100644 --- a/Cunkebao/src/store/module/websocket.ts +++ b/Cunkebao/src/store/module/websocket.ts @@ -9,6 +9,7 @@ export interface WebSocketMessage { cmdType?: string; seq?: number; wechatAccountIds?: string[]; + content?: any; [key: string]: any; } @@ -77,7 +78,7 @@ const DEFAULT_CONFIG: WebSocketConfig = { accessToken: "", autoReconnect: true, cmdType: "", // 添加默认的命令类型 - seq: 0, // 添加默认的序列号 + seq: +new Date(), // 添加默认的序列号 reconnectInterval: 3000, maxReconnectAttempts: 5, }; @@ -123,7 +124,17 @@ export const useWebSocketStore = createPersistStore( t: Date.now().toString(), }); + // 检查URL是否为localhost,如果是则不连接 const wsUrl = fullConfig.url + "?" + params; + if (wsUrl.includes("localhost") || wsUrl.includes("127.0.0.1")) { + console.error("WebSocket连接被拦截:不允许连接到本地地址", wsUrl); + Toast.show({ + content: "WebSocket连接被拦截:不允许连接到本地地址", + position: "top", + }); + set({ status: WebSocketStatus.ERROR }); + return; + } set({ status: WebSocketStatus.CONNECTING, @@ -260,12 +271,12 @@ export const useWebSocketStore = createPersistStore( accessToken: token2, accountId: Number(getAccountId()), client: currentState.config?.client || "kefu-client", - seq: currentState.config?.seq || 1, + seq: +new Date(), }); //获取UserId currentState.sendCommand("CmdRequestWechatAccountsAliveStatus", { wechatAccountIds: ["300745", "4880930", "32686452"], - seq: 2, + seq: +new Date(), }); }