[{"data":1,"prerenderedAt":83},["ShallowReactive",2],{"content:\u002Farticles:5449":3},{"item":4,"md":15,"html":16,"menu":17,"excerpt":34,"related":35},{"title":5,"len":6,"tags":7,"id":10,"time":11,"modifyTime":11,"encrypt":12,"showComments":13,"customSlug":14},"Nuxt3创建SSE server以及client的简明教程",1905,[8,9],"前端","vue",5449,1720261091923,false,true,"",null,"\u003Ch2 data-marked-line=\"1\">\u003Csup class=\"fake-head\" id=\"SSE(server-sent%20events)%E6%98%AF%E4%BB%80%E4%B9%88\">\u003C\u002Fsup>\u003Ca class=\"header-link\" href=\"#SSE(server-sent%20events)%E6%98%AF%E4%BB%80%E4%B9%88\">SSE(server-sent events)是什么\u003C\u002Fa>\u003C\u002Fh2>\u003Cp data-marked-line=\"2\">&emsp;&emsp;MDN上有\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FServer-sent_events\" target=\"_blank\">关于SSE的介绍\u003C\u002Fa>，可以把SSE理解为单向的WebSocket。普通请求 + WebSocket + SSE联手一起完善了浏览器网络请求体系。它们的区别如下：\u003C\u002Fp>\n\u003Cul data-marked-line=\"3\">\n\u003Cli>\u003Cstrong>普通请求(GET,POST,PUT等等)\u003C\u002Fstrong>：浏览器---&gt;服务器，单向非持续数据流。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>WebSocket\u003C\u002Fstrong>：浏览器&lt;---&gt;服务器，双向持续数据流。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>SSE\u003C\u002Fstrong>：浏览器&lt;---服务器，单向持续数据流。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp data-marked-line=\"7\">&emsp;&emsp;SSE的表现形式上和WebSocket差不多，依旧由浏览器主动发起请求，但是服务端不会立即返回，而是保持长连接，伺机返回数据，相应的，浏览器端则需要监听事件。\u003C\u002Fp>\n\u003Ch2 data-marked-line=\"9\">\u003Csup class=\"fake-head\" id=\"%E5%9C%BA%E6%99%AF%E8%A7%A3%E6%9E%90\">\u003C\u002Fsup>\u003Ca class=\"header-link\" href=\"#%E5%9C%BA%E6%99%AF%E8%A7%A3%E6%9E%90\">场景解析\u003C\u002Fa>\u003C\u002Fh2>\u003Cp data-marked-line=\"10\">&emsp;&emsp;知道了SSE能实现什么功能，你可能已经跃跃欲试了，但是SSE的使用场景是什么呢？为什么不直接使用WebSocket呢？\n&emsp;&emsp;一个经典的使用场景就是\u003Cem>AI聊天\u003C\u002Fem>。在用户输入提问后，AI会流式地返回消息，所以浏览器发送提问后，就只需要持续接受消息，不需要再发送消息了。\n&emsp;&emsp;以下内容以AI聊天为切入点，详解Nuxt3中如何实现这类需求。\u003C\u002Fp>\n\u003Cdiv data-marked-line=\"14\" class=\"container-block tip\">\n                    \u003Cp class=\"container-title\">需求\u003C\u002Fp>\n                    \u003Cp>&emsp;&emsp;假设我们有一个api_key，用这个key可以和ChatGPT聊天，需求是：开发一个web应用，让所有访问者都可以和ChatGPT聊天，而用户不需要知道key是什么。简单来说，就是实现一个ChatGPT套壳。\u003Cstrong>（假设ChatGPT的接口地址为\u003Ccode>https:\u002F\u002Fchatgpt.com\u002Fchat\u003C\u002Fcode>是基于SSE的）\u003C\u002Fstrong>\u003C\u002Fp>\n\n                  \u003C\u002Fdiv>\u003Ch2 data-marked-line=\"18\">\u003Csup class=\"fake-head\" id=\"%E7%9B%B8%E5%85%B3%E5%B7%A5%E5%85%B7\">\u003C\u002Fsup>\u003Ca class=\"header-link\" href=\"#%E7%9B%B8%E5%85%B3%E5%B7%A5%E5%85%B7\">相关工具\u003C\u002Fa>\u003C\u002Fh2>\u003Cp data-marked-line=\"19\">&emsp;&emsp;在网上搜索后，我找到了以下信息\u002F文档：\u003C\u002Fp>\n\u003Cul data-marked-line=\"20\">\n\u003Cli>Nuxt3目前已经原生支持SSE，提供\u003Ccode>createEventStream()\u003C\u002Fcode>函数，详见\u003Ca href=\"https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Fwebsocket#server-sent-events-sse\" target=\"_blank\">官方样例\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>一个很有价值的gist参考，用hookable实现NuxtServer的api间通信：\u003Ca href=\"https:\u002F\u002Fgist.github.com\u002FAtinux\u002F05836469acca9649fa2b9e865df898a2\" target=\"_blank\">gist\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>一个npm包，可以在nodejs里方便地创建SSE客户端：\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fgfortaine\u002Ffetch-event-source\" target=\"_blank\">@fortaine\u002Ffetch-event-source\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 data-marked-line=\"24\">\u003Csup class=\"fake-head\" id=\"%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90\">\u003C\u002Fsup>\u003Ca class=\"header-link\" href=\"#%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90\">流程解析\u003C\u002Fa>\u003C\u002Fh2>\u003Cp data-marked-line=\"25\">&emsp;&emsp;流程图如下(第一次用obsidian的excalidraw做这么复杂的图，做得有点差，见谅\u003Cimg src=\"\u002Fsticker\u002Faru\u002F20.png\" alt=\"sticker\"\u002F>)：\n\u003Cspan class=\"image-container\">\u003Cimg data-viewer alt=\"\" title=\"\" src=\"https:\u002F\u002Fr2-blog.yunyuyuan.net\u002Fo4q1vPW8suVTaMF.png\"\u002F>\u003Csmall class=\"desc\">\u003C\u002Fsmall>\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp data-marked-line=\"28\">&emsp;&emsp;流程详解：\u003C\u002Fp>\n\u003Col data-marked-line=\"29\">\n\u003Cli>浏览器生成一个随机uid，用来区分其他聊天。\u003Cspan style=\"color: red\">uid是必须的，否则无法区分聊天。\u003C\u002Fspan>\u003C\u002Fli>\n\u003Cli>浏览器请求\u003Ccode>\u002Fapi\u002Fstart-sse\u003C\u002Fcode>接口，携带uid和prompt。\u003C\u002Fli>\n\u003Cli>NuxtServer收到请求，开启一个\u003Ccode>fetch()\u003C\u002Fcode>请求ChatGPT的服务器，携带api_key和prompt(Nuxt作为sse的client)。随即返回\u003Ccode>{ success: true }\u003C\u002Fcode>。\u003C\u002Fli>\n\u003Cli>浏览器开启一个\u003Ccode>EventSource()\u003C\u002Fcode>，接口地址是\u003Ccode>\u002Fapi\u002Fsse\u003C\u002Fcode>，同时也携带uid。\u003C\u002Fli>\n\u003Cli>NuxtServer收到请求，创建一个eventStream并返回(Nuxt作为sse的server)，浏览器收到响应，触发\u003Ccode>onopen\u003C\u002Fcode>事件。\u003C\u002Fli>\n\u003Cli>在第3步中，NuxtServer开启了一个\u003Ccode>fetch()\u003C\u002Fcode>，其响应是流式的，每次收到响应都会调用\u003Ccode>callHook()\u003C\u002Fcode>，触发\u003Ccode>\u002Fapi\u002Fsse\u003C\u002Fcode>中的\u003Ccode>hook()\u003C\u002Fcode>，最终\u003Ccode>push()\u003C\u002Fcode>推送消息到了浏览器。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 data-marked-line=\"36\">\u003Csup class=\"fake-head\" id=\"%E6%A0%B7%E4%BE%8B%E4%BB%A3%E7%A0%81\">\u003C\u002Fsup>\u003Ca class=\"header-link\" href=\"#%E6%A0%B7%E4%BE%8B%E4%BB%A3%E7%A0%81\">样例代码\u003C\u002Fa>\u003C\u002Fh2>\u003Cp data-marked-line=\"37\">&emsp;&emsp;我写了一个简单的示例项目，\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fyunyuyuan\u002Fnuxt-sse-example\" target=\"_blank\">开源在Github\u003C\u002Fa>，项目里不会真的调用ChatGPT，而是用Nuxt另外起了一个SSE接口，每隔一秒返回当前时间：\n\u003Cspan class=\"image-container\">\u003Cimg data-viewer alt=\"\" title=\"\" src=\"https:\u002F\u002Fr2-blog.yunyuyuan.net\u002FxHajXwtMDbRImfQ.gif\"\u002F>\u003Csmall class=\"desc\">\u003C\u002Fsmall>\u003C\u002Fspan>\u003C\u002Fp>\n",[18,22,25,28,31],{"size":19,"text":20,"url":21},"big","SSE(server-sent events)是什么","SSE(server-sent%20events)%E6%98%AF%E4%BB%80%E4%B9%88",{"size":19,"text":23,"url":24},"场景解析","%E5%9C%BA%E6%99%AF%E8%A7%A3%E6%9E%90",{"size":19,"text":26,"url":27},"相关工具","%E7%9B%B8%E5%85%B3%E5%B7%A5%E5%85%B7",{"size":19,"text":29,"url":30},"流程解析","%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90",{"size":19,"text":32,"url":33},"样例代码","%E6%A0%B7%E4%BE%8B%E4%BB%A3%E7%A0%81","SSE(server-sent events)是什么 MDN上有关于SSE的介绍，可以把SSE理解为单向的WebSocket。普通请求 + WebSocket + SSE联手一起完善了浏览器网络请求体系。它们的区别如下： 普通请求(GET,POST,PUT等等)：浏览器--->服务器，单向非持续数据…",[36,47,54,64,74],{"item":37,"count":46},{"title":38,"len":39,"tags":40,"id":42,"time":43,"modifyTime":44,"showComments":13,"encrypt":12,"customSlug":45},"关于本站的重构 (又双叒叕重构了)",1503,[8,9,41],"杂谈",8500,1741528184917,1759579038298,"about-renovate",2,{"item":48,"count":46},{"title":49,"len":50,"tags":51,"id":52,"time":53,"modifyTime":53,"encrypt":12,"showComments":13,"customSlug":14},"vue3用于管理axios异步请求一致性的功能函数",3227,[9,8],9820,1725014139074,{"item":55,"count":63},{"title":56,"len":57,"tags":58,"id":60,"time":61,"modifyTime":62,"showComments":13,"encrypt":12,"customSlug":14},"用React实现跟随鼠标的3D透视效果",863,[8,59],"react",6856,1731479185023,1734350101562,1,{"item":65,"count":63},{"title":66,"len":67,"tags":68,"id":71,"time":72,"modifyTime":73,"encrypt":12,"showComments":13,"customSlug":14},"树莓派监控摄像头制作",1673,[69,70,8],"树莓派","python",1010,1669896775319,1679559691141,{"item":75,"count":63},{"title":76,"len":77,"tags":78,"id":80,"time":81,"modifyTime":82,"encrypt":12,"showComments":13,"customSlug":14},"终极白嫖：使用免费MongoDB显示浏览量",4391,[8,79],"数据库",9812,1668791055520,1683773643807,1793351205605]