跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptWeChatAI大前端

UniApp + Dify 实战:详解 SSE 流式响应解析与前端渲染

综述由AI生成介绍在 UniApp 中对接 Dify 实现 SSE 流式响应的完整方案。核心在于使用 uni.request 开启 enableChunked 模式接收分片数据,通过 TextDecoder 解码 ArrayBuffer 避免乱码,并根据 event 类型(message/message_end)拼接文本。同时解决了 Vue 响应式更新、Markdown 渲染及 JSON 截断等常见问题,适用于微信小程序等多端场景。

云朵棉花糖发布于 2026/4/6更新于 2026/5/2340 浏览
UniApp + Dify 实战:详解 SSE 流式响应解析与前端渲染

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 

处理难点:

  1. 前缀处理:每行数据都以 data: 开头,解析 JSON 前必须去掉。
  2. 粘包处理:有时候一次网络请求回调会收到多条 data,需要用 \n\n 分割。
  3. 事件区分:必须判断 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: 'POST',
    header: {
      'Authorization': 'Bearer {API_KEY}', // 替换为真实 Key
      'Content-Type': 'application/json'
    },
    data: {
      inputs: {},
      query: userQuery,
      response_mode: 'streaming', // 必须是 streaming
      user: 'uni-user-123',
      conversation_id: that.conversationId || '' // 如果是连续对话,需传入
    },
    enableChunked: true, // 【关键】开启流式传输支持
    success: (res) => {
      // 这里是请求完成后的回调,流式通常不在这里处理数据
    }
  });

  // 3. 监听流式数据头(可选)
  requestTask.onHeadersReceived((headers) => {
    // console.log('Header received', headers);
  });

  // 4. 【核心】监听分片数据
  requestTask.onChunkReceived((res) => {
    // res.data 是 ArrayBuffer,需要转换
    const arrayBuffer = res.data;
    // 小程序/App 端需要 TextDecoder,或者使用第三方库转换
    // 如果环境不支持 TextDecoder,需使用类似 text-encoding 的 polyfill
    const uint8Array = new Uint8Array(arrayBuffer);
    let text = '';
    // 简易转换 (注意:中文可能乱码,生产环境建议用专业库如 fast-text-encoding)
    // 微信小程序基础库高版本已支持 TextDecoder
    try {
      const decoder = new TextDecoder('utf-8');
      text = decoder.decode(uint8Array, { stream: true });
    } catch (e) {
      // 兼容写法,逐字节处理(此处仅为示意,建议引入库)
      text = String.fromCharCode.apply(null, uint8Array);
      // 实际开发请务必处理 UTF-8 多字节中文乱码问题
      text = decodeURIComponent(escape(text));
    }

    // 5. 处理 Dify 返回的原始数据字符串
    that.processDifyStream(text, currentMsgIndex);
  });
},

// 处理 Dify 数据流的专用函数
processDifyStream(chunkText, msgIndex) {
  // Dify 的数据块以 \n\n 分隔
  const lines = chunkText.split('\n\n');
  lines.forEach(line => {
    // 去掉 data: 前缀
    if (line.startsWith('data: ')) {
      const jsonStr = line.replace('data: ', '');
      try {
        const data = JSON.parse(jsonStr);
        // 根据 Dify 文档判断 event 类型
        if (data.event === 'message') {
          // 【关键步骤】拼接 answer 字段到当前消息
          this.messageList[msgIndex].content += data.answer;
          // 保存 conversation_id 以便下一轮对话
          if (!this.conversationId && data.conversation_id) {
            this.conversationId = data.conversation_id;
          }
        } else if (data.event === 'message_replace') {
          // 内容审查替换,直接覆盖
          this.messageList[msgIndex].content = data.answer;
        } else if (data.event === 'message_end') {
          console.log('生成结束', data);
          // 可以在这里处理 metadata,比如 token 消耗
        } else if (data.event === 'error') {
          console.error('Dify 报错:', data);
          this.messageList[msgIndex].content += '\n[出错:' + data.message + ']';
        }
        // 【重要】强制触发 Vue 视图更新(如果在某些层级深的结构中)
        // 这一步在 Vue2 中可能不需要,但在某些 UniApp 场景下需要
        // this.$forceUpdate();
      } catch (e) {
        // JSON 解析失败通常是因为数据包不完整(被截断),
        // 生产环境需要做一个 buffer 缓存上一块未解析完的字符串
        // 暂时忽略或存入 buffer
        console.log('JSON parse error (ignore partial chunk):', e);
      }
    }
  });
}

4. 常见坑排查清单

如果还是展示不出来,请按以下顺序检查:

  1. ArrayBuffer 解码乱码:
    • UniApp 的 onChunkReceived 返回的是 ArrayBuffer。如果不进行 UTF-8 解码直接转字符串,中文会显示乱码或空白。
    • 解决:确保使用了 TextDecoder 或者 decodeURIComponent(escape(String.fromCharCode(...))) 这种方式正确解码。
  2. Vue 响应式失效:
    • 如果在 onChunkReceived 这种异步回调中,this 指向可能丢失。
    • 解决:确保在外部定义了 const that = this;,或者使用箭头函数。
    • 解决:如果是 Vue 2,修改数组索引可能不会触发视图更新。使用 this.$set(this.messageList, index, newValue) 或者直接修改对象属性 this.messageList[index].content += '...' 通常是有效的,但要确保 messageList 是在 data 中定义的。
  3. Markdown 渲染:
    • Dify 输出的是 Markdown 格式(包含 **加粗**,Code Block 等)。
    • 如果直接用 <text>{{ content }}</text>,只能显示纯文本。
    • 建议:在 UniApp 中引入 mp-html 或 towxml 等组件来渲染 Markdown,这样能正确展示代码块和格式。
  4. JSON 解析报错:
    • 流式传输网络抖动时,JSON 可能会被截断(比如 {"answer": "你好 后面断了)。
    • 解决:需要实现一个 buffer 变量,如果 JSON.parse 失败,将当前字符串存起来,等下一个 chunk 来了拼接到头部再解析。

目录

  1. 1. 理解核心机制:拼接而非替换
  2. 2. 关键数据解析逻辑
  3. 3. UniApp 代码实现方案
  4. 4. 常见坑排查清单
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • WebGoat 环境搭建及漏洞实战完全指南
  • 本地 AI 电话机器人:通过 UDP 传输手机通话声音的 Python 脚本
  • VSCode + GitHub Copilot 从安装到 Agent 实战详解
  • GitHub 浏览器插件实现界面中文翻译
  • Whisper 语音识别微调实战:多平台部署与训练指南
  • GitHub 学生开发者认证操作指南
  • 圣光艺苑:基于 Stable Diffusion 的鎏金画框艺术生成工具
  • 利用 AI Ping 实现大模型统一调用与成本优化实践
  • C++ string 类模拟实现
  • 基于 DeepFace 和 OpenCV 的实时情绪分析器实现
  • 基于腾讯云轻量应用服务器部署 OpenClaw 并接入 QQ 飞书机器人
  • 医疗 AI 可信革命全栈实现:向量索引与贝叶斯网络
  • 大模型降低 AIGC 率指令实战指南:从原理到最佳实践
  • Llama Factory 微调实战:优化截断长度解决显存溢出
  • 基于 Stable Diffusion 的 AI 姓氏头像生成教程
  • 7 款 AI 工具助力产品经理工作效率提升
  • 半小时基于 OpenClaw 搭建 AI 量化系统:开源三件套实测
  • 前端拖拽交互实现:从原生 API 到专业库
  • Java IO 流进阶:字符流与字节流的深度应用
  • 基于 Verilog FPGA 的双线性插值视频缩放系统实现

相关免费在线工具

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online