打造专业级ChatGPT风格聊天界面:SpringBoot与Vue实现动态打字机效果,附完整前后端源码

大家好,今天用SpringBootvue写了一个仿ChatGPT官网聊天的打字机效果。
所有代码地址:gitee代码地址 ,包含前端和后端,可以直接运行

使用本技术实现的项目:aicnn.cn,欢迎大家体验

如果文章知识点有错误的地方,请指正!大家一起学习,一起进步。

本文主要应用的技术有:SpringBoot、Vue、Reactive、WebFlux、EventSource等,学习和练手的好项目。实现效果如下

在这里插入图片描述

准备好了吗,let’s get it!

采用本文技术实现的前后端项目,点击体验使用:aicnn.cn

文章目录

      • 前言
    • 项目运行
      • SSE技术概览
        • 什么是Server-Sent Events?
        • SSE与WebSockets的区别
        • 为什么选择SSE?
        • SSE的应用场景
      • 在Spring Boot中实现SSE
        • 设置SSE
        • 处理连接和事件
        • 处理异常和断开连接
        • 一些实用的提示
      • Vue前端对SSE的处理
        • 在Vue中接收SSE
        • 保证流畅的用户体验
        • 异常处理和重连
        • 小结
      • 实际案例分析:实时通知系统
        • 场景描述
        • 为什么选择SSE?
        • 实现概览
        • 体验优化
        • 可能的挑战
      • 结语

前言

Web开发的世界永远充满惊喜,不是吗?每当我们认为自己掌握了所有的技巧和工具,总会有新的技术出现,挑战我们的知识库。今天,我们要探讨的这项技术可能对一些人来说并不陌生,但对于其他人来说,则像是新发现的宝藏。没错,我在说的是Server-Sent Events(SSE)。

在这里插入图片描述

你可能会问:“SSE是什么?”简单来说,SSE是一种让服务器实时向客户端发送更新的技术。但别误会,这不是另一个WebSockets。SSE和WebSockets之间的斗争,有点像是电影《星球大战》中的帝国和反抗军的斗争——两者都有其优势和用武之地,但战场完全不同。SSE是为了解决特定类型的实时通讯问题而生,而不是为了取代WebSockets。

在这里插入图片描述

在本文中,我们将一探究竟,看看SSE到底是什么魔法,以及如何在Spring Boot应用程序和VUE中轻松实现它。当然,我们也不会忘记前端的小伙伴们——我们将一起探索如何在Vue应用中接收和处理这些实时数据,并一起实现类似ChatGPT官网的聊天打字机效果。准备好了吗?让我们开始这趟探索之旅吧!


项目运行

所有代码地址:代码地址 ,包含前端和后端,可以直接运行。

springboot安装依赖,并设置对应的sd-key即可:

aicnn后端代码设置

api key 的获取方式如下:

  • 第一步:打开aicnn.cn
  • 第二步:进入设置页面
  • 第三步:点击创建新的秘钥
  • 第四步:复制密钥值,替换上面代码中的sk-*******,替换后的代码如下所示: .header("Authorization", "Bearer sk-1234567890123456789")

aicnn获取key

前端项目采用vue3实现,

在项目中,使用如下命令运行项目,即可运行前端:

yarn install
yarn serve

aicnn界面展示

SSE技术概览

什么是Server-Sent Events?

在深入探索Server-Sent Events(SSE)之前,让我们先搞清楚它到底是什么。简单地说,SSE是一种允许服务器主动向客户端发送信息的技术。与传统的请求-响应模式不同,SSE建立了一个单向通道,使得服务器可以实时发送更新。这听起来有点像WebSockets,但SSE的工作方式和用例却大有不同。

SSE与WebSockets的区别

SSE和WebSockets都是实现实时通信的技术,但它们各有千秋。WebSockets提供了一个全双工的通信渠道,允许数据在客户端和服务器之间双向流动。相比之下,SSE是单向的——仅从服务器到客户端。这意味着如果你需要从客户端向服务器发送数据,SSE可能就不是最佳选择了。SSE的优势在于它的简单性和轻量级,特别适用于那些仅需要服务器单向传送数据的场景,比如实时新闻更新、股票行情等。

在这里插入图片描述

为什么选择SSE?

现在你可能在想:“为什么我要使用SSE而不是其他技术?”好问题!首先,SSE在浏览器中有很好的支持,这使得它非常容易实现。其次,由于SSE是基于HTTP的,它可以利用现有的HTTP协议特性,如缓存、认证等。这些特性在WebSockets中可能需要额外的处理。最后,SSE的轻量级特性使其成为一种高效的实时数据传输方式,尤其是当你只需要服务器到客户端的单向数据流时。

SSE的应用场景

SSE最适合的是那些需要服务器定期或不定期推送信息到客户端的场景。例如,如果你正在开发一个需要显示实时消息、股票行情或任何形式实时数据的应用,SSE是一个不错的选择。它的轻量级和易用性使其成为这些类型应用的理想选择。

在Spring Boot中实现SSE

设置SSE

在Spring Boot中实现SSE并不复杂。其核心在于使用Spring框架的webflux。使用webflux类能够创建一个持久的连接,使服务器能够向客户端发送多个事件,而无需每次都建立新的连接。

先在pom中引入相关依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

然后需要在Controller中创建一个方法来返回Flux实例。这个方法将被映射到特定的URL,客户端将使用这个URL来接收事件。

package cn.aicnn.chatssespringboot.controller;

import cn.aicnn.chatssespringboot.dto.AIAnswerDTO;
import cn.aicnn.chatssespringboot.service.GptServiceImpl;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

import javax.annotation.Resource;


@RestController
public class ChatController {

    //用于流式请求第三方的实现类
    @Resource
    GptServiceImpl gptService;

    //通过stream返回流式数据
    @GetMapping(value = "/completions", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<AIAnswerDTO>> getStream(@RequestParam("messages")String messages) {
        return gptService.doChatGPTStream(messages)//实现类发送消息并获取返回结果
                .map(aiAnswerDTO -> ServerSentEvent.<AIAnswerDTO>builder()//进行结果的封装,再返回给前端
                        .data(aiAnswerDTO)
                        .build()
                )
                .onErrorResume(e -> Flux.empty());//发生异常时发送空对象
    }

}

处理连接和事件

一旦有客户端连接到这个URL,可以通过调用GptServiceImpl实例的doChatGPTStream方法来发送事件。这些事件可以是简单的字符串消息,也可以是更复杂的数据结构,如JSON对象。记住,SSE的设计初衷是轻量级和简单,所以你发送的每个事件都应当是独立的和自包含的。

GptServiceImpl的实现方式如下,也是springboot后端实现的重点

package cn.aicnn.chatssespringboot.service;

import cn.aicnn.chatssespringboot.dto.AIAnswerDTO;
import cn.aicnn.chatssespringboot.dto.ChatRequestDTO;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;

import javax.annotation.PostConstruct;
import java.util.*;


/**
 * @author aicnn.cn
 * @date 2023/2/13
 * @description: aicnn.cn
 **/
@Service
public class GptServiceImpl {
    //webflux的client
    private WebClient webClient;

    //用于读取第三方的返回结果
    private ObjectMapper objectMapper = new ObjectMapper();

    @PostConstruct
    public void postConstruct() {
        this.webClient = WebClient.builder()//创建webflux的client
                .baseUrl("https://api.aicnn.cn/v1")//填写对应的api地址
                .defaultHeader("Content-Type", "application/json")//设置默认请求类型
                .build();
    }

    //请求stream的主题
    public Flux<AIAnswerDTO> doChatGPTStream(String requestQuestion) {

        //构建请求对象
        ChatRequestDTO chatRequestDTO = new ChatRequestDTO();
        chatRequestDTO.setModel("gpt-3.5-turbo");//设置模型
        chatRequestDTO.setStream(true);//设置流式返回

        ChatRequestDTO.ReqMessage message = new ChatRequestDTO.ReqMessage();//设置请求消息,在此可以加入自己的prompt
        message.setRole("user");//用户消息
        message.setContent(requestQuestion);//用户请求内容
        ArrayList<ChatRequestDTO.ReqMessage> messages = new ArrayList<>();
        messages.add(message);
        chatRequestDTO.setMessages(messages);//设置请求消息


        //构建请求json
        String paramJson = JSONUtil.toJsonStr(chatRequestDTO);;

        //使用webClient发送消息
        return this.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(result -> handleWebClientResponse(result));//接收到消息的处理方法
    }

    private Flux<AIAnswerDTO> handleWebClientResponse(String resp) {
        if (StrUtil.equals("[DONE]",resp)){//[DONE]是消息结束标识
            return Flux.empty();
        }

        try {
            JsonNode jsonNode = objectMapper.readTree(resp);
            AIAnswerDTO result = objectMapper.treeToValue(jsonNode, AIAnswerDTO.class);//将获得的结果转成对象
            if (CollUtil.size(result.getChoices())  > 0 && !Objects.isNull(result.getChoices().get(0)) &&
                    !StrUtil.isBlank(result.getChoices().get(0).delta.getError())){//判断是否有异常
                throw new RuntimeException(result.getChoices().get(0).delta.getError());
            }
            return Flux.just(result);//返回获得的结果
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}

在这里,我们请求的api地址设置为https://api.aicnn.cn/v1,并且设置从aicnn.cn获取的api key即可。

处理异常和断开连接

在使用SSE时,处理异常和断开连接也非常重要。确保在客户端断开连接或发生异常时,正确地关闭webflux实例。这有助于避免资源泄露和其他潜在问题。

一些实用的提示
  • 超时管理:SSE连接可能因为超时而被关闭。确保妥善处理这种情况。
  • 错误处理:适当地处理可能发生的异常,如网络问题或客户端断开连接。
  • 资源清理:在连接结束时清理资源,确保应用的健康和性能。

至此,我们就完成了SpringBoot的SSE后端开发。

Vue前端对SSE的处理

在Vue中接收SSE

在Vue应用中接收SSE消息是相对直截了当的。需要做的基本上就是在Vue组件中创建一个新的EventSource实例,并指向你的Spring Boot应用中设置的SSE URL,本文使用EventSource作为示例,也可以选择axios或@microsoft/fetch-event-source发送post请求的SSE请求,使用另外两种的好处是可以控制header,携带token信息,以便于控制权限。

        this.eventSource = new EventSource('http://127.0.0.1:8080/completions?messages='+this.inputText);

一旦建立了连接,就可以定义各种事件监听器来处理从服务器接收到的消息。在Vue中,这通常涉及到更新组件的数据属性,这些属性又通过Vue的响应式系统自动更新UI。

sendSSEMessage() {
      // 只有当eventSource不存在时才创建新的EventSource连接
      if (!this.eventSource) {
        this.messages.push({text: this.inputText, isMine: true});
        this.messages.push({text: "", isMine: false});

        // 创建新的EventSource连接
        this.eventSource = new EventSource('http://127.0.0.1:8080/completions?messages='+this.inputText);

        // 设置消息接收的回调函数
        this.eventSource.onmessage = (event) => {
          const data = JSON.parse(event.data);
          this.messages[this.messages.length - 1].text += data.choices[0].delta.content;
        };

        // 可选:监听错误事件,以便在出现问题时能够重新连接或处理错误
        this.eventSource.onerror = (event) => {
          console.error("EventSource failed:", event);
          this.eventSource.close(); // 关闭出错的连接
          this.eventSource = null; // 重置eventSource变量,允许重建连接
        };
      }
    }
保证流畅的用户体验

当处理实时数据时,保证一个流畅且不中断的用户体验至关重要。在Vue中,这意味着需要确保UI的更新是平滑和高效的。幸运的是,Vue的响应式系统会处理大部分重活,但你仍需要注意不要进行不必要的大规模DOM操作或数据处理。

异常处理和重连

处理连接中断或其他异常也是至关重要的。你可能需要在失去连接时尝试重新连接,或者至少提醒用户当前的连接状态。这可以通过监听EventSource的错误事件并采取适当的行动来实现。

小结

将SSE与Vue结合使用,可以为用户提供一个富有动态性和实时性的界面。无论是实时消息、通知,还是实时数据流,SSE都能让Vue应用更加生动和实用。

当然,除了使用SSE进行文字聊天的打字机模式删除,SSE技术还有应用:


实际案例分析:实时通知系统

场景描述

想象一下,你正在为一个大型在线零售平台工作,该平台需要一种方法来实时通知其用户关于订单状态的更新。在这种情况下,使用SSE来推送通知变得非常有意义。这不仅提高了用户体验,还减少了服务器的负载,因为不需要频繁的轮询。

为什么选择SSE?

在这个场景中,SSE提供了一种高效且轻量的方式来实时更新用户的订单状态。由于订单状态更新不需要来自客户端的即时响应,SSE的单向数据流完全符合需求。相比于使用WebSockets,这种方式更加简单且资源消耗更少。

实现概览

在Spring Boot后端,你可以设置一个SSE端点,以便在订单状态发生变化时发送通知。

在Vue前端,可以创建一个监听SSE消息的组件。当接收到新的订单状态更新时,该组件可以相应地更新UI,为用户提供实时反馈。

体验优化

为了优化用户体验,还可以在前端实现一些附加功能,比如当用户离开页面时暂停事件流,或者在网络不稳定导致连接断开时自动重连。

可能的挑战

当然,实现SSE并非没有挑战。例如,你需要确保在高并发场景下后端能够有效地处理大量的连接。此外,处理网络不稳定情况下的重连机制也是需要考虑的。

结语

回顾我们今天的探索,我们可以看到Server-Sent Events(SSE)在现代web开发中的价值和应用潜力。通过在Spring Boot后端轻松实现SSE,并在Vue前端高效处理这些实时事件,我们可以为用户创造出更加动态和互动的体验,并使用SSE从0开始实现了类似ChatGPT聊天的前后端,以及打字机效果的输出。

SSE提供了一种简单、高效的方法来处理单向数据流。它是那些需要服务器向客户端实时推送数据的应用的理想选择。无论是实时通知、实时新闻更新,还是其他任何需要实时数据的场景,SSE都能够提供一种轻量级且易于实现的解决方案。

当然,每种技术都有其适用场景。SSE并不是万能的,它最适合于那些不需要客户端向服务器发送数据的场景。在选择技术方案时,了解你的需求和各种技术的优势及局限性至关重要。

最后,鼓励所有的开发者尝试将SSE集成到自己的项目中。感谢你和我一起探索SSE的奇妙世界。现在,该是你动手实践的时候了!祝你编码愉快!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/354102.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Visio绘图文件阅读器 --- VSD Viewer

VSD Viewer是一款轻量级、直观易用的软件&#xff0c;专门设计用于查看和打印Microsoft Visio绘图文件。它支持多种Visio文件格式&#xff0c;如VSD、VSDX等&#xff0c;并能够快速加载Visio绘图文件&#xff0c;无需等待太长时间。VSD Viewer还提供高质量的打印功能&#xff0…

【目标跟踪】多相机环视跟踪

文章目录 一、前言二、流程图三、实现原理3.1、初始化3.2、输入3.3、初始航迹3.4、航迹预测3.5、航迹匹配3.6、输出结果 四、c 代码五、总结 一、前言 多相机目标跟踪主要是为了实现 360 度跟踪。单相机检测存在左右后的盲区视野。在智能驾驶领域&#xff0c;要想靠相机实现无…

Ps:根据 HSB 调色(以可选颜色命令为例)

在数字色彩中&#xff0c;RGB 和 HSV&#xff08;又称 HSB&#xff09;是两种常用的颜色表示方式&#xff08;颜色模型&#xff09;。 在 RGB 颜色模式下&#xff0c;Photoshop 的红&#xff08;Red&#xff09;、绿&#xff08;Green&#xff09;、蓝&#xff08;Blue&#xf…

空气质量预测 | Python实现基于线性回归、Lasso回归、岭回归、决策树回归的空气质量预测模型

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 政府机构使用空气质量指数 (AQI) 向公众传达当前空气污染程度或预测空气污染程度。 随着 AQI 的上升,公共卫生风险也会增加。 不同国家有自己的空气质量指数,对应不同国家的空气质量标准。 对于空气质量预测,…

SpringBoot将第三方的jar中的bean对象自动注入到ioc容器中

新建一个模块&#xff0c;做自动配置 config&#xff1a;需要准备两个类&#xff0c;一个自动配置类&#xff0c;一个配置类 CommonAutoConfig&#xff1a;此类用于做自动配置类它会去读取resoutces下的META-INF.spring下的org.springframework.boot.autoconfigure.AutoConfig…

EDEM仿真导入stl文件慢的原因

我在将solidworks导出为STL文件后&#xff0c;再导入EDEM软件进行离散元仿真时&#xff0c;导入速度特别慢&#xff0c;发现是因为stl文件分辨率设置的过于精细&#xff0c;将文件分辨率设置为粗糙后&#xff0c;导入速度就快了。

SSH客户端 Termius for Mac 中文激活版

Termius for Mac是一款强大的终端和SSH客户端&#xff0c;为开发人员、系统管理员和网络工程师提供了全面的远程访问和管理工具。 软件下载&#xff1a;Termius for Mac 中文激活版下载 无论您是在使用Mac、Windows还是Linux系统&#xff0c;Termius都能提供出色的功能和用户体…

一、计算机组成与体系结构【7分】

一、课程内容 二、分值 三、数据表示 1、进制转换 其他进制转换为十进制 十进制转换为其他进制 二进制转换为八进制与十六进制 2、码制 这一块目前不是很懂 3、浮点数表示 4、算数与逻辑运算 四、校验码 1、奇偶校验码&#xff08;只能检测出奇位的错误&#xff0c;…

mac上搭建hbase伪集群

1. 前言 之前我们已经搭建过了 hbase单点环境&#xff0c;(单机版搭建参见&#xff1a; https://blog.csdn.net/a15835774652/article/details/135569456) 但是 为了模拟一把集群环境 我们还是尝试搭建一个伪集群版 2. 环境准备 jdk环境 1.8hdfs &#xff08;hadoop环境 可选…

如何保证缓存与数据库双写时的数据一致性?

背景&#xff1a;使用到缓存&#xff0c;无论是本地内存做缓存还是使用 Redis 做缓存&#xff0c;那么就会存在数据同步的问题&#xff0c;因为配置信息缓存在内存中&#xff0c;而内存时无法感知到数据在数据库的修改。这样就会造成数据库中的数据与缓存中数据不一致的问题。 …

使用py-spy对python程序进行性能诊断学习

py-spy简介 py-spy是一个用Rust编写的轻量级Python分析工具&#xff0c;它能够监视正在运行的Python程序&#xff0c;而不需要修改代码或者重新启动程序。Py-spy可以在不影响程序运行的情况下&#xff0c;采集程序运行时的信息&#xff0c;生成火焰图&#xff08;flame graph&…

MYSQL表的约束详解!

文章目录 前言一、空属性二、默认值三、列描述四、zerofill五、主键六、自增长七、唯一键八、外键 前言 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数据的合法性&#xff0c;从业务逻辑角度保证数据…

【CANoe使用大全】——Logging窗口

&#x1f64b;‍♂️【CANoe使用大全】系列&#x1f481;‍♂️点击跳转 文章目录 1.概述2.Logging窗口打开方式3.创建Logging4.配置4.1. 命名4.2.格式选择4.3. 路径选择与命名4.3.1.Logging文件命名_自定义4.3.2.Logging文件命名_系统内选择 5.Logging触发方式5.1 Logging模块…

QT实现USB摄像头接入显示

一、UVC协议简介 UVC全称是USB Video Class&#xff08;USB视频类&#xff09;&#xff0c;是一种标准化的USB视频设备通信协议&#xff0c;它定义了摄像头与主机之间的数据传输协议和格式。 UVC协议的出现&#xff0c;解决了摄像头厂商之间互不兼容&#xff0c;以及摄像头应…

win10通过ssh链接deepin23并开启x11转发

前提 主机环境&#xff1a;win10 lstc 虚拟机环境&#xff1a;deepin23beta2 终端&#xff1a;tabby x11服务器: vcxsrv 安装ssh sudo apt install ssh开启root登录(看你需求&#xff09; 首先你要给root账号设置密码 sudo passwd root修改配置文件 sudo vim /etc/ssh/ss…

MongoDB本地部署并结合内网穿透实现公网访问本地数据库

文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 4. 结语 前言 MongoDB是一个基于分布式文件…

Linux-ROS学习之旅-话题编程(二)

##承接上一篇文章的知识&#xff0c;有下面的实例操作 通过代码新生一个海龟&#xff0c;放置在(5,5)点&#xff0c;命名为turtle2&#xff0c;通过代码订阅turtle2的实时位置并打印在终端&#xff0c;控制turtle2实现旋转运动 步骤&#xff1a; 1.创建一个工作空间和一个功…

【GitHub项目推荐--30 天学会XXX(HTML/React/Python/JavaScript)】【转载】

30 天学会 React 这个项目是《30 天 React 挑战》&#xff0c;是在 30 天内学习 React 的分步指南。它需要你学习 React 之前具备 HTML、CSS 和 JavaScript 知识储备。 除了 30 天学会 React&#xff0c;开发者还发布过 30 天学会 JavaScript 等项目。 开源地址&#xff1a;…

MySQL数据操纵语言DML

MySQL数据操纵语言DML(SELECT,UPDATE,INSERT INTO,DELETE) 目录 MySQL数据操纵语言DML(SELECT,UPDATE,INSERT INTO,DELETE)DML关键字数据操纵语言DML1.查表2.插入数据3.更新数据4.删除数据 DML关键字 DML关键字含义SELECT从数据库中检索数据INSERT INTO向数据库表中插入新的行…

什么是协方差矩阵?

协方差矩阵&#xff08;Covariance Matrix&#xff09;是一个用于衡量多个变量之间相互关系的工具&#xff0c;在统计学和数据分析领域中非常重要。这个矩阵展现了每一对变量之间的协方差。协方差是衡量两个变量如何一起变化的度量&#xff1b;如果两个变量的协方差是正的&…