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

news2024/11/8 19:46:45

一.什么是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-input
          style="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 {

    @Bean
    public 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; // 问题ID
    private 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;
    @Autowired
    private void setAnswerMapper(AnswerMapper answerMapper) {
        WebSocketServer.answerMapper = answerMapper;
    }


    //存放会话对象
    private static Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session,@PathParam("role") Integer role) {
        String key=getUserInfo(role);
        log.info("WebSocketServer onOpen role:{}",key);
        sessionMap.put(key, session);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public 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);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public 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获取当前登录用户id
        Long 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.coloradmin.cn/o/2236045.html

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

相关文章

【Linux】冯诺依曼体系、再谈操作系统

目录 一、冯诺依曼体系结构&#xff1a; 1、产生&#xff1a; 2、介绍&#xff1a; 二、再谈操作系统&#xff1a; 1、为什么要管理软硬件资源&#xff1a; 2、操作系统如何进行管理&#xff1a; 3、库函数&#xff1a; 4、学习操作系统的意义&#xff1a; 一、冯诺依曼…

Linux的目录结构 | 命令的认识 | 相对路径 | 绝对路径 | 常用命令(一)

文章目录 1.Linux的目录结构2.命令的认识3.相对路径和绝对路径4.常用命令&#xff08;目录文件操作&#xff09;5.常用命令&#xff08;文本查看&#xff09; 1.Linux的目录结构 \ &#xff1a;根目录 root&#xff1a;root用户的工作目录 home&#xff1a;普通用户的工作目录 …

linux nvidia/cuda安装

1.查看显卡型号 lspci |grep -i vga2.nvidia安装 2.1在线安装 终端输入&#xff08;当显卡插上之后&#xff0c;系统会有推荐的安装版本&#xff09; ubuntu-drivers devices可得到如下内容 vendor : NVIDIA Corporation model : TU104GL [Tesla T4] driver : nvid…

简单又便宜的实现电脑远程开机唤醒方法

现有的远程开机方案 1&#xff09;使用向日葵开机棒 缺点是比较贵一点&#xff0c;开机棒要一百多&#xff0c;而且查了评论发现挺多差评说不稳定&#xff0c;会有断联和无法唤醒的情况&#xff0c;而且设置也麻烦&#xff0c;还需要网卡支持WOL 2&#xff09;使用远程开机卡 …

容器架构-Docker的成长之路

目录 1. 什么是容器 2. 容器 vs 虚拟机 3. Docker极速上手指南 环境准备 3.1 配置docker源 3.2 下载镜像加速的配置 3.3 下载自动补全工具 4. Docker C/S架构 5. Docker的镜像管理 5.1 下载nginx:alpine镜像并查看 5.2 sl大法 5.3 删除镜像 5.4 镜像清理用的命令 5…

【开源社区】ELK 磁盘异常占用解决及优化实践

1、问题及场景描述 本文主要讨论在 CentOS环境下基于 rpm 包部署 ELK 系统磁盘异常占用的问题解析和解决方案。 生产问题描述&#xff1a;以下问题现实场景基于ELK体系下&#xff0c;ES服务的磁盘占用问题解析。默认情况下&#xff0c;基于 RPM 安装的 Elasticsearch 服务的安…

仪表板展示|DataEase看中国:历年双十一电商销售数据分析

背景介绍 2024年“双十一”购物季正在火热进行中。自2009年首次推出至今&#xff0c;“双十一”已经成为中国乃至全球最大的购物狂欢节&#xff0c;并且延伸到了全球范围内的电子商务平台。随着人们消费水平的提升以及电子商务的普及&#xff0c;线上销售模式也逐渐呈现多元化…

【深度学习】论文笔记:空间变换网络(Spatial Transformer Networks)

博主简介&#xff1a;努力学习的22级计算机科学与技术本科生一枚&#x1f338;博主主页&#xff1a; Yaoyao2024往期回顾&#xff1a; 【机器学习】有监督学习由浅入深讲解分类算法Fisher算法讲解每日一言&#x1f33c;: 今天不想跑&#xff0c;所以才去跑&#xff0c;这才是长…

基于java+SpringBoot+Vue的旅游管理系统设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven mysql5.7或8.0等等组成&#x…

【HarmonyOS】not supported when useNormalizedOHMUrl is not true.

【HarmonyOS】 not supported when useNormalizedOHMUrl is not true. 问题背景&#xff1a; 集成三方库编译时&#xff0c;IDE提示报错信息如下&#xff1a; hvigor ERROR: Bytecode HARs: [cashier_alipay/cashiersdk] not supported when useNormalizedOHMUrl is not true…

如何对LabVIEW软件进行性能评估?

对LabVIEW软件进行性能评估&#xff0c;可以从以下几个方面着手&#xff0c;通过定量与定性分析&#xff0c;全面了解软件在实际应用中的表现。这些评估方法适用于确保LabVIEW程序的运行效率、稳定性和可维护性。 一、响应时间和执行效率 时间戳测量&#xff1a;使用LabVIEW的时…

gitlab项目如何修改主分支main为master,以及可能遇到的问题

如果你希望将 Git 仓库的主分支名称从 main 修改为 master&#xff1a; 1. 本地修改分支名称 首先&#xff0c;切换到 main 分支&#xff1a; git checkout main将 main 分支重命名为 master&#xff1a; git branch -m main master2. 更新远程仓库 将本地更改推送到远程仓库…

(六千字心得笔记)零基础C语言入门第八课——函数(上)

文章目录 一、函数的概念1.1 函数的概念 二、库函数2.1 标准库和头文件2.2 库函数的使用方法sqrt函数&#xff08;举例&#xff09;功能包含的头文件实践2.2.4 库函数文档的一般格式 三、自定义函数3.1 自定义函数的语法形式3.2 函数的举例 四、形参和实参4.1 实参4.1 形参4.3 …

案例精选 | 河北省某检察院安全运营中异构日志数据融合的实践探索

河北省某检察院是当地重要的法律监督机构&#xff0c;肩负着维护法律尊严和社会公平正义的重要职责。该机构依法独立行使检察权&#xff0c;负责对犯罪行为提起公诉&#xff0c;并监督整个诉讼过程&#xff0c;同时积极参与社会治理&#xff0c;保护公民权益&#xff0c;推动法…

DBAPI连接阿里云 maxcompute 报错

使用正确的驱动包 访问以下链接寻找驱动包 https://github.com/aliyun/aliyun-odps-jdbc/releases/tag/v3.4.3 注意要使用odps-jdbc-3.4.3-jar-with-dependencies.jar &#xff0c;这个是完整的jar包 不要使用odps-jdbc-3.4.3.jar&#xff0c;这个不是完整的&#xff0c;它还…

【MongoDB】MongoDB的Java API及Spring集成(Spring Data)

文章目录 Java APISpring 集成1. 添加依赖2. 配置 MongoDB3. 创建实体类4. 创建 Repository 接口5. 创建 Service 类6. 创建 Controller 类7. 启动 Spring Boot 应用8. 测试你的 API 更多相关内容可查看 Java API maven <dependency><groupId>org.mongodb</gr…

2-Ubuntu/Windows系统启动盘制作

学习目标&#xff1a; 掌握使用Win32DiskImager、Rufus等工具制作系统启动盘的基本步骤。独立将ISO镜像文件写入USB闪存驱动器&#xff0c;确保在需要时顺利安装或修复系统。通过学习如何选择正确的源文件和目标驱动器&#xff0c;理解启动盘的使用场景和注意事项&#xff0c;…

CSS的三个重点

目录 1.盒模型 (Box Model)2.位置 (position)3.布局 (Layout)4.低代码中的这些概念 在学习CSS时&#xff0c;有三个概念需要重点理解&#xff0c;分别是盒模型、定位、布局 1.盒模型 (Box Model) 定义&#xff1a; CSS 盒模型是指每个 HTML 元素在页面上被视为一个矩形盒子。…

【贪心算法】No.1---贪心算法(1)

文章目录 前言一、贪心算法&#xff1a;二、贪心算法示例&#xff1a;1.1 柠檬⽔找零1.2 将数组和减半的最少操作次数1.3 最⼤数1.4 摆动序列1.5 最⻓递增⼦序列1.6 递增的三元⼦序列 前言 &#x1f467;个人主页&#xff1a;小沈YO. &#x1f61a;小编介绍&#xff1a;欢迎来到…

人工智能又创新!人声分离AI工具大放异彩

AI可以与人对话聊天、帮我们写PPT、做简单的图片处理等等&#xff0c;随着人工智能技术的发展&#xff0c;AI也逐渐深入到音视频编辑领域&#xff0c;很多人声分离AI工具应运而生。这些AI的作用&#xff0c;就是帮助我们从一首歌曲中将人声和伴奏分开。 AI是如何做到人声分离的…