uniapp处理流式请求
在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 = truethis.addMessage('连接已建立')// 订阅数据流this.socketTask.send({data: JSON.stringify({action: 'subscribe'})})})this.socketTask.onMessage((res) => {this.addMessage(res.data)})this.socketTask.onClose(() => {this.isConnected = falsethis.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 {// 尝试解析JSONconst 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 = falsefunction startPolling() {isPolling = truepoll()
}function stopPolling() {isPolling = false
}function poll() {if (!isPolling) returnuni.request({url: 'https://your-api.com/data',data: {last_id: lastId,limit: 10},success: (res) => {const data = res.dataif (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。
<viewclass="":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},// 接收流式数据qianwenasync changeModel(val) {let params = {model: this.modelValue,messages: this.messages,stream: true};try {// 发送请求到 APIconst 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 });// 将字节流转换为字符串并追加到 valuevalue += 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){// 获取响应之后关闭loadingthis.$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>
相关文章:
uniapp处理流式请求
在uniapp里面处理流式请求相对于web端来说有点麻烦,下面我将讲述几种处理流式请求的方式。 1.websocket WebSocket 是处理实时数据流的最佳选择之一,UniApp 提供了原生的 WebSocket 支持: <template><view class"container&…...
代码随想录算法训练营第四十一天|买卖股票专题:121. 买卖股票的最佳时机、122.买卖股票的最佳时机II、123.买卖股票的最佳时机III
动规五部曲牢记于心 1、确定好dp[j]数组,以及下标含义 2、推导出dp[j]公式 3、初始化,关键dp[0][0]、dp[0][1],第i天,后面的01表示状态:持有、不持有 4、确定遍历顺序: 如果求组合问题,不考虑排…...
AI小白的第七天:必要的数学知识(概率)
概率 Probability 1. 概率的定义 概率是一个介于 0 和 1 之间的数,表示某个事件发生的可能性: 0:事件不可能发生。1:事件必然发生。0 到 1 之间:事件发生的可能性大小。 例如,掷一枚公平的硬币…...
[Windows] 图吧工具箱
[Windows] 图吧工具箱 链接:https://pan.xunlei.com/s/VOMCXYDix3pvwdkU7w7bfVsDA1?pwdk8v5# DIY爱好者的必备工具...
Docker镜像迁移方案
Docker镜像迁移方案 文章目录 Docker镜像迁移方案一:背景二:操作方式三:异常原因参考: 一:背景 比如机器上已经有先有的容器,但是docker pull的时候是失败的二:操作方式 1、停止正在运行的容器…...
1264. 动态求连续区间和-acwing -树状数组
原题链接:1264. 动态求连续区间和 - AcWing题库 给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和。 输入格式 第一行包含两个整数 n 和m,分别表示数的个数和操作次数。 第…...
三分钟读懂微服务
一、什么是微服务 微服务,简单来说,就是把一个庞大复杂的软件系统,拆分成一个个小型的、独立的服务模块。打个比方,一个大型商场就如同传统的单体架构软件系统,里面所有的店铺、设施都紧密关联在一起。而微服务架构下…...
【AIGC】图片变视频 - SD ComfyUI视频生成
效果图 完整过程 SD ComfyUI 下载 下载 https://pan.quark.cn/s/64b808baa960 解压密码:bilibili-秋葉aaaki 完整 https://www.bilibili.com/video/BV1Ew411776J/ SD ComfyUI 安装 1.解压 2.将controlnet内部文件复制到 ComfyUI-aki-v1.6\ComfyUI\models\control…...
JVM详解(包括JVM内存模型与GC垃圾回收)
📖前言: 学会使用Java对于一个程序员是远远不够的。Java语法的掌握只是一部分,另一部分就是需要掌握Java内部的工作原理,从编译到运行,到底是谁在帮我们完成工作的? 接下来着重对Java虚拟机,也就…...
cocos creator 笔记-路边花草
版本:3.8.5 实现目标:给3d道路生成路边景观花草 在场景下创建一个节点,我这里种植两种花草模型,兰花和菊花,所以分别在节点下另创建两个节点,为了静态合批。 1.将花草模型分别拖入场景中,制作…...
在shell脚本内部获取该脚本所在目录的绝对路径
目录 需求描述 方法一:使用 dirname 和 readlink 命令 方法二:使用 BASH_SOURCE 变量 方法三:仅使用纯 Bash 实现 需求描述 工作中经常有这样情况,需要在脚本内部获取该脚本自己所在目录的绝对路径。 假如有一个脚本/a/b/c/…...
Qt 线程类
线程类 这些类与线程应用程序相关。 Concurrent Filter and Filter-Reduce 并行地从序列中选择值并组合它们 Concurrent Map and Map-Reduce 并行地从序列中转换值并组合它们 Concurrent Run 在单独线程中运行任务的简单方法 Concurrent Task 在独立线程中运行任务的可…...
Langchain中的表格解析:RAG 和表格的爱恨情仇
实现 RAG(Retrieval-Augmented Generation)是一个挑战,尤其是在有效解析和理解非结构化文档中的表格时。这在处理扫描文档或图像格式的文档时尤为困难。这些挑战至少包括以下三个方面: 1.表格的“叛逆期”:不准确的解析可能会破坏表格结构: 表格在文档里就像个叛逆的青少…...
神奇的闹钟(算法题)
神奇的闹钟 题目 原题 小蓝发现了一个神奇的闹钟,从纪元时间(19701970 年 11 月 11 日 00:00:0000:00:00)开始,每经过 xx 分钟,这个闹钟便会触发一次闹铃 (纪元时间也会…...
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的标准库提供了丰富的内置数据结构和函数,使用这些工具能为我们提供一套强有力的工具。 需要注意的是,相比C与Java,Python的一些特点: Python不需要显式声明变量类型Python没有模板(Template)的概念,因为Pytho…...
NIO入门
IO和NIO的区别: IO:通过流处理数据,仅支持阻塞IO。 核心组件:InputStream /OutputStream用于字节的读写,Reader / Writer:用于字符流的读写。读取过程中无法被中断,是阻塞式IO。 NIO:通过管道处…...
leetcode 用队列模拟栈
这个其实只需要一个队列就可以的,但是我这里用的是2个队列进行替换, 先转n-1个到空的队列, 然后在此基础上进行队列的互换,把剩下的那一个元素所在的队列进行poleft操作就可以了。 class MyStack:def __init__(self):self.q1_i…...
spring security 使用的过滤器还是拦截器
spring security 使用的过滤器还是拦截器 Spring Security 是一个强大的安全框架,用于保护 Java 应用程序。它主要使用过滤器(Filters)来实现安全功能,而不是拦截器(Interceptors)。不过,它也提…...
大疆上云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# 提供的网络通信类(其它语言也有对应的 Socket 类),是…...
C++进阶(一)
个人主页:PingdiGuo_guo 收录专栏:C干货专栏 前言 本篇博客是讲解函数的重载以及引用的知识点的。 文章目录 前言 1.函数重载 1.1何为函数重载 1.2函数重载的作用 1.3函数重载的实现 2.引用 2.1何为引用 2.2定义引用 2.3引用特性 2.4常引用 2…...
深度解读DeepSeek:开源周(Open Source Week)技术解读
深度解读DeepSeek:开源周(Open Source Week)技术解读 深度解读DeepSeek:源码解读 DeepSeek-V3 深度解读DeepSeek:技术原理 深度解读DeepSeek:发展历程 文章目录 一、开源内容概览Day1:FlashMLAD…...
AI Agent开发与应用
AI Agent开发与应用:本地化智能体实践——本地化智能体开发进展与主流框架分析 我要说的都在ppt里面了,相关复现工作请参考ai agent开发实例 OpenManus Dify Owl 第二个版本更新了对话的框架,通过gradio做了一个全新的界面 只测试了阿里云…...
石斛基因组-文献精读122
A chromosome-level Dendrobium moniliforme genome assembly reveals the regulatory mechanisms of flavonoid and carotenoid biosynthesis pathways 《染色体水平的石斛基因组组装揭示了黄酮类和胡萝卜素生物合成途径的调控机制》 摘要 石斛(Dendrobium monil…...
javaSE.多维数组
1 final 引用类型 final int[] arr 继承Object 的引用类型,不能改变引用的对象 存的其实是引用 数组类型数组,其实存的是引用 int [][] arr new int[][] { {1,2,3}, {4,5,6} };int [] a arr[0]; int [] b arr[1];...
Spring IOC容器详解:深入理解控制反转与依赖注入
一、什么是IOC? 在java当中一个类想要使用另一个类的方法,就必须在这个类当中创建这个类的对象,那么可能会出现如下情况, 比如A类当中创建着B对象,B类当中有C对象,C类当中有A对象,这个如果一个类…...
Python条件处理,新手入门到精通
Python条件处理,新手入门到精通 对话实录 **小白**:(崩溃)我写了if x 1:,为什么Python会报错? **专家**:(推眼镜)**是赋值,才是比较**!想判断相…...
JPA实体类注解缺失异常全解:从报错到防御!!!
🚨 JPA实体类注解缺失异常全解:从报错到防御 🛡️ 一、💥 问题现象速览 // 经典报错示例 Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.example.entity.Product典型症状: &…...
Spring 源码硬核解析系列专题(三十二):Spring Cloud LoadBalancer 的负载均衡源码解析
在前几期中,我们从 Spring 核心到 Spring Boot 的多个模块,再到 Spring Cloud Alibaba,逐步揭示了 Spring 生态在微服务领域的广泛应用。Spring Cloud LoadBalancer 是 Spring Cloud 提供的客户端负载均衡组件,替代 Ribbon,支持服务发现和负载均衡策略。本篇将深入 Spring…...
