尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
阶段六:SSE AI 流式对话
从零构建带AI流式对话的Vue2全栈应用-CSDN博客本文是对这篇文章阶段六的讲解一、普通接口为什么不能产生打字机效果普通接口用户提问 ↓ 后端生成完整回答 ↓ 一次性返回整个回答 ↓ 页面一次性显示SSE用户提问 ↓ 后端保持连接 ↓ 发送第一个字 ↓ 发送第二个字 ↓ 发送第三个字 ↓ 直到结束这就是类似 ChatGPT 的流式输出。二、SSE 是什么SSE 全称Server-Sent Events意思是服务器持续向浏览器发送事件。它主要是单向通信服务器 → 浏览器浏览器使用new EventSource(url)连接服务器。三、SSE 后端响应头res.setHeader( Content-Type, text/event-stream; charsetutf-8 )告诉浏览器这是 SSE 数据而不是普通 JSON。res.setHeader( Cache-Control, no-cache, no-transform )禁止缓存和修改流式内容。res.setHeader( Connection, keep-alive )告诉网络连接不要立刻关闭。res.setHeader( X-Accel-Buffering, no )避免部分代理服务器把数据缓存起来等全部完成后才一次性返回。res.flushHeaders()立即发送响应头。不需要等待第一段正文。四、SSE 消息格式后端res.write( data: ${JSON.stringify(data)}\n\n )SSE 格式要求data: 数据内容每条消息后必须有两个换行。代码中的\n\n就是两个换行。发送sendEvent({ type: content, text: 你 })浏览器收到data: {type:content,text:你}五、为什么要定义不同的事件类型我们自己设计了三种消息{ type: start }表示开始生成。{ type: content, text: 你 }表示新生成了一段文字。{ type: end }表示生成结束。还可以有{ type: error, message: 模型调用失败 }表示错误。六、模拟逐字发送const characters Array.from(answer)把字符串变成字符数组。例如Array.from(你好)得到[你, 好]然后let currentIndex 0记录当前发送到第几个字符。setIntervalconst timer setInterval(() { // 每隔 70 毫秒执行一次 }, 70)每隔 70 毫秒发送一个字符。第一次你第二次好第三次继续。这就产生了打字机效果。发送完成if ( currentIndex characters.length ) { sendEvent({ type: content, text: characters[currentIndex] }) currentIndex 1 return }如果还有字符就发送当前字符。全部发送完成后sendEvent({ type: end }) clearInterval(timer) res.end()clearInterval停止定时器。res.end()关闭 HTTP 响应。七、用户停止生成前端执行this.eventSource.close()浏览器关闭 SSE 连接。后端监听req.on(close, () { clearInterval(timer) })连接关闭后停止继续生成。真实大模型时还应该同时取消上游模型请求避免继续产生费用。八、为什么 SSE 使用EventSource不用 AxiosAxios 更适合普通请求发送请求 等待完整响应 得到结果SSE 需要连接持续存在 不断接收新消息浏览器原生提供EventSource所以使用this.eventSource new EventSource(streamUrl)九、聊天页面中的数据data() { return { question: , messages: [], streamingContent: , isStreaming: false, eventSource: null, nextMessageId: 1 } }questionquestion: 保存输入框内容。模板el-input v-modelquestion /v-model是双向绑定。用户输入文字 → question 变化同时question 变化 → 输入框显示变化messagesmessages: []保存已经完成的历史消息。例如[ { id: 1, role: user, content: 你好 }, { id: 2, role: assistant, content: 你好请问有什么可以帮助你 } ]streamingContentstreamingContent: 保存当前还在生成中的 AI 回答。收到一个字this.streamingContent data.text例如原来你 新字符好 结果你好isStreamingisStreaming: false表示是否正在生成。false可以发送新问题 true正在生成显示停止按钮模板可以根据它切换按钮el-button v-ifisStreaming 停止生成 /el-button el-button v-else 发送 /el-buttoneventSourceeventSource: null保存当前 SSE 连接对象。有了它才能主动执行this.eventSource.close()十、发送问题流程sendQuestion() { const trimmedQuestion this.question.trim() if ( !trimmedQuestion || this.isStreaming ) { return } }先去掉空格。如果问题为空直接结束。如果正在生成也直接结束避免同时建立多个连接。保存用户消息this.messages.push({ id: this.nextMessageId, role: user, content: trimmedQuestion })向数组添加一条用户消息。Vue 发现数组变化后会自动更新页面中的v-formessage in messages清空输入框this.question 因为输入框使用v-modelquestion所以数据变空后输入框也自动清空。建立 SSEconst streamUrl createAiStreamUrl( trimmedQuestion ) this.eventSource new EventSource(streamUrl)最终地址类似/api/ai/stream?question请介绍一下SSE十一、onmessagethis.eventSource.onmessage event { const data JSON.parse(event.data) }每次服务器发送data: ...都会触发onmessage。event.data是字符串。例如{type:content,text:你}通过JSON.parse(event.data)转换成 JavaScript 对象。处理文字if (data.type content) { this.streamingContent data.text || return }每收到一个文字片段就追加到页面。处理结束if (data.type end) { this.finishStream() }finishStream()会把临时回答保存到 messages 关闭 SSE 恢复发送按钮十二、保存流式消息saveStreamingMessage() { if (!this.streamingContent) { return } this.messages.push({ id: this.nextMessageId, role: assistant, content: this.streamingContent }) this.nextMessageId 1 this.streamingContent }生成过程中回答在streamingContent生成完成后转移到messages然后把临时内容清空。十三、为什么要在beforeDestroy关闭连接beforeDestroy() { this.closeEventSource() }当用户离开聊天页面时组件准备销毁。如果不关闭 SSE用户已经进入别的页面 后端仍然继续发送 连接仍占用资源所以离开页面前必须关闭。十四、自动滚动scrollToBottom() { this.$nextTick(() { const container this.$refs.messageContainer container.scrollTop container.scrollHeight }) }$refs模板中div refmessageContainer脚本中可以通过this.$refs.messageContainer获得这个真实 HTML 元素。scrollHeight表示内容总高度。scrollTop表示当前滚动位置。设置scrollTop scrollHeight就会滚动到底部。$nextTickVue 修改数据后真实网页不是立即同步完成。所以先等待 Vue 更新 DOMthis.$nextTick(...)再滚动。十五、为什么阶段五 Cookie 对 SSE 很重要原生EventSource不方便添加自定义请求头。很难像 Axios 一样手动写Authorization: Bearer ...但是浏览器会自动给同源 EventSource 请求携带 Cookie。所以组合是阶段五HttpOnly Cookie 阶段六EventSource后端requireAuth可以正常读取 Cookie判断用户是否登录。
RELATED

相关推荐

阶段五:HttpOnly Cookie 登录持久化

阶段五:HttpOnly Cookie 登录持久化

从零构建带AI流式对话的Vue2全栈应用-CSDN博客本文是对这篇文章阶段五的讲解 一、阶段五为什么要改 Token 保存方式 阶段四: Token 保存在 Vuex 前端 JavaScript 可以读取 Token 刷新后 Token 消失 阶段五: Token 保存在 HttpOnly Cookie JavaScrip…

📅 2026/10/14 8:46:50
番茄钟终极指南:如何用Tomodoro实现深度专注与高效时间管理

番茄钟终极指南:如何用Tomodoro实现深度专注与高效时间管理

番茄钟终极指南:如何用Tomodoro实现深度专注与高效时间管理 【免费下载链接】tomodoro A pomodoro web app with PIP mode, white noise generation, tasks and more! 项目地址: https://gitcode.com/gh_mirrors/to/tomodoro 你是否曾坐在电脑前数小时&#…

📅 2026/11/9 8:31:44
HarmonyOS:ArkTS数据模型的设计与演进实战

HarmonyOS:ArkTS数据模型的设计与演进实战

引子:数据设计好了,UI 就是套模板 星空运势应用经历了两次数据结构的变化。第一版是四个文件——Types.ts、SignsData.ts、TarotData.ts、Constants.ts,每个文件职责单一。重构后合并成了一个 DataModel.ets,12 星座的数据从 5 个…

📅 2026/9/10 16:08:09
MORE NEWS

更多资讯

📰

低压多少伏需要电工证 郑州报考避坑指南

低压多少伏需要电工证 郑州报考避坑指南 你现在手里拿着电工证吗?别让这个证耽误你找工作!最近在郑州,越来越多的工地和企业明确要求,低压电工作业必须持证上岗,否则连面试机会都没有。你是不是也在愁这个证怎么考、怎么报、怎么避坑?别急,这篇文章给…

📰

电工证复审完换新证吗千万别踩坑,平顶山房建电工必看

电工证复审完换新证吗千万别踩坑,平顶山房建电工必看 急需特种作业证才能上岗找工作,这事儿对咱们平顶山房建行业的电工来说,真不是开玩笑。尤其是很多人刚拿到电工证,还不清楚复审完是不是要换新证,一不小心就踩坑了。今天咱们就来聊聊【电工证复审完换…

📰

电工证怎么样领取补贴?工地太忙根本没时间复习考试,良心建议来了

电工证怎么样领取补贴?工地太忙根本没时间复习考试,良心建议来了 工地太忙根本没时间复习考试,很多人一听说要考电工证,脑袋就大。尤其是那些在郑州金水区房建项目上跑腿的施工员、电工、技术员,白天忙得脚不沾地,晚上还得加夜班,哪还有精力去准备考试…

📰

禹州市高压电工证到期换证流程全解析

禹州市高压电工证到期换证流程全解析 证书过期了不知道怎么复审延期?别慌,官方报名入口就在这儿,赶紧看清楚,别再耽误你的时间和机会。…

📰

电工证考试别再被坑了!如何快速考电工证视频+良心建议全在这

电工证考试别再被坑了!如何快速考电工证视频+良心建议全在这 不知道去哪报名怕被中介坑,这话说得真扎心。最近我帮朋友跑腿处理电工证考试的事,他一开始就被几个培训机构忽悠得团团转,差点花了冤枉钱。今天我就把 如何快速考电工证视频 和 良心建议…

📰

考电工作业操作考试怎么考才靠谱 全国通用证书怎么选

考电工作业操作考试怎么考才靠谱 全国通用证书怎么选 你是不是也在为 考电工作业操作考试 发愁?怕被中介坑?怕走弯路?别急,咱们今天就从报名、考试到证书使用全流程讲清楚,告诉你怎么避开陷阱,拿到 全国通用 的电工证。…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬