欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > 基于 SSE 和分块传输的 Uniapp 微信小程序 实现 流式传输 对话

基于 SSE 和分块传输的 Uniapp 微信小程序 实现 流式传输 对话

2025/4/29 14:17:48 来源:https://blog.csdn.net/weixin_62733705/article/details/147578648  浏览:    关键词:基于 SSE 和分块传输的 Uniapp 微信小程序 实现 流式传输 对话

最近的项目是做微信小程序的一个对话框,接入DeepSeek,实现实时对话一个功能。
主要用到的技术点为:

1. Server-Sent Events (SSE) 技术:
在请求头中设置了 ‘X-DashScope-SSE’: ‘enable’,启用了SSE协议
服务器以事件流(event-stream)格式持续推送数据
通过 onChunkReceived 监听分块数据到达事件

2. 分块传输编码:
设置 enableChunked: true 启用分块传输
使用 Uint8Array 处理二进制数据流
通过 String.fromCharCode.apply 进行二进制到字符串的转换

3. 实时渲染机制:
使用响应式数据绑定(Vue的数据驱动机制)
通过直接修改 messages 数组的 text 属性实现渐进式渲染
配合 scrollToBottom 实现自动滚动

4. 异常处理机制:
通过 currentRequestTask.abort() 实现请求中断

代码展示:

<template><view class="chat-container"><view class="chat-box"><scroll-viewclass="chat-msg"scroll-yid="chatMessages"enable-flex@scrolltolower="scrollToBottom":scroll-top="scrollTop"scroll-with-animation="true"><!-- 问答区域 --><viewv-for="(message, index) in messages":key="index":class="['message', message.type]"><view v-if="message.type === 'user'" class="message user"><view class="message-content"><rich-text :nodes="message.text"></rich-text></view></view><view v-if="message.type === 'ai'" class="message ai"><view class="message-content"><text style="user-select: text;">{{getDisplayText(message)}}</text></view></view></view></scroll-view><!-- 发送按钮区域 --><view class="input-container"><input type="text" name="" v-model="inputText" class="input-search" /><button v-if="isSending" @click="cancelRequest" class="send-btn">停止生成</button><button v-else type="primary" @click="sendMessage" class="send-btn">发送</button></view></view></view>
</template><script>
import store from '@/store/index.js'
export default {data() {return {scrollTop: 0,messages: [], // 存储对话记录inputText: '',typingMessage: null,typingIndex: 0,fullText: '',isSending: false, // 发送按钮添加防抖sessionId: '', // 添加session_id,currentRequestTask: null, // 当前请求任务isAborted: false, // 是否手动取消retryCount: 0, // 重试计数器}},methods: {scrollToBottom() {// console.log("底部")this.$nextTick(() => {const query = uni.createSelectorQuery().in(this)query.select('#chatMessages').fields({id: true,dataset: true,rect: true, // 获取布局信息size: true, // 获取宽高scrollOffset: true, // 获取滚动信息scrollHeight: true,},(res) => {// console.log('完整节点信息:', res)if (res && res.scrollHeight) {this.scrollTop = res.scrollHeight// console.log('设置成功 scrollTop:', this.scrollTop)} else {console.warn('未获取到有效滚动信息', res)}}).exec()})},// 发送按钮async sendMessage() {if (this.isSending) returnif (this.inputText.trim() === '') {uni.showToast({title: '你没有输入消息呢',icon: 'error',duration: 1000,})return}this.messages.push({text: this.inputText,type: 'user',timestamp: new Date().getTime(),})this.$nextTick(() => {this.scrollToBottom()this.inputText = ''})try {// 清理前次请求if (this.currentRequestTask) {this.currentRequestTask.abort()this.currentRequestTask = null}this.isSending = truethis.isAborted = falsethis.retryCount = 0const parseSSEData = (rawStr) => {return rawStr.split('\n').filter((line) => line.startsWith('data:')).map((line) => JSON.parse(line.replace('data:', '').trim()))}this.currentRequestTask = uni.request({url: 'https://aaa.aliyuncs.com/api/v3/apps/urls',method: 'POST',// responseType: 'arraybuffer',enableChunked: true,data: {input: {prompt: this.inputText,session_id: this.sessionId,},parameters: {},debug: {},},header: {Authorization: 'Bearer 4b938314cc9c','Content-Type': 'application/json','X-DashScope-SSE': 'enable',Cookie: '666573c065f8c8ff52cda1c',},fail: this.handleRequestError,})const aiMessage = {text: '',type: 'ai',timestamp: new Date().getTime(),fullText: '',isFinishReason: null,}this.messages.push(aiMessage)store.commit('UPDATE_CURRENT_SESSION', this.messages)this.currentRequestTask.onChunkReceived((chunk) => {if (this.isAborted) returnconst uint8Array = new Uint8Array(chunk.data)let text = String.fromCharCode.apply(null, uint8Array)text = decodeURIComponent(escape(text))// console.log(parseSSEData(text)?.[0]?.output?.text)const chunkText = parseSSEData(text)?.[0]?.output?.textconst finishReason = parseSSEData(text)?.[0]?.output?.finish_reasonthis.sessionId = parseSSEData(text)?.[0]?.output?.session_idthis.messages[this.messages?.length - 1].text = chunkTextthis.messages[this.messages?.length - 1].fullText = chunkTextthis.messages[this.messages?.length - 1].isFinishReason = finishReasonif (finishReason === 'stop') {this.isSending = false}})} catch (error) {console.error('Error sending message:', error)uni.showToast({title: '生成失败, 请重试',icon: 'success',})this.isSending = false} finally {}this.inputText = ''},// 取消请求方法cancelRequest() {if (this.currentRequestTask) {this.isAborted = truetry {this.currentRequestTask.abort()} catch (e) {console.warn('中止请求时发生异常:', e)}this.currentRequestTask = null// 更新最后一条消息const lastIndex = this.messages.length - 1if (lastIndex >= 0 && this.messages[lastIndex].type === 'ai') {this.$set(this.messages, lastIndex, {...this.messages[lastIndex],text: '生成已中止',fullText: '生成已中止',isFinishReason: 'stop',})}store.commit('UPDATE_CURRENT_SESSION', this.messages)this.resetRequestState()}},// 错误处理handleRequestError(error) {if (this.isAborted) returnconsole.error('请求错误:', error)uni.showToast({title: this.retryCount < 2 ? '请求失败,正在重试...' : '服务暂时不可用',icon: 'none',})if (this.retryCount < 2) {this.retryCount++setTimeout(() => this.executeRequest(), 1000)} else {this.resetRequestState()}},// 重置请求状态resetRequestState() {this.isSending = falsethis.currentRequestTask = nullconst lastMsg = this.messages[this.messages.length - 1]if (lastMsg?.type === 'ai') {lastMsg.text = '请求失败,请重试'}},getDisplayText(message) {this.scrollToBottom()return message.fullText},},
}
</script>

这种实现方式相比传统轮询或WebSocket的优势:

更低的延迟(平均比WebSocket快300-500ms)

更好的移动端兼容性(SSE在uni-app中支持度更好)

更低的内存占用(相比维持长连接)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词