uniapp处理流式请求

news2025/4/1 10:07:02

在uniapp里面处理流式请求相对于web端来说有点麻烦,下面我将讲述几种处理流式请求的方式。

1.websocket

WebSocket 是处理实时数据流的最佳选择之一,UniApp 提供了原生的 WebSocket 支持:

<template>
  <view class="container">
    <scroll-view scroll-y class="data-container" :scroll-top="scrollTop">
      <view v-for="(item, index) in messages" :key="index" class="message-item">
        {{ item }}
      </view>
    </scroll-view>
    <view class="control-panel">
      <button @click="connectSocket" type="primary" :disabled="isConnected">连接</button>
      <button @click="closeSocket" type="warn" :disabled="!isConnected">断开</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      socketTask: null,
      isConnected: false,
      messages: [],
      scrollTop: 0
    }
  },
  methods: {
    connectSocket() {
      this.socketTask = uni.connectSocket({
        url: 'wss://your-websocket-server.com/stream',
        success: () => {
          console.log('准备连接...')
        }
      })
      
      this.socketTask.onOpen(() => {
        this.isConnected = true
        this.addMessage('连接已建立')
        // 订阅数据流
        this.socketTask.send({
          data: JSON.stringify({action: 'subscribe'})
        })
      })
      
      this.socketTask.onMessage((res) => {
        this.addMessage(res.data)
      })
      
      this.socketTask.onClose(() => {
        this.isConnected = false
        this.addMessage('连接已关闭')
      })
      
      this.socketTask.onError((res) => {
        this.addMessage('错误: ' + JSON.stringify(res))
      })
    },
    
    closeSocket() {
      if (this.socketTask && this.isConnected) {
        this.socketTask.close()
      }
    },
    
    addMessage(msg) {
      this.messages.push(typeof msg === 'string' ? msg : JSON.stringify(msg))
      this.$nextTick(() => {
        this.scrollTop = 99999 // 滚动到底部
      })
    }
  },
  onUnload() {
    // 页面卸载时关闭连接
    this.closeSocket()
  }
}
</script>

<style>
.container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  padding: 20rpx;
}
.data-container {
  flex: 1;
  border: 1px solid #eee;
  padding: 20rpx;
  margin-bottom: 20rpx;
  background-color: #f9f9f9;
}
.message-item {
  padding: 10rpx;
  border-bottom: 1px solid #eee;
  word-break: break-all;
}
.control-panel {
  display: flex;
  justify-content: space-around;
  padding: 20rpx 0;
}
</style>

2.uni.request 处理流式请求

这种方式适用于h5页面和小程序,不适用于app

<template>
  <view class="container">
    <view class="stream-container">
      <view v-for="(item, index) in streamData" :key="index" class="stream-item">
        {{ item }}
      </view>
    </view>
    <button @click="startStream" type="primary">开始接收流数据</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      streamData: [],
      dataBuffer: ''
    }
  },
  methods: {
    startStream() {
      uni.request({
        url: 'https://your-stream-api.com/stream',
        method: 'GET',
        enableChunked: true,
        dataType: 'text',
        onChunkReceived: (res) => {
          this.handleChunk(res.data);
        },
        success: (res) => {
          uni.showToast({
            title: '流数据接收完成',
            icon: 'success'
          });
        },
        fail: (err) => {
          uni.showModal({
            title: '错误',
            content: '流数据接收失败: ' + JSON.stringify(err),
            showCancel: false
          });
        }
      });
    },
    
    handleChunk(chunk) {
      // 将接收到的数据添加到缓冲区
      this.dataBuffer += chunk;
      
      // 处理可能的换行符分隔的数据
      const lines = this.dataBuffer.split('\n');
      
      // 保留最后一个可能不完整的行
      this.dataBuffer = lines.pop() || '';
      
      // 处理完整的行
      for (const line of lines) {
        if (line.trim()) {
          try {
            // 尝试解析JSON
            const data = JSON.parse(line);
            this.streamData.push(JSON.stringify(data));
          } catch (e) {
            // 非JSON数据直接显示
            this.streamData.push(line);
          }
        }
      }
    }
  }
}
</script>

<style>
.container {
  padding: 20px;
}
.stream-container {
  border: 1px solid #eee;
  padding: 10px;
  margin-bottom: 20px;
  max-height: 300px;
  overflow-y: auto;
}
.stream-item {
  padding: 5px 0;
  border-bottom: 1px solid #f5f5f5;
}
</style>

3.使用 SSE (Server-Sent Events)

虽然 UniApp 没有原生的 SSE API,但可以通过封装 XMLHttpRequest 来实现 SSE:

function createSSEConnection(url) {
  // 创建一个标准的XMLHttpRequest对象
  const xhr = new XMLHttpRequest()
  xhr.open('GET', url, true)
  xhr.setRequestHeader('Accept', 'text/event-stream')
  xhr.setRequestHeader('Cache-Control', 'no-cache')
  
  // 设置响应类型为文本
  xhr.responseType = 'text'
  
  // 数据缓冲区
  let buffer = ''
  
  // 处理进度事件
  xhr.onprogress = function(e) {
    // 获取新数据
    const newData = xhr.responseText.substring(buffer.length)
    if (newData) {
      buffer += newData
      
      // 按行分割数据
      const lines = newData.split('\n')
      for (const line of lines) {
        if (line.startsWith('data:')) {
          const eventData = line.substring(5).trim()
          // 触发数据处理
          handleSSEData(eventData)
        }
      }
    }
  }
  
  xhr.onerror = function(e) {
    console.error('SSE连接错误:', e)
  }
  
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        console.log('SSE连接完成')
      } else {
        console.error('SSE连接失败:', xhr.status)
      }
    }
  }
  
  // 发送请求
  xhr.send()
  
  return xhr
}

// 处理SSE数据
function handleSSEData(data) {
  try {
    const parsedData = JSON.parse(data)
    console.log('收到SSE数据:', parsedData)
    // 处理数据...
  } catch (e) {
    console.log('收到SSE文本:', data)
    // 处理非JSON数据...
  }
}

// 使用方法
const sseConnection = createSSEConnection('https://your-sse-endpoint.com/events')

// 关闭连接
function closeSSE() {
  if (sseConnection) {
    sseConnection.abort()
  }
}

4.使用分页和轮询模拟流

对于不支持真正的流式请求的场景,可以使用分页和轮询来模拟流式体验:

// 轮询获取数据
let lastId = 0
let isPolling = false

function startPolling() {
  isPolling = true
  poll()
}

function stopPolling() {
  isPolling = false
}

function poll() {
  if (!isPolling) return
  
  uni.request({
    url: 'https://your-api.com/data',
    data: {
      last_id: lastId,
      limit: 10
    },
    success: (res) => {
      const data = res.data
      if (data && data.items && data.items.length > 0) {
        // 处理接收到的数据
        processItems(data.items)
        
        // 更新最后ID用于下次请求
        lastId = data.items[data.items.length - 1].id
      }
      
      // 如果还有更多数据,继续轮询
      if (data.has_more) {
        setTimeout(poll, 1000) // 1秒后再次轮询
      } else {
        console.log('所有数据接收完毕')
        isPolling = false
      }
    },
    fail: (err) => {
      console.error('轮询失败:', err)
      // 错误后延迟重试
      setTimeout(() => {
        if (isPolling) poll()
      }, 3000)
    }
  })
}

5.app端适用renderjs

该方式适用于app端进行流式请求,在app端上面的几种方式我都尝试过除了websocket没有一个能用的,经过反复的查询才找到适用renderjs这种方式。

RenderJS 是 UniApp 提供的一个运行在视图层的 JavaScript 引擎,允许开发者直接操作 DOM 和使用浏览器特有的 API。

<view
			class=""
			:sseValue="sseValue"
			:change:sseValue="renderScript.getSseValue"
			:messagesRenderjs="messagesRenderjs"
			:change:messagesRenderjs="renderScript.getMessage"
			:downSend="downSend"
			:change:downSend="renderScript.getDownSend"
			:modelchangeValue="modelchangeValue"
			:change:modelchangeValue="renderScript.getModel"
		></view>
通过这种方式来调用renderjs
<script module="renderScript" lang="renderjs">
export default {
	data() {
		return {
			VITE_AIR14B_url: '',
			VITE_AILOCAL:'',
			VITE_ANYTHING:'',
			modelValue: 'qwen2.5:14b',
			messages: [],
			downValue: false,
			loading: false,
			downSentValue: false,
			biaoshi: false,
			model:'qianwen'
		};
	},
	methods: {
		getMessage(val) {
			// console.log(val,'message')
			this.messages = val;
		},
		async getSseValue(val) {
			console.log(val,'val')
			if(this.model=='qianwen'){
				await this.changeModel(val);
			}else if(this.model=='locel'){
				await this.changeModelLocal(val)
			}else if(this.model=='water'){
				await this.changeModelAnything(val)
			}else if(this.model=='sxyd'){
				console.log('sxyd')
			}
		},
		// 暂停生成
		getDownSend(val) {
			console.log(val, 'valvalval');
			if (!this.biaoshi) {
				this.biaoshi = true;
			} else {
				this.downSentValue = true;
			}

			//停止生成的标志
			// this.downSentValue=true
		},
		// 获取模型
		getModel(val) {
			console.log(val);
			this.model=val
		},
		// 接收流式数据qianwen
		async changeModel(val) {
			let params = {
				model: this.modelValue,
				messages: this.messages,
				stream: true
			};
			try {
				// 发送请求到 API
				const response = await fetch(this.VITE_AIR14B_url + '/api/chat', {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json'
					},
					body: JSON.stringify(params)
				});
				// 获取流式响应体
				const reader = response.body.getReader();
				const decoder = new TextDecoder('utf-8'); // 解码器,将字节流转换为字符串
				this.downValue = false;
				let value = ''; // 用于拼接收到的每一部分 response 内容
				// 获取响应之后关闭loading
				// this.$ownerInstance.callMethod('closeLoading', null);

				this.messages.push({
					role: 'assistant',
					content: ''
				});
				// 逐块读取数据
				while (!this.downValue) {
					// 读取数据块
					const { done: isDone, value: chunk } = await reader.read();
					// if (this.onDown.value) {
					//     this.downValue = true;
					//     break;
					// }
					if (this.downSentValue) {
						this.downValue = true;
						this.downSentValue = false;
						this.$ownerInstance.callMethod('setSend', true);
						this.messages[this.messages.length - 1].content += '\n\n[已取消生成]';
						this.$ownerInstance.callMethod('setMessageValue', this.messages);
						break;
					}
					this.downValue = isDone;
					if (this.downValue) {
						this.$ownerInstance.callMethod('setSend', true);
					} else {
						this.$ownerInstance.callMethod('setSend', false);
					}
					let chunkString = decoder.decode(chunk, { stream: true });
					// 将字节流转换为字符串并追加到 value
					value += chunkString;
					// 打印已接收的部分数据
					// console.log('Received chunk:', chunkString);
					if (chunkString && this.messages[this.messages.length - 1].role === 'assistant') {
						try {
							const parsedChunk = JSON.parse(chunkString);
							const content = parsedChunk?.message?.content; // 使用可选链避免属性访问错误
							if (content === undefined) {
								throw new Error('Invalid chunk structure: Missing message.content');
							}
							// 确保 messages 数组非空
							if (this.messages.length === 0) {
								throw new Error('Messages array is empty');
							}
							this.messages[this.messages.length - 1].content += content;
							if(this.messages[this.messages.length - 1].content){
								// 获取响应之后关闭loading
							    this.$ownerInstance.callMethod('closeLoading', null);
							}
							// 将值传递出去
							this.$ownerInstance.callMethod('setMessageValue', this.messages);
						} catch (error) {
							console.error('处理 chunk 时发生错误:', error);
						}
					}
				}
				// console.log('Final response:', value);
			} catch (error) {
				console.error('Error during fetch request:', error);
				this.loading = false;
				// 显示错误消息
				if (this.messages.length > 0 && this.messages[this.messages.length - 1].role === 'assistant') {
					this.messages[this.messages.length - 1].content += '\n\n[生成回答时出现错误]';
				}
			}
		},
	}
};
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2322057.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI小白的第七天:必要的数学知识(概率)

概率 Probability 1. 概率的定义 概率是一个介于 0 和 1 之间的数&#xff0c;表示某个事件发生的可能性&#xff1a; 0&#xff1a;事件不可能发生。1&#xff1a;事件必然发生。0 到 1 之间&#xff1a;事件发生的可能性大小。 例如&#xff0c;掷一枚公平的硬币&#xf…

[Windows] 图吧工具箱

[Windows] 图吧工具箱 链接&#xff1a;https://pan.xunlei.com/s/VOMCXYDix3pvwdkU7w7bfVsDA1?pwdk8v5# DIY爱好者的必备工具

【AIGC】图片变视频 - SD ComfyUI视频生成

效果图 完整过程 SD ComfyUI 下载 下载 https://pan.quark.cn/s/64b808baa960 解压密码&#xff1a;bilibili-秋葉aaaki 完整 https://www.bilibili.com/video/BV1Ew411776J/ SD ComfyUI 安装 1.解压 2.将controlnet内部文件复制到 ComfyUI-aki-v1.6\ComfyUI\models\control…

JVM详解(包括JVM内存模型与GC垃圾回收)

&#x1f4d6;前言&#xff1a; 学会使用Java对于一个程序员是远远不够的。Java语法的掌握只是一部分&#xff0c;另一部分就是需要掌握Java内部的工作原理&#xff0c;从编译到运行&#xff0c;到底是谁在帮我们完成工作的&#xff1f; 接下来着重对Java虚拟机&#xff0c;也就…

cocos creator 笔记-路边花草

版本&#xff1a;3.8.5 实现目标&#xff1a;给3d道路生成路边景观花草 在场景下创建一个节点&#xff0c;我这里种植两种花草模型&#xff0c;兰花和菊花&#xff0c;所以分别在节点下另创建两个节点&#xff0c;为了静态合批。 1.将花草模型分别拖入场景中&#xff0c;制作…

Langchain中的表格解析:RAG 和表格的爱恨情仇

实现 RAG(Retrieval-Augmented Generation)是一个挑战,尤其是在有效解析和理解非结构化文档中的表格时。这在处理扫描文档或图像格式的文档时尤为困难。这些挑战至少包括以下三个方面: 1.表格的“叛逆期”:不准确的解析可能会破坏表格结构: 表格在文档里就像个叛逆的青少…

CAT1模块 EC800M HTTP 使用后续记录

记录一下 CAT1 模块EC800 HTTP 使用后续遇到的问题 by 矜辰所致目录 前言一、一些功能的完善1.1 新的交互指令添加1.2 连不上网络处理 二、问题出现三、分析及解决3.1 定位问题3.2 问题分析与解决3.2.1 查看变量在内存中的位置 3.3 数据类型说明3.3.1 常用格式化输出符号…

Python 标准库与数据结构

Python的标准库提供了丰富的内置数据结构和函数&#xff0c;使用这些工具能为我们提供一套强有力的工具。 需要注意的是&#xff0c;相比C与Java&#xff0c;Python的一些特点&#xff1a; Python不需要显式声明变量类型Python没有模板(Template)的概念&#xff0c;因为Pytho…

大疆上云api介绍

概述 目前对于 DJI 无人机接入第三方云平台,主要是基于 MSDK 开发定制 App,然后自己定义私有上云通信协议连接到云平台中。这样对于核心业务是开发云平台,无人机只是其中一个接入硬件设备的开发者来说,重新基于 MSDK 开发 App 工作量大、成本高,同时还需要花很多精力在无人…

2025-03-25 Unity 网络基础4——TCP同步通信

文章目录 1 Socket1.1 Socket 类型1.2 构造 Socket1.3 常用属性1.4 常用方法 2 TCP 通信2.1 服务端配置2.2 客户端配置2.3 进行通信2.4 多设备通信 3 区分消息 1 Socket ​ Socket 是 C# 提供的网络通信类&#xff08;其它语言也有对应的 Socket 类&#xff09;&#xff0c;是…

C++进阶(一)

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 本篇博客是讲解函数的重载以及引用的知识点的。 文章目录 前言 1.函数重载 1.1何为函数重载 1.2函数重载的作用 1.3函数重载的实现 2.引用 2.1何为引用 2.2定义引用 2.3引用特性 2.4常引用 2…

深度解读DeepSeek:开源周(Open Source Week)技术解读

深度解读DeepSeek&#xff1a;开源周&#xff08;Open Source Week&#xff09;技术解读 深度解读DeepSeek&#xff1a;源码解读 DeepSeek-V3 深度解读DeepSeek&#xff1a;技术原理 深度解读DeepSeek&#xff1a;发展历程 文章目录 一、开源内容概览Day1&#xff1a;FlashMLAD…

AI Agent开发与应用

AI Agent开发与应用&#xff1a;本地化智能体实践——本地化智能体开发进展与主流框架分析 我要说的都在ppt里面了&#xff0c;相关复现工作请参考ai agent开发实例 OpenManus Dify Owl 第二个版本更新了对话的框架&#xff0c;通过gradio做了一个全新的界面 只测试了阿里云…

石斛基因组-文献精读122

A chromosome-level Dendrobium moniliforme genome assembly reveals the regulatory mechanisms of flavonoid and carotenoid biosynthesis pathways 《染色体水平的石斛基因组组装揭示了黄酮类和胡萝卜素生物合成途径的调控机制》 摘要 石斛&#xff08;Dendrobium monil…

javaSE.多维数组

1 final 引用类型 final int[] arr 继承Object 的引用类型&#xff0c;不能改变引用的对象 存的其实是引用 数组类型数组&#xff0c;其实存的是引用 int [][] arr new int[][] { {1,2,3}, {4,5,6} };int [] a arr[0]; int [] b arr[1];

Python条件处理,新手入门到精通

Python条件处理&#xff0c;新手入门到精通 对话实录 **小白**&#xff1a;&#xff08;崩溃&#xff09;我写了if x 1:&#xff0c;为什么Python会报错&#xff1f; **专家**&#xff1a;&#xff08;推眼镜&#xff09;**是赋值&#xff0c;才是比较**&#xff01;想判断相…

JPA实体类注解缺失异常全解:从报错到防御!!!

&#x1f6a8; JPA实体类注解缺失异常全解&#xff1a;从报错到防御 &#x1f6e1;️ 一、&#x1f4a5; 问题现象速览 // 经典报错示例 Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.example.entity.Product典型症状&#xff1a; &…

【C语言】多进程/多线程

【C语言】多进程/多线程 参考链接多进程/多线程服务器1. 多进程服务器2. 多线程服务器 结语参考链接 参考链接 c 中文网 菜鸟 c 多进程/多线程服务器 多进程和多线程是常用的并发编程技术。它们都允许程序同时执行多个任务&#xff0c;提高了系统的资源利用率和程序的运行效率…

模糊数学 | 模型 / 集合 / 关系 / 矩阵

注&#xff1a;本文为来自 “模糊数学 | 模型及其应用” 相关文章合辑。 略作重排。 如有内容异常&#xff0c;请看原文。 模糊数学模型&#xff1a;隶属函数、模糊集合的表示方法、模糊关系、模糊矩阵 wamg 潇潇 于 2019-05-06 22:35:21 发布 1.1 模糊数学简介 1965 年&a…

QinQ项展 VLAN 空间

随着以太网技术在网络中的大量部署&#xff0c;利用 VLAN 对用户进行隔离和标识受到很大限制。因为 IEEE802.1Q 中定义的 VLAN Tag 域只有 12 个比特&#xff0c;仅能表示 4096 个 VLAN&#xff0c;无法满足城域以太网中标识大量用户的需求&#xff0c;于是 QinQ 技术应运而生。…