1. 理解核心机制:拼接而非替换
Dify 的 streaming 模式下,服务器会不断推送形如 data: {"event": "message", "answer": "字"} 的数据包。
核心逻辑是: 收到一个包,解析出 answer 字段,将其**追加(Append)**到当前正在显示的对话变量后,而不是直接替换。
2. 关键数据解析逻辑
Dify 返回的数据流格式如下:
data: {"event": "message", "answer": "我", ...}\n\n data: {"event": "message", "answer": "是", ...}\n\n data: {"event": "message_end", ...}\n\n
处理难点:
- 前缀处理:每行数据都以
data:开头,解析 JSON 前必须去掉。 - 粘包处理:有时候一次网络请求回调会收到多条
data,需要用\n\n分割。 - 事件区分:必须判断
event字段。message: 文本块,核心展示内容。message_replace: 敏感词替换,需要替换整段文本。message_end: 结束标志。ping: 心跳,忽略即可。
3. UniApp 代码实现方案
在 UniApp 中(特别是微信小程序端),不能直接使用浏览器原生的 EventSource。推荐使用 uni.request 的 enableChunked: true 参数。
以下是一个完整的处理示例代码:
// 假设这是发送消息的方法sendMessage(userQuery){const that =this;// 1. 在界面先创建一个空的回答占位(为了立刻显示 loading 或光标)this.messageList.push({ role:'user', content: userQuery });this.messageList.push({ role:'assistant', content:''// 初始为空,稍后拼接});// 获取当前正在更新的这条消息在数组中的索引const currentMsgIndex =this.messageList.length -1;// 2. 发起请求const requestTask = uni.request({ url:'http://47.243.127.167:4010/v1/chat-messages', method:'', :{:, 替换为真实 Key:}, data:{ inputs:{}, query: userQuery, response_mode:, 必须是 streaming user:, conversation_id: that.conversationId ||// 如果是连续对话,需传入}, enableChunked:true, 【关键】开启流式传输支持success:(res)=>{ 这里是请求完成后的回调,流式通常不在这里处理数据}}); . 监听流式数据头(可选) requestTask.onHeadersReceived((headers)=>{ console.log(, headers);}); . 【核心】监听分片数据 requestTask.onChunkReceived((res)=>{ res.data 是 ArrayBuffer,需要转换const arrayBuffer = res.data; 小程序/App端需要 TextDecoder,或者使用第三方库转换// 如果环境不支持 TextDecoder,需使用类似 text-encoding 的 polyfillconst uint8Array =newUint8Array(arrayBuffer);let text =; 简易转换 (注意:中文可能乱码,生产环境建议用专业库如 fast-text-encoding)// 微信小程序基础库高版本已支持 TextDecodertry{const decoder =newTextDecoder(); text = decoder.decode(uint8Array,{ stream:true});}catch(e){ 兼容写法,逐字节处理(此处仅为示意,建议引入库) text = String.fromCharCode.apply(null, uint8Array); 实际开发请务必处理 UTF- 多字节中文乱码问题 text =decodeURIComponent(escape(text));}// . 处理 Dify 返回的原始数据字符串 that.processDifyStream(text, currentMsgIndex);});}, 处理 Dify 数据流的专用函数processDifyStream(chunkText, msgIndex){ Dify 的数据块以 \n\n 分隔const lines = chunkText.(); lines.forEach({ 去掉 data: 前缀(line.startsWith()){const jsonStr = line.replace(,);try{const data =JSON.parse(jsonStr); 根据 Dify 文档判断 event 类型(data.event ===){ 【关键步骤】拼接 answer 字段到当前消息this.messageList[msgIndex].content += data.answer; 保存 conversation_id 以便下一轮对话(!this.conversationId && data.conversation_id){this.conversationId = data.conversation_id;}}elseif(data.event ===){ 内容审查替换,直接覆盖this.messageList[msgIndex].content = data.answer;}elseif(data.event ===){ console.log(, data); 可以在这里处理 metadata,比如 token 消耗}elseif(data.event ===){ console.error(, data);this.messageList[msgIndex].content +=+ data.message +;}// 【重要】强制触发 Vue 视图更新(如果在某些层级深的结构中)// 这一步在 Vue2 中可能不需要,但在某些 UniApp 场景下需要// this.(); }catch(e){ JSON 解析失败通常是因为数据包不完整(被截断),// 生产环境需要做一个 buffer 缓存上一块未解析完的字符串// 暂时忽略或存入 buffer console.log(, e);}}});}


