阶段六: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判断用户是否登录。

相关新闻

阶段五:HttpOnly Cookie 登录持久化

阶段五:HttpOnly Cookie 登录持久化

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

2026/10/14 23:19:34 阅读更多 →
番茄钟终极指南:如何用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/10/10 10:35:27 阅读更多 →
HarmonyOS:ArkTS数据模型的设计与演进实战

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

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

2026/10/11 16:41:13 阅读更多 →

最新新闻

哇塞!原来毕业论文还能这样写?2026降AIGC网站推荐合集

哇塞!原来毕业论文还能这样写?2026降AIGC网站推荐合集

一、核心工具 TOP4(实测有效) 1. 千笔AI—— 中文双降天花板(首选) 核心优势:查重 降 AI 双效合一,适配知网 / 维普 / 万方 2026 最新算法,实测重复率 42%→8.3%、AIGC 率 56%→4.2%。 核心功能…

2026/10/15 1:16:56 阅读更多 →
MIPS单周期CPU数据通路从零搭建:指令、硬件与控制信号全解析

MIPS单周期CPU数据通路从零搭建:指令、硬件与控制信号全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 1:16:56 阅读更多 →
交叉验证:原理、变体与规避评估陷阱的完整指南

交叉验证:原理、变体与规避评估陷阱的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 1:16:56 阅读更多 →
PyTorch工程化源码拆解:数据流、训练循环与部署落地

PyTorch工程化源码拆解:数据流、训练循环与部署落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 1:16:56 阅读更多 →
Python爬虫实战:requests+XPath稳定抓取豆瓣TOP250图书数据

Python爬虫实战:requests+XPath稳定抓取豆瓣TOP250图书数据

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 1:16:56 阅读更多 →
基于MP2642GR-Z与STM32F411RE的串联电池主动均衡器设计实践

基于MP2642GR-Z与STM32F411RE的串联电池主动均衡器设计实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 1:15:56 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →