在做这个功能之前,本人也是走了很多弯路(花了好几天才搞好),你能看到本篇博文,那你就是找对地方了。百度上很多都是使用SseEmitter这种方式,这种方式使用的是websocket,使用这种方式就搞复杂了,会为后面项目分布式布署上埋下坑,什么坑,下面会说明。要实现【VUE3和SpringBoot实现ChatGPT页面打字效果SSE流式数据展示】这种效果,其实就是要使用SSE这种协议,这种协议很简单。我们先明白websocket和sse有什么区别,说几个主要的区别(太细的自己百度,这不是本博文的重点)
1、SSE是一种基于HTTP的单向通信机制,用于服务器向客户端推送数据,是单向通信
2、WebSocket是一种全双工的通信协议,它通过在客户端和服务器之间建立持久连接,实现双向通信
3、使用SSE在SpringBoot端,就像平时写接口一样,不需要啥配置,不需要保存用户的连接Session,WebSocket在SpringBoot端配置太多,而且需要保存用户的连接session,保存连接seesion在分布式布署上就比较麻烦。
博文最后会有整个项目源代码下载地址,下载之后里面有两个文件夹,myserver是后端springboot的,myweb是vue3前端页面的,如下截图
1、在使用之前,我们还是安装一个大模型,我们这里使用的是LmStudio工具,这里面可以下载阿里开源的通义千问的语言模型,而且是在windows操作,简单;
2、下载地址:https://releases.lmstudio.ai/windows/0.2.17/latest/LM-Studio-0.2.17-Setup.exe
3、安装之后,就是下载大模型(下载需要魔法上网),选择Qwen 1.5
4、启动大模型,按箭头顺序,大模型启动后端口是1234
用idea打开myserver项目,直接运行ServerApplication这个类,关键代码如下:
//构建请求对象 ChatRequest chatRequest = new ChatRequest(); chatRequest.setModel("qwen:latest");//设置模型 chatRequest.setStream(true);//设置流式返回 ReqMessage reqMessage = new ReqMessage();//设置请求消息,在此可以加入自己的prompt reqMessage.setRole("user");//用户消息 reqMessage.setContent(message);//用户请求内容 ArrayListmessageList = new ArrayList<>(); messageList.add(reqMessage); chatRequest.setMessages(messageList);//设置请求消息 //构建请求json String paramJson = JSONUtil.toJsonStr(chatRequest);; Flux response = webClient.post() .uri("/chat/completions")//请求uri .header("Authorization", "Bearer sk-**************")//设置成自己的key,获得key的方式可以在下文查看 .header(HttpHeaders.ACCEPT, MediaType.TEXT_EVENT_STREAM_VALUE)//设置流式响应 .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(paramJson)) .retrieve() .bodyToFlux(String.class) .flatMap(res->{ if (StrUtil.equals("[DONE]",res)){//[DONE]是消息结束标识 return Flux.empty(); } ObjectMapper objectMapper = new ObjectMapper(); try { //System.out.println(res); JsonNode jsonNode = objectMapper.readTree(res); Answer aiAnswer = objectMapper.treeToValue(jsonNode, Answer.class); List choicesList = aiAnswer.getChoices(); Choices choices = choicesList.get(0); Delta delta = choices.getDelta(); String json = objectMapper.writeValueAsString(delta); System.out.println(json); return Flux.just(json); } catch (JsonProcessingException e) { e.printStackTrace(); } return Flux.empty(); });
1、打开myweb项目,先cmd命令,进入到当前目录执行如下命令
npm install
2、运行项目,执行如下命令
npm run dev
3、前端页面关键代码
const abortController = new AbortController(); eventSource.value = fetchEventSource('http://localhost:8080/ck/chat?message='+sendMsg.value, { method: "GET", signal: abortController.signal, openWhenHidden: true, onmessage(event) { let res = event.data; console.log(res) if(res!='[DONE]'&&res!=null){ let data = JSON.parse(event.data); let content = data.content console.log('content=='+content) if(content!=null&&content.indexOf('\n')!=-1){ let text = tableRightData.value[tableRightData.value.length - 1].content + content content = DOMPurify.sanitize(marked.parse(text)) tableRightData.value[tableRightData.value.length - 1].content = content tableRightData.value.push({"role": "assistant", "content": '', "showPhoto": false, "error": null}); }else if(content!=null&&content!=''){ tableRightData.value[tableRightData.value.length - 1].content += content } } nextTick(()=>{ //滚动条置最下面 const container = rightContainerRef.value container.scrollTop = container.scrollHeight }) }, onclose() { console.log('结束了***************************') deleteBlankRow() // eventSource.close(); eventSource.value = null; // 重置eventSource变量,允许重建连接 console.log('结束了2*****************') abortController.abort(); }, onerror(event){ console.log("EventSource failed:", event); abortController.abort(); eventSource.value.close(); // 关闭出错的连接 eventSource.value = null; // 重置eventSource变量,允许重建连接 } } );
四、源代码下载:
下载地址:百度网盘 请输入提取码