From d698259e5d9bb99fd03976e97ded72e55968527c Mon Sep 17 00:00:00 2001 From: zoujing Date: Fri, 8 Aug 2025 15:31:16 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E6=B5=81=E5=BC=8F=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E8=AF=B7=E6=B1=82=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/chat/ChatMainList.vue | 4 +- request/api/AgentChatStream.js | 176 +++++++++++++++++---------------- 2 files changed, 95 insertions(+), 85 deletions(-) diff --git a/pages/chat/ChatMainList.vue b/pages/chat/ChatMainList.vue index ae98f0a..30475c2 100644 --- a/pages/chat/ChatMainList.vue +++ b/pages/chat/ChatMainList.vue @@ -348,15 +348,17 @@ let typeWriterTimer = null; let aiMsgBuffer = ''; // 全局缓冲区 let isTyping = false; // 是否正在打字 + let currentMessageId = '' /// 发送获取AI聊天消息 const sendChat = (message, isInstruct = false) => { + currentMessageId = 'mid' + new Date().getTime() const args = { conversationId: conversationId.value, agentId: agentId.value, messageType: isInstruct ? 1 : 0, messageContent: isInstruct ? commonType : message, - messageId: 'mid' + new Date().getTime() + messageId: currentMessageId } // 插入AI消息 diff --git a/request/api/AgentChatStream.js b/request/api/AgentChatStream.js index 1e408f9..2057e7d 100644 --- a/request/api/AgentChatStream.js +++ b/request/api/AgentChatStream.js @@ -1,4 +1,5 @@ import { BASE_URL } from "../../constant/base"; +import { goLogin } from "@/hooks/useGoLogin"; /// 请求流式数据的API const API = '/agent/assistant/chat'; @@ -9,45 +10,54 @@ const API = '/agent/assistant/chat'; * @param {Function} onChunk 回调,每收到一段数据触发 * @returns {Object} 包含Promise和requestTask的对象 */ + +// 在文件顶部重新设计状态追踪 let requestTask = null; -let isAborted = false; // 添加终止状态标志 -let currentPromiseReject = null; // 保存当前Promise的reject函数 -let currentMessageId = null; // 当前请求的messageId +let isAborted = false; +let currentPromiseReject = null; +let lastRequestId = null; // 记录上一次请求ID +let activeRequestId = null; // 记录当前活动请求ID /** * 终止的请求 */ const stopAbortTask = () => { console.log("🛑 开始强制终止请求... "); - isAborted = true; // 立即设置终止标志 + isAborted = true; + + // 将当前活动请求ID保存为上一次请求ID + lastRequestId = activeRequestId; + // 清除当前活动请求ID + activeRequestId = null; - // 立即拒绝当前Promise(最强制的终止) if (currentPromiseReject) { currentPromiseReject(new Error('请求已被用户终止')); currentPromiseReject = null; } if (requestTask) { - // 先取消所有监听器 - try { - if (requestTask.offChunkReceived) { - requestTask.offChunkReceived(); + // 先移除监听器,再终止请求 + const cleanupListeners = () => { + try { + if(requestTask.offChunkReceived) { + console.log("======>offChunkReceived") + requestTask.offChunkReceived(); + } + if(requestTask.offHeadersReceived) { + console.log("======>offHeadersReceived") + requestTask.offHeadersReceived(); + } + } catch (e) { + console.error('清理事件监听器失败:', e); } - } catch (e) { - console.log('🛑 取消 ChunkReceived 监听失败:', e); - } + }; - try { - if (requestTask.offHeadersReceived) { - requestTask.offHeadersReceived(); - } - } catch (e) { - console.log('🛑 取消 HeadersReceived 监听失败:', e); - } + cleanupListeners(); - // 然后终止网络请求 + // 终止请求 try { if (requestTask.abort) { + console.log("======>abort") requestTask.abort(); } } catch (e) { @@ -57,23 +67,28 @@ const stopAbortTask = () => { requestTask = null; } - currentMessageId = null; console.log('🛑 请求强制终止完成'); } const agentChatStream = (params, onChunk) => { - const promise = new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const token = uni.getStorageSync('token'); - let hasError = false; - isAborted = false; // 重置终止状态 - - // 保存当前Promise的reject函数,用于强制终止 + const requestId = Date.now().toString(); // 生成唯一请求ID + + // 重置状态 + isAborted = false; currentPromiseReject = reject; + + // 更新请求ID追踪 + lastRequestId = activeRequestId; // 保存上一次请求ID + activeRequestId = requestId; // 设置新的活动请求ID - // 设置当前请求的messageId,用于区分不同请求 - const messageId = params.messageId; - currentMessageId = messageId; // 记录当前请求的messageId - console.log("🚀 发送请求内容: ", params) + console.log(`🚀 发送请求 [${requestId}], 上一次请求ID [${lastRequestId}]`); + + // 检查数据块是否来自已终止的请求 + const isStaleData = (dataRequestId) => { + return dataRequestId === lastRequestId || dataRequestId !== activeRequestId; + }; // #ifdef MP-WEIXIN requestTask = uni.request({ @@ -88,58 +103,65 @@ const agentChatStream = (params, onChunk) => { }, responseType: 'arraybuffer', success(res) { - if (!isAborted && currentMessageId === messageId) { - console.log("✅ 请求成功"); + if (!isAborted && !isStaleData(requestId)) { + console.log(`✅ 请求 [${requestId}] 成功`); resolve(res.data); - } else if (currentMessageId !== messageId) { - console.log("❌ 请求已过期(messageId不匹配),忽略success回调"); } else { - console.log("❌ 请求已终止,忽略success回调"); + console.log(`❌ 请求 [${requestId}] 已终止或过期,忽略success回调`); } }, fail(err) { - if (!isAborted && currentMessageId === messageId) { - console.log("❌ 请求失败,错误:", JSON.stringify(err)); + if (!isAborted && activeRequestId === requestId) { + console.log(`❌ 请求 [${requestId}] 失败:`, JSON.stringify(err)); reject(err); - } else if (currentMessageId !== messageId) { - console.log("❌ 请求已过期(messageId不匹配),忽略fail回调"); } else { - console.log("❌ 请求已终止,忽略fail回调"); + console.log(`❌ 请求 [${requestId}] 已终止或过期,忽略fail回调`); } }, complete(res) { - if (!isAborted && currentMessageId === messageId && res.statusCode !== 200) { - console.log("❌ 请求完成但状态错误,状态:", res.statusCode); - if (onChunk) { - onChunk({ error: true, message: '服务器错误', detail: res }); + // 使用 requestId 来验证请求有效性 + if (!isAborted && activeRequestId === requestId) { + if (res.statusCode !== 200) { + console.log(`❌ 请求 [${requestId}] 完成但状态错误,状态:`, res.statusCode); + if(res.statusCode === 424) { + uni.setStorageSync('token', ''); + goLogin(); + } + if (onChunk) { + onChunk({ + error: true, + message: '服务器错误', + detail: res + }); + } + reject(res); } - reject(res); - } else if (currentMessageId !== messageId) { - console.log("❌ 请求已过期(messageId不匹配),忽略complete回调"); - } else if (isAborted) { - console.log("❌ 请求已终止,忽略complete回调"); + } else { + console.log(`❌ 请求 [${requestId}] ${isAborted ? '已终止' : '已过期'},忽略complete回调`); } } }); requestTask.onHeadersReceived(res => { - // 检查请求是否已终止或过期(messageId不匹配) - if (isAborted || currentMessageId !== messageId) { - if (currentMessageId !== messageId) { - console.log('🚫 Headers已过期(messageId不匹配),忽略'); - } else { - console.log('🚫 Headers已终止,忽略'); - } + // 使用 requestId 验证请求有效性 + if (isAborted || activeRequestId !== requestId) { + console.log(`🚫 Headers [${requestId}] ${isAborted ? '已终止' : '已过期'},忽略`); return; } - console.log('📡 onHeadersReceived,res:', res); + console.log(`📡 请求 [${requestId}] Headers接收:`, res); const status = res.statusCode || (res.header && res.header.statusCode); + if (status && status !== 200) { - hasError = true; - if (onChunk && !isAborted && currentMessageId === messageId) { - onChunk({ error: true, message: `服务器错误(${status})`, detail: res }); + console.log(`❌ 请求 [${requestId}] 服务器返回错误状态:`, status); + if (onChunk && !isAborted && activeRequestId === requestId) { + onChunk({ + error: true, + message: `服务器错误(${status})`, + detail: res + }); } + // 终止异常请求 if (requestTask && requestTask.abort) { requestTask.abort(); } @@ -147,13 +169,9 @@ const agentChatStream = (params, onChunk) => { }); requestTask.onChunkReceived(res => { - // 第一道防线:立即检查终止状态和messageId(防止处理过期请求) - if (isAborted || hasError || requestTask === null || currentMessageId !== messageId) { - if (currentMessageId !== messageId) { - console.log('🚫 数据块已过期(messageId不匹配),忽略 - 第一道检查'); - } else { - console.log('🚫 数据块已终止或无效,忽略 - 第一道检查'); - } + // 立即验证请求有效性 + if (isAborted || isStaleData(requestId)) { + console.log(`🚫 数据块 [${requestId}] 已终止或过期,忽略`); return; } @@ -162,37 +180,27 @@ const agentChatStream = (params, onChunk) => { try { data = decodeURIComponent(escape(weAtob(base64))); } catch (e) { - // 某些平台可能不支持 atob,可以直接用 base64 - data = base64; + console.error('Base64解码失败:', e); + return; } + console.log("📦 onChunkReceived,res:", data) - // 第二道防线:解析前再次检查 - if (isAborted || hasError || requestTask === null || currentMessageId !== messageId) { - if (currentMessageId !== messageId) { - console.log('🚫 数据块已过期(messageId不匹配),忽略 - 第二道检查'); - } else { - console.log('🚫 数据块已终止或无效,忽略 - 第二道检查'); - } + // 再次验证请求有效性 + if (isAborted || activeRequestId !== requestId) { + console.log(`🚫 解析后数据 [${requestId}] 已终止或过期,忽略`); return; } const messages = parseSSEChunk(data); messages.forEach(msg => { - // 第三道防线:每个消息处理前都检查(确保只处理当前请求的消息) - if (onChunk && !isAborted && !hasError && requestTask !== null && currentMessageId === messageId) { + if (!isAborted && !isStaleData(requestId) && onChunk) { onChunk(msg); - } else if (currentMessageId !== messageId) { - console.log('🚫 消息已过期(messageId不匹配),忽略处理'); - } else { - console.log('🚫 消息已终止或无效,忽略处理'); } }); }); // #endif }); - - return promise } // window.atob兼容性处理