全双工通信协议WebSocket——使用WebSocket实现智能学习助手/聊天室功能

一.什么是WebSocket?

WebSocket是基于TCP的一种新的网络协议。它实现了浏览器与服务器的全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输

HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 AJAX 请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

http协议:

07e80c5383974729ba528bceef04debb.jpeg

 

websocket协议:

aec402415caf4ee68e8b89bad2e044c8.jpeg

二.HTTP协议和WebSocket协议对比:

  • HTTP是短连接
  • WebSocket是长连接
  • HTTP通信是单向的,基于请求响应模式 WebSocket支持双向通信
  • HTTP和WebSocket底层都是TCP连接

三.基于WebSocket的智能学习助手功能实现

1.需求

通过 websocket 实现一个简易的聊天室功能 

        当点击智能学习助手选项,会进入一个聊天室,小助手自动向用户问好,用户可以向小助手提问问题,小助手后端查询到问题答案后会进行回复

bfa15c4c381e4e42829925803765104d.png

2.前端

前端环境:vue3+element-plus+pinia

因为我的这个项目用户端和管理端共用该功能,所以URL上带上了从pinia中获取的当前登录用户的信息,用于与后端建立唯一的连接标识

下面的代码是只对于学习小助手组件的.vue文件,读者有需自行扩展

<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";import useUserInfoStore from "@/stores/userInfo.js";
const userInfoStore = useUserInfoStore();
const userInfo = ref({ ...userInfoStore.info });// 发送的信息
const say = ref("");
// 内容
const content = ref("");
// 管理员1,普通用户0
const role = userInfo.value.role == "管理员" ? 1 : 0;
// 发送给后端的URL
const url = ref("ws://localhost:8080/char/" + role);var websocket = null;
//判断当前浏览器是否支持WebSocket
if ("WebSocket" in window) {//连接WebSocket节点websocket = new WebSocket(url.value);
} else {ElMessage.error("Not support websocket");
}//连接发生错误的回调方法
websocket.onerror = function () {ElMessage.error("连接错误");
};//连接成功建立的回调方法
websocket.onopen = function () {ElMessage.success("连接成功");createContent(false, "你好,我是智能学习小助手~");
};//接收到消息的回调方法
websocket.onmessage = function (event) {createContent(false, event.data);
};//连接关闭的回调方法
websocket.onclose = function () {ElMessage.success("连接关闭");
};//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {websocket.close();
};//发送消息
function send() {websocket.send(say.value);createContent(true, say.value);say.value = "";
}//关闭连接
function closeWebSocket() {websocket.close();
}// 构造消息框
const createContent = (isMyMsg, msg) => {let html;// 当前用户消息if (isMyMsg) {html ='<div class="el-row" style="padding: 5px 0">\n' +'  <div class="el-col el-col-22" style="text-align: right; padding-right: 10px">\n' +'    <div style="color: white;text-align: center;border-radius: 10px;font-family: sans-serif;padding: 10px;width:auto;display:inline-block !important;display:inline;background-color: deepskyblue;">' +msg +"</div>\n" +"  </div>\n" +'  <div class="el-col el-col-2">\n' +'  <span class="el-avatar el-avatar--circle" style="height: 40px; width: 40px; line-height: 40px;">\n' +'    <img src="' +userInfo.value.userUrl +'" style="object-fit: cover;">\n' +"  </span>\n" +"  </div>\n" +"</div>";} else {// 助手信息html ='<div class="el-row" style="padding: 5px 0">\n' +'  <div class="el-col el-col-2" style="text-align: right">\n' +'  <span class="el-avatar el-avatar--circle" style="height: 40px; width: 40px; line-height: 40px;">\n' +'    <img src="' +"https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" +'" style="object-fit: cover;">\n' +"  </span>\n" +"  </div>\n" +'  <div class="el-col el-col-22" style="text-align: left; padding-left: 10px">\n' +'    <div style="color: white;text-align: center;border-radius: 10px;font-family: sans-serif;padding: 10px;width:auto;display:inline-block !important;display:inline;background-color: deepskyblue;">' +msg +"</div>\n" +"  </div>\n" +"</div>";}content.value += html;
};
</script>
<template><el-card class="page-container"><template #header><div class="block text-center"><el-row :gutter="20"><el-col :span="6" :offset="11"><span>智能学习小助手</span></el-col></el-row><!-- 分割线 --><el-divider /><!-- 走马灯 --><el-carousel height="150px"><el-carousel-item v-for="item in 4" :key="item"><h3 class="small justify-center" text="2xl">韩磊大帅哥{{ item }}</h3></el-carousel-item></el-carousel></div></template><!-- 滚动条 --><el-scrollbar height="460px"><div v-html="content"></div></el-scrollbar><template #footer><el-row><el-inputstyle="width: 100%":autosize="{ minRows: 4, maxRows: 8 }"type="textarea"v-model="say"placeholder="有困难就找汪汪队~"/></el-row><el-row style="margin-top: 10px"><el-col :span="2" :offset="20"><div class="grid-content ep-bg-purple" /><el-button type="primary" @click="send()">发送</el-button></el-col><el-col :span="1"><div class="grid-content ep-bg-purple" /><el-button type="primary" @click="say = ''">清空文本</el-button></el-col></el-row></template></el-card>
</template>
<style lang="scss" scoped>
.page-container {min-height: 100%;box-sizing: border-box;.header {display: flex;align-items: center;justify-content: space-between;}
}
.el-carousel__item h3 {color: #475669;opacity: 0.75;line-height: 150px;margin: 0;text-align: center;
}
.img {width: 100%;height: 460px;
}
.el-carousel__item:nth-child(2n) {background-color: #99a9bf;
}.el-carousel__item:nth-child(2n + 1) {background-color: #d3dce6;
}
.tip {color: white;text-align: center;border-radius: 10px;font-family: sans-serif;padding: 10px;width: auto;display: inline-block !important;display: inline;
}
.right {background-color: deepskyblue;
}
.left {background-color: forestgreen;
}
</style>

3.后端

后端环境:springboot+lombok+web+mybatisplus+websocket

3.1.Springboot 添加Pom依赖

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

3.2.添加Websocket配置


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** WebSocket配置类,用于注册WebSocket的Bean*/
@Configuration
public class WebSocketConfiguration {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}

3.3.构建数据库

3.3.1.配置并连接数据库并创建问题表(自行完成)

3.3.2.pojo类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("lw_answers")
public class Answer {@TableId(type = IdType.AUTO)private Long answerId; // 问题IDprivate String issue; // 问题private String answer; // 回答
}

3.3.3.注册Mapper接口

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hl.pojo.entity.Answer;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface AnswerMapper extends BaseMapper<Answer> {
}

3.3.4.注册WebSocketServer接口

因为我们每个建立会话的对象要唯一!所以对于不同的用户我们根据用户角色和用户id来建立唯一标识

package com.hl.server.websocket;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hl.common.constant.MessageConstant;
import com.hl.common.context.BaseContext;
import com.hl.common.enumeration.UserRole;
import com.hl.pojo.entity.Answer;
import com.hl.server.mapper.AnswerMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** WebSocket服务*/
@Slf4j
@Component
@ServerEndpoint("/char/{role}")
public class WebSocketServer {/*** WebSocket API 是独立于任何特定框架的标准,* 它的生命周期和管理是由 WebSocket 容器负责的,而不是由 Spring 容器负责。* 因此,WebSocket 容器不会识别或处理 Spring 的依赖注入注解。*/private static AnswerMapper answerMapper;@Autowiredprivate void setAnswerMapper(AnswerMapper answerMapper) {WebSocketServer.answerMapper = answerMapper;}//存放会话对象private static Map<String, Session> sessionMap = new ConcurrentHashMap<>();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session,@PathParam("role") Integer role) {String key=getUserInfo(role);log.info("WebSocketServer onOpen role:{}",key);sessionMap.put(key, session);}/*** 收到客户端消息后调用的方法** @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message,@PathParam("role") Integer role) {String key=getUserInfo(role);log.info("WebSocketServer onMessage user:{} message:{}",key,message);LambdaQueryWrapper<Answer> queryWrapper=new LambdaQueryWrapper<Answer>().like(Answer::getIssue,message);Answer ans = answerMapper.selectOne(queryWrapper);String answer = ans==null? MessageConstant.BU_ZHIDAO:ans.getAnswer();sessionMap.get(key).getAsyncRemote().sendText(answer);}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(@PathParam("role") Integer role) {String key=getUserInfo(role);log.info("WebSocketServer onClose:{}",key);sessionMap.remove(key);}/*** 获取唯一标识key*/public String getUserInfo(Integer role) {String key="";// 根据LocalThread获取当前登录用户idLong id= BaseContext.getCurrentId();if(role== UserRole.COMMON.getValue()){//普通用户key=UserRole.COMMON.getDesc()+id;}else{//管理员key=UserRole.ADMIN.getDesc()+id;}return key;}}

注: WebSocket API 是独立于任何特定框架的标准, 它的生命周期和管理是由 WebSocket 容器负责的,而不是由 Spring 容器负责。 因此,WebSocket 容器不会识别或处理 Spring 的依赖注入注解,所以我们不能使用字段注入!

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

音乐网站新篇章:SpringBoot Web实现

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

【初阶数据结构篇】链式结构二叉树(二叉链)的实现(感受递归暴力美学)

文章目录 须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…

el-talble selection行 初始默认勾选

导言 el-talble selection 行&#xff08;选择列&#xff09;用于显示复选框&#xff0c;让用户可以选择或取消选择某些表格行&#xff0c;常用于批量操作场景。 刚刚试了下&#xff0c;想加深印象记录一下当学习碎片。参考的是表格多选并根据每行值初始化选中状态&#xff08;…

RabbitMQ交换机类型

RabbitMQ交换机类型 1、RabbitMQ工作模型2、RabbitMQ交换机类型2.1、Fanout Exchange&#xff08;扇形&#xff09;2.1.1、介绍2.1.2、示例2.1.2.1、生产者2.1.2.2、消费者2.1.2.3、测试 2.2、Direct Exchange&#xff08;直连&#xff09;2.2.1、介绍2.2.2、示例2.2.2.1、生产…

数据结构---排序(上)

一.直接插入排序 思想&#xff1a;将一个个未排序的数字插入到已经排好顺序的数组中。 例如&#xff1a; 思路&#xff1a;先将前两个数字排序&#xff0c;然后将后面数字与前面数字比较排序。 操作&#xff1a; 1.引入变量 i 遍历数组[1&#xff0c;array.lenth] 2.用临时…

ai翻唱部分步骤

模型部署 我是用的RVC进行的训练&#xff0c;也可以使用so-vits-svc。 通过百度网盘分享的文件&#xff1a;RVC-beta 链接&#xff1a;https://pan.baidu.com/s/1c99jR2fLChoqUFqf9gLUzg 提取码&#xff1a;4090 以Nvida显卡为例&#xff0c;分别下载“RVC1006Nvidia”和…

C++的stack和Queue

1.简单实现stack 构建一个模板&#xff0c;俩个参数&#xff0c;这里第一个一般是数据的类型&#xff0c;第二个是由什么来实现栈&#xff0c;在主函数里传了int和vector<int>&#xff0c;第二个不传参也可以&#xff0c;因为是缺省参数&#xff0c;默认为vector&#x…

默认路由:实现内网所有网段流量走一条默认路由访问外网

默认路由 Tip&#xff1a;默认路由一般指出口网关设备的出口路由。实现所有网段流量都走一条路由。 实验模拟&#xff1a;公司内部pc 通过出口网关 访问运营商内部 baidu服务 isp网关配置&#xff1a; <Huawei>sy Enter system view, return user view with CtrlZ. …

蘑菇书(EasyRL)学习笔记(2)

1、序列决策 1.1、智能体和环境 如下图所示&#xff0c;序列决策过程是智能体与环境之间的交互&#xff0c;智能体通过动作影响环境&#xff0c;环境则返回观测和奖励。智能体的目标是从这些反馈中学习出能最大化长期奖励的策略&#xff0c;这一过程通过不断试错和调整实现强化…

【C语言刷力扣】28.找出字符串中第一个匹配项的下标

题目&#xff1a; 解题思路&#xff1a; 暴力算法 int strStr(char* haystack, char* needle) {int n strlen(haystack), m strlen(needle);for (int i 0; i m < n; i) {bool res true;for (int j 0; j < m; j) {if (haystack[ji] ! needle[j]) {res false;break…

电脑没有下载声卡驱动怎么办?电脑声卡驱动安装方法

在日常使用电脑的过程中&#xff0c;我们可能会遇到电脑没有声音的问题&#xff0c;这往往与声卡驱动缺失或损坏有关。声卡驱动是连接电脑硬件&#xff08;声卡&#xff09;与操作系统之间的桥梁&#xff0c;确保音频信号能够正常输入输出。那么&#xff0c;当电脑没有声卡驱动…

favicon是什么文件?如何制作网站ico图标?

一般我们做网站的话&#xff0c;都会制作一个独特的ico图标&#xff0c;命名为favicon.ico。这个ico图标一般会出现在浏览器网页标题前面。如下图红色箭头所示&#xff1a; 部分博客导航大全也会用到所收录网站的ico图标。比如boke123导航新收录的网站就不再使用网站首页缩略图…

路由策略与路由控制

1. 路由控制概述 2. 路由控制工具 2.1 路由匹配工具 访问控制列表&#xff08;Access Control List, ACL&#xff09;是一个匹配工具。 由若干条 permit / deny 组成的ACL规则组成。 ACL 匹配原则&#xff1a; 一旦命中即停止匹配 ACL在做路由匹配时&#xff0c;更多是匹配…

天生倔强脸的白纸新人,徐畅演艺生涯初舞台获得肯定!

国内首档“微短剧综艺”创新真人秀《开播&#xff01;短剧季》已播出四期&#xff0c;节目集结20余位青年演员竞演角逐&#xff0c;进行经典IP的“二度创作”&#xff0c;最终实现短剧IP孵化。在最新一期正片中&#xff0c;新人演员徐畅凭借一段《离婚律师》的试镜表演&#xf…

Spring Boot解决 406 错误之返回对象缺少Getter/Setter方法引发的问题

目录 前言1. 问题背景2. 问题分析2.1 检查返回对象 3. 解决方案3.1 确保Controller返回Result类型3.2 测试接口响应 4. 原理探讨5. 常见问题排查与优化建议结语 前言 在Spring Boot开发中&#xff0c;接口请求返回数据是系统交互的重要环节&#xff0c;尤其在开发RESTful风格的…

第二十八天|贪心算法|122.买卖股票的最佳时机II,55. 跳跃游戏,45.跳跃游戏II,1005.K次取反后最大化的数组和

目录 122.买卖股票的最佳时机II 方法1&#xff1a;贪心算法&#xff08;简单&#xff09; 方法2&#xff1a;动态规划 55. 跳跃游戏 45.跳跃游戏II 方法1 方法2&#xff08;简洁版&#xff09; 1005.K次取反后最大化的数组和 按照绝对值大小从大到小排序一次 两次排序…

PureMVC在Unity中的使用(含下载链接)

前言 Pure MVC是在基于模型、视图和控制器MVC模式建立的一个轻量级的应用框架&#xff0c;这种开源框架是免费的&#xff0c;它最初是执行的ActionScript 3语言使用的Adobe Flex、Flash和AIR&#xff0c;已经移植到几乎所有主要的发展平台&#xff0c;支持两个版本框架&#xf…

Python CGI编程-cookie的设置、检索

设置检索 其他&#xff1a; 1. http.cookies和http.cookiejar区别&#xff1a; http.cookies主要用于创建和操作单个cookie对象&#xff0c;适用于需要精细控制单个cookie属性的场景。http.cookiejar则用于管理多个cookie&#xff0c;适用于需要自动处理多个请求和响应中的coo…

算法实现 - 快速排序(Quick Sort) - 理解版

文章目录 算法介绍算法分析核心思想三个版本运行过程挖坑法Hoare 原版前后指针法 算法稳定性和复杂度稳定性时间复杂度平均情况O(nlogn)最差情况O( n 2 n^2 n2) 空间复杂度 算法介绍 快速排序是一种高效的排序算法&#xff0c;由英国计算机科学家C. A. R. Hoare在1960年提出&a…

探索Python新境界:Buzhug库的神秘面纱

文章目录 探索Python新境界&#xff1a;Buzhug库的神秘面纱第一部分&#xff1a;背景介绍第二部分&#xff1a;Buzhug库是什么&#xff1f;第三部分&#xff1a;如何安装Buzhug库&#xff1f;第四部分&#xff1a;Buzhug库函数使用方法第五部分&#xff1a;Buzhug库使用场景第六…