前端处理流式数据

参考

    // 流式加载
    async fetchData() {
      let self = this
      await fetch(
        baseUrl +
          '/biz-api/eemp/ldas/v1/chatApi/DeepSearchChat?searchType=' +
          self.searchIndex +
          '&question=' +
          self.messageWord,
        {
          method: 'GET',
          headers: {
            Authorization: sessionStorage.getItem('accessToken') || localStorage.getItem('accessToken'),
            'Content-Type': 'text/event-stream;charset=utf-8'
          }
        }
      ).then((response) => {
        const reader = response.body.getReader()
        let mesObj = {
          recipient_id: 'user',
          text: '',
          docs: [],
          type: '',
          daily: '',
          cityName: '',
          speak: false,
          feedBcak: -1,
          isLoading: false
        }
        // 创建用于解码文本的解码器
        const decoder = new TextDecoder('utf-8')
        const stream = new ReadableStream({
          async start(controller) {
            while (true) {
              // 从数据读取器中读取数据
              const { done, value } = await reader.read()
              // 如果读取完成,关闭控制器并退出循环
              if (done) {
                controller.close()
                break
              }
              // 解码值并将其附加到指定ID的消息内容中
              const match = decoder.decode(value, { stream: true })
              if (match) {
                try {
                  if (JSON.parse(match).code === 200) {
                    // 系统自查
                    self.dataDeal(JSON.parse(match), mesObj)
                  }
                } catch (error) {
                  // 大模型分析
                  self.deepDataDeal(match, mesObj.text, mesObj)
                }
              }
            }
          }
        })
        // 显示答案
        self.messageLIsts.splice(self.messageLIsts.length - 1, 1)
        self.messageLIsts.push(mesObj)
        self.scrollTo()

        // 保存deep问答
        self.messageLIstsDeep = [...self.messageLIsts]
        sessionStorage.setItem('messageLIstsDeep', JSON.stringify(self.messageLIstsDeep))
      })
    },
    // 系统自查数据包装
    dataDeal(res, mesObj) {
      let answer = ''
      console.log('res.data.type', res.data.type)
      if (res.data.type === 'ranks') {
        let obj = JSON.parse(res.data.msg)
        console.log('obj.echarts', obj)
        mesObj.text = obj.content
        mesObj.echarts = obj.echarts
        mesObj.type = 'ranks'
        return
      }
      let msgType = ['weather', 'rank', 'quality', 'deepSearchChat', 'task', 'siteAlarm']
      if (msgType.includes(res.data.type)) {
        answer = res.data.msg
      }
      mesObj.text = answer
    },
    // 大模型分析数据包装
    deepDataDeal(match, newStr, mesObj) {
      let _self = this
      const chunks = match.trim().split('\n\n')
      chunks.forEach((chunk) => {
        const jsonString = chunk.replace('data: ', '')
        try {
          const data = JSON.parse(jsonString)
          const content = data.choices[0].delta.content.replace('\n\n', '').replace('###', '\n')
          // const content = data.choices[0].delta.content

          if (content) {
            newStr = newStr + content
            mesObj.text = newStr
            _self.scrollTo()
          }
        } catch (error) {
          // console.error('Error parsing JSON:', error)
        }
      })
    }```

<think>好的,用户的问题是关于如何在前端实现流输出的封装方法,特别是结合之前提到的SSE(服务器发送事件)和Vue3的示例。首先,我需要回忆一下SSE的基本原理和Vue3的相关特性。 SSE是一种允许服务器向客户端实时推送数据的技术,基于HTTP长连接,适用于单向数据流。在前端,通常通过EventSource API来接收这些数据流。用户可能已经知道后端如何设置,现在需要在前端进行封装,使其更易于使用和管理。 接下来,我需要考虑Vue3的特性,比如Composition API和响应系统。用户可能希望封装一个可复用的函数或组件,处理SSE的连接、数据接收、错误处理和自动关闭。这需要考虑以下几个方面: 1. **创建EventSource实例**:需要接收后端SSE的URL,可能还需要自定义请求头,比如认证信息。不过,标准的EventSource不支持自定义头部,可能需要使用fetch或者第三方库,但用户示例中的引用提到的是使用控制器的SSE处理,可能已经处理好了认证,所以暂时假设不需要自定义头部,或者使用其他方法绕过。 2. **数据接收和处理**:当接收到消息时,需要将数据传递给回调函数,可能还需要处理不同的消息类型。比如,后端可能发送不同的event类型,需要分派处理。 3. **错误处理**:网络问题或服务器错误时,需要重连或通知用户。 4. **响应集成**:在Vue3中,可能需要将接收到的数据绑定到组件的响应状态,比如使用ref或reactive。 5. **自动关闭连接**:当组件卸载时,需要关闭连接,避免内存泄漏。 然后,参考用户提供的引用[1],其中提到使用控制器的SSE处理逻辑,前端需要接收流结果。因此,封装方法应该包括创建EventSource,监听消息事件,处理错误,并在适当的时候关闭连接。 可能的封装步骤: - 创建一个函数,接受URL参数和回调函数,返回一个包含关闭连接方法的对象。 - 在Vue3中,可以使用Composition API,将封装逻辑放在useSSE这样的函数中,返回响应数据和方法。 - 处理不同的SSE事件,比如默认的'message'事件,或者其他自定义事件。 - 错误处理时,可能需要尝试重新连接,但需设置最大重试次数。 - 在组件卸载时自动关闭连接,使用生命周期钩子如onUnmounted。 需要检查可能的陷阱,比如EventSource的跨域问题,是否需要携带cookie或其他认证信息。如果后端需要认证,可能需要使用fetch来创建SSE流,但这会更复杂,因为EventSource不支持自定义头部,但用户提供的引用可能已经处理了认证部分,所以暂时假设不需要。 示例代码结构: 在Vue3组件中,导入封装的useSSE函数,在setup中调用,传入URL和回调函数,处理接收到的数据,并绑定到响应变量。同时,确保在组件销毁时关闭连接。 可能需要考虑如何分块处理流数据,比如逐词显示,使用字符串累积或者直接替换内容。此外,错误反馈和加载状态的提示也是用户体验的一部分。 最后,验证示例代码的正确性,确保EventSource正确连接到后端,数据能实时更新,错误处理和自动关闭正常工作。</think>以下是在Vue3中实现流输出封装的解决方案,分为核心封装方法和应用示例两部分: ### 一、SSE流封装方法(utils/sseHelper.js) ```javascript import { ref, onUnmounted } from 'vue' export function useSSE() { const eventSource = ref(null) const data = ref('') const error = ref(null) const isConnected = ref(false) const connect = (url, callback) => { try { eventSource.value = new EventSource(url) eventSource.value.onopen = () => { isConnected.value = true } eventSource.value.onmessage = (e) => { data.value = e.data callback?.(e.data) // 支持自定义回调处理 } eventSource.value.onerror = (e) => { error.value = `SSE连接异常: ${e.status || '未知错误'}` close() } } catch (err) { error.value = `SSE初始化失败: ${err.message}` } } const close = () => { if (eventSource.value) { eventSource.value.close() isConnected.value = false } } onUnmounted(() => { close() }) return { data, error, isConnected, connect, close } } ``` ### 二、组件应用示例(components/StreamOutput.vue) ```vue <template> <div> <div v-if="isLoading">连接中...</div> <div v-if="error" class="error">{{ error }}</div> <pre class="output">{{ outputText }}</pre> <button @click="disconnect">断开连接</button> </div> </template> <script setup> import { ref, watch } from 'vue' import { useSSE } from '../utils/sseHelper' const props = defineProps({ endpoint: { type: String, required: true } }) const { data, error, isConnected, connect, close } = useSSE() const outputText = ref('') const isLoading = ref(false) // 初始化连接 const initStream = () => { isLoading.value = true connect(props.endpoint, (chunk) => { outputText.value += chunk // 增量追加内容 }) } // 监听连接状态变化 watch(isConnected, (newVal) => { isLoading.value = !newVal }) // 主动断开连接 const disconnect = () => { close() outputText.value = '' } // 组件挂载时启动 onMounted(initStream) </script> ``` ### 三、关键实现说明 1. **响应状态管理**: - 使用Vue3的`ref`管理连接状态、数据和错误信息 - 通过`watch`自动更新加载状态[^1] 2. **自动清理机制**: - 通过`onUnmounted`生命周期自动关闭SSE连接 - 提供手动断开连接的按钮 3. **数据拼接处理**: - 采用增量追加方实现逐词显示效果 - 支持自定义回调处理数据块 4. **错误处理**: - 捕获初始化异常和连接错误 - 自动更新错误状态并断开连接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

k0933

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值