最近公司在做一个婚恋app,需要增加一个功能,实现多人实时在线聊天。基于WebSocket在Springboot中的使用,前端使用vue开发。

一:后端 

1. 引入 websocket 的 maven 依赖

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

2. 进行 config 配置 ServerEndpointExporter 确保【后续在使用 @ServerEndpoint 】时候能被 SpringBoot 自动检测并注册

 

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration // 这个类为配置类,Spring 将扫描这个类中定义的 Beans
public class WebSocketConfig {/*** serverEndpointExporter 方法的作用是将 ServerEndpointExporter 注册为一个 Bean,* 这个 Bean 负责自动检测带有 @ServerEndpoint 注解的类,并将它们注册为 WebSocket 服务器端点,* 这样,这些端点就可以接收和处理 WebSocket 请求**/@Bean // 这个方法返回的对象应该被注册为一个 Bean 在 Spring 应用上下文中public ServerEndpointExporter serverEndpointExporter() {// 创建并返回 ServerEndpointExporter 的实例,其中ServerEndpointExporter 是用来处理 WebSocket 连接的关键组件return new ServerEndpointExporter();}}

3.后端注册webSocket服务

   后端:广播给所有客户端 ,在@OnMessage 将单一广播,切换为群体广播

   存储所有用户会话userId

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;//为 ConcurrentHashMap<String,WebSocketServer> 加入一个会话userId
@ServerEndpoint("/chatWebSocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {/***  [关于@OnOpen、@OnMessage、@OnClose、@OnError 中 Session session 的用意]**  Session session: 主要用于代表一个单独的 WebSocket 连接会话.每当一个 WebSocket 客户端与服务器端点建立连接时,都会创建一个新的 Session 实例*      标识连接:每个 Session 对象都有一个唯一的 ID,可以用来识别和跟踪每个单独的连接。   ——>     可以使用 session.getId() 方法来获取这个 ID.对于日志记录、跟踪用户会话等方面非常有用。*      管理连接:可以通过 Session 对象来管理对应的 WebSocket 连接,例如发送消息给客户端、关闭连接等    ——>     session.getBasicRemote().sendText(message) 同步地发送文本消息,*                                                                                                 或者使用 session.getAsyncRemote().sendText(message) 异步地发送.可以调用 session.close() 来关闭 WebSocket 连接。*      获取连接信息:Session 对象提供了方法来获取连接的详细信息,比如连接的 URI、用户属性等。    ——>     可以使用 session.getRequestURI() 获取请求的 URI* **///存储所有用户会话//ConcurrentHashMap<String,WebSocketServer> 中String 键(String类型)通常是用户ID或其他唯一标识符。允许服务器通过这个唯一标识符快速定位到对应的 WebSocketServer 实例,从而进行消息发送、接收或其他与特定客户端相关的操作//ConcurrentHashMap<String,WebSocketServer> 中为什么写 WebSocketServer 而不是其他,因为 WebSocketServer 作为一个实例,用于存储每个客户端连接。//所以在接下来@Onopen等使用中,当使用 ConcurrentHashMap<String,WebSocketServer> 时候,就不能单独使用 session, 需要添加一个诸如 userId 这样的会话来作为键。private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();private Session session;private String userId="";//建立连接时@OnOpen//获取会话userId//@PathParam: 是Java JAX-RS API(Java API for RESTful Web Services)的一部分,用于WebSocket和RESTful Web服务. 在WebSocket服务器端,@PathParam 注解用于提取客户端连接URL中的参数值。public void onOpen(Session session, @PathParam("userId") String userId){this.session = session;             //当前WebSocket连接的 Session 对象存储在 WebSocketServer 实例 【这样做是为了在后续的通信过程中(例如在处理消息、关闭连接时),您可以使用 this.session 来引用当前连接的 Session 对象。】this.userId = userId;               //存储前端传来的 userId;webSocketMap.put(userId,this);      //WebSocketServer 实例与用户userId关联,并将这个关联存储在 webSocketMap 中。【其中this: 指的是当前的 WebSocketServer 实例】log.info("会话id:" + session.getId() + "对应的会话用户:" + userId + "【进行链接】");log.info("【websocket消息】有新的连接, 总数:{}", webSocketMap.size());System.out.println("会话id:" + session.getId() + " 对应的会话用户:" + userId + " 【进行链接】");System.out.println("【websocket消息】有新的连接, 总数: "+webSocketMap.size());}//接收客户端消息@OnMessagepublic void onMessage(String message,Session session) throws IOException {//当从客户端接收到消息时调用log.info("会话id"+ session.getId() +"对应的会话用户:" + userId + "的消息:" + message);System.out.println("会话id: "+ session.getId() +" 对应的会话用户:" + userId + " 的消息: " + message);//修改 onMessage 方法来实现广播: 当服务器接收到消息时,不是只发送给消息的发送者,而是广播给所有连接的客户端。 ——> (实现群聊)//判断message传来的消息不为空时,才能在页面上进行显示if(message != null && !message.isEmpty()){JSONObject obj = new JSONObject();obj.put("userId", userId);obj.put("message", message);// 封装成 JSON (Java对象转换成JSON格式的字符串。)String json = new ObjectMapper().writeValueAsString(obj);for(WebSocketServer client :webSocketMap.values()){client.session.getBasicRemote().sendText(json);}}}//链接关闭时@OnClosepublic void onClose(Session session){//关闭浏览器时清除存储在 webSocketMap 中的会话对象。webSocketMap.remove(userId);log.info("会话id:" + session.getId() + "对应的会话用户:" + userId + "【退出链接】");log.info("【websocket消息】有新的连接, 总数:{}", webSocketMap.size());System.out.println("会话id:" + session.getId() + " 对应的会话用户:" + userId + " 【退出链接】");System.out.println("【websocket消息】有新的连接, 总数: "+ webSocketMap.size());}//链接出错时@OnErrorpublic void onError(Session session,Throwable throwable){//错误提示log.error("出错原因 " + throwable.getMessage());System.out.println("出错原因 " + throwable.getMessage());//抛出异常throwable.printStackTrace();}
}

如果不是群发,一对一对话 单一广播, onMessage方法如下:

//接收客户端消息@OnMessagepublic void onMessage(String message,Session session) throws IOException {//当从客户端接收到消息时调用log.info("会话id:" + session.getId() + ": 的消息" + message);session.getBasicRemote().sendText("回应" + "[" + message + "]");
}

 

二:前端

在 Vue 中使用 WebSocket 并不需要引入专门的库或框架,因为 WebSocket 是一个浏览器内置的 API,可以直接在任何现代浏览器中使用。但是,你可能需要编写一些代码来适当地处理 WebSocket 连接、消息的发送与接收、错误处理以及连接的关闭。

前端: (前端整体没有发生太大变化,只加了一个userId用于向后端传输)

<template><div class="iChat"><div class="container"><div class="content"><div class="item item-center"><span>今天 10:08</span></div><div class="item" v-for="(item, index) in receivedMessage" :key="index" :class="{'item-right':isCurrentUser(item),'item-left':!isCurrentUser(item)}"><!-- 右结构 --><div v-if="isCurrentUser(item)" style="display: flex"><div class="bubble" :class="{'bubble-right':isCurrentUser(item),'bubble-left':!isCurrentUser(item)}">{{item.message}}</div><div class="avatar"><imgsrc="http://192.168.0.134/img/20250701114048Tclu5k.png"/>{{item.userId}}</div></div><!-- 左结构 --><div v-else style="display: flex"><div class="avatar">{{item.userId}}<imgsrc="http://192.168.0.134/img/202507031603386lQ4ft.png"/></div><div class="bubble" :class="{'bubble-right':isCurrentUser(item),'bubble-left':!isCurrentUser(item)}">{{item.message}}</div></div></div></div><div class="input-area"><!-- 文本框 --><textarea v-model="message" id="textarea"></textarea><div class="button-area"><button id="send-btn" @click="sendMessage()">发 送</button></div></div></div></div>
</template><script>
export default {data() {return {ws:null,message:'',receivedMessage:[],currentUserId:"用户1" + Math.floor(Math.random() * 1000)};},mounted() {this.initWebSocket()},methods: {//建立webSocket连接initWebSocket(){//定义用户的,并加入到下述链接中,且记不要少了/const userId = this.currentUserId;//链接接口this.ws = new WebSocket('ws://localhost:9000/jsonflow/chatWebSocket/' + userId)console.log('ws://localhost:9000/jsonflow/chatWebSocket/' + userId);//打开事件this.ws.onopen = function(){console.log("websocket已打开");}//消息事件this.ws.onmessage = (event) => {//接到后端传来数据 - 并对其解析this.receivedMessage.push(JSON.parse(event.data));console.log(this.receivedMessage)}//关闭事件this.ws.onclose = function() {console.log("websocket已关闭");};//错误事件this.ws.onerror = function() {console.log("websocket发生了错误");};},//发送消息到服务器sendMessage(){this.ws.send(this.message);this.message = '';},//判断是否是当前用户(boolean值)isCurrentUser(item){return item.userId == this.currentUserId}},
};
</script>
<style lang="scss" scoped>
.container{height: 666px;border-radius: 4px;border: 0.5px solid #e0e0e0;background-color: #f5f5f5;display: flex;flex-flow: column;overflow: hidden;
}
.content{width: calc(100% - 40px);padding: 20px;overflow-y: scroll;flex: 1;
}
.content:hover::-webkit-scrollbar-thumb{background:rgba(0,0,0,0.1);
}
.bubble{max-width: 400px;padding: 10px;border-radius: 5px;position: relative;color: #000;word-wrap:break-word;word-break:normal;
}
.item-left .bubble{margin-left: 15px;background-color: #fff;
}
.item-left .bubble:before{content: "";position: absolute;width: 0;height: 0;border-left: 10px solid transparent;border-top: 10px solid transparent;border-right: 10px solid #fff;border-bottom: 10px solid transparent;left: -20px;
}
.item-right .bubble{margin-right: 15px;background-color: #9eea6a;
}
.item-right .bubble:before{content: "";position: absolute;width: 0;height: 0;border-left: 10px solid #9eea6a;border-top: 10px solid transparent;border-right: 10px solid transparent;border-bottom: 10px solid transparent;right: -20px;
}
.item{margin-top: 15px;display: flex;width: 100%;
}
.item.item-right{justify-content: flex-end;
}
.item.item-center{justify-content: center;
}
.item.item-center span{font-size: 12px;padding: 2px 4px;color: #fff;background-color: #dadada;border-radius: 3px;-moz-user-select:none; /*火狐*/-webkit-user-select:none; /*webkit浏览器*/-ms-user-select:none; /*IE10*/-khtml-user-select:none; /*早期浏览器*/user-select:none;
}.avatar img{width: 42px;height: 42px;border-radius: 50%;
}
.input-area{border-top:0.5px solid #e0e0e0;height: 150px;display: flex;flex-flow: column;background-color: #fff;
}
textarea{flex: 1;padding: 5px;font-size: 14px;border: none;cursor: pointer;overflow-y: auto;overflow-x: hidden;outline:none;resize:none;
}
.button-area{display: flex;height: 40px;margin-right: 10px;line-height: 40px;padding: 5px;justify-content: flex-end;
}
.button-area button{width: 80px;border: none;outline: none;border-radius: 4px;float: right;cursor: pointer;
}/* 设置滚动条的样式 */
::-webkit-scrollbar {width:10px;
}
/* 滚动槽 */
::-webkit-scrollbar-track {-webkit-box-shadow:inset006pxrgba(0,0,0,0.3);border-radius:8px;
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {border-radius:10px;background:rgba(0,0,0,0);-webkit-box-shadow:inset006pxrgba(0,0,0,0.5);
}
</style>

三:最终效果

 页面效果:

 

 

后端日志:

四:后续:

       代码中用户头像我是用nginx代理的,写死了一个头像。后期前端可以根据系统当前登录人取他的头像,选中头像后,与某个人对话。总之基本对话功能都实现了,欢迎白嫖党一键三连,哈哈哈哈哈哈~~~~~~~~~~~

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记随记-FPGA/硬件加速

一、FPGA&#xff1a;Field Programmable Gate Array 现场可编程门阵列 可编程输入/输出单元、基本可编程逻辑单元、嵌入式块RAM、丰富的布线资源、底层嵌入功能单元和内嵌专用硬核。 可编程输入/输出单元&#xff08;I/O&#xff09;单元 输入/输出&#xff08;Input/Ouput&…

docker宿主机修改ip后起不来问题解决

确保容器已经连接到了正确的网络。如果没有&#xff0c;你可以使用以下命令将容器连接到网络&#xff1a; 1、停止docker网络 ifconfig docker0 down1. 停止 Docker 服务 sudo systemctl stop docker2. 删除 docker0 接口 sudo ip link delete docker03、删除旧的网桥 docker n…

G1 垃圾回收算法详解

目录 简介 G1 GC 的设计目标 内存结构 回收过程 初始标记&#xff08;Initial Mark&#xff09;并发标记&#xff08;Concurrent Mark&#xff09;最终标记&#xff08;Final Mark / Remark&#xff09;筛选回收&#xff08;Cleanup / Evacuation&#xff09; 混合回收&…

JavaEE多线程——锁策略 CAS synchronized优化

目录前言1.锁策略1.1 乐观锁和悲观锁1.2 重量级锁和轻量级锁1.3 挂起等待锁和自旋锁1.4 公平锁和非公平锁1.5 可重入锁和不可重入锁1.6 读写锁2.CAS2.1 CAS的应用2.2 CAS的ABA问题3.synchronized优化3.1锁升级3.2锁消除3.3锁粗化总结前言 本篇文章主要介绍多线程中锁策略、CAS…

Windows符号链接解决vscode和pycharm占用C盘空间太大的问题

Windows符号链接解决vscode和pycharm占用C盘空间太大的问题 参考文章&#xff1a;Windows符号链接 1、找到vscode和pycharm在C盘的缓存文件夹。 C:\Users\用户名\AppData\Roaming\Code C:\Users\用户名\.vscode\extensionsC:\Users\用户名\AppData\Local\JetBrains C:\Users…

赋能家庭、行业与工业场景,智微智能新一代Twin Lake 全栈智能终端发布

在数字化浪潮席卷全球的今天&#xff0c;智能终端已成为连接物理世界与数字世界的核心枢纽。智微智能基于Intel Twin Lake平台&#xff0c;推出覆盖家庭/行业应用及工业物联网的全场景产品矩阵&#xff0c;为不同场景下的用户提供高效、可靠的产品和解决方案。Intel Twin Lake架…

复习笔记 31

前言 好好复习。今天距离考研初试还剩一百六十一天。我的时间其实没剩多少了呀。我得好好加油。 归并排序 #include<algorithm> #include<iostream>using namespace std;const int N 100010; int n; int a[N], tmp[N];void merge ( int a[], int l, int r ) {if (…

el-tree 懒加载 loadNode

el-tree 是 Element UI 提供的树形组件&#xff0c;其懒加载功能通过 loadNode 方法实现&#xff0c;可以在用户展开节点时动态加载子节点数据&#xff0c;避免一次性加载大量数据。下面介绍 loadNode 的具体用法和示例。基本用法loadNode 是 el-tree 的一个重要属性&#xff0…

【机器学习入门巨详细】(研0版)二创OPEN MLSYS

自学机器学习&#xff0c;从入门到精通导论机器学习的基本框架设计目标机器学习框架基本组成原理机器学习生态机器学习工作流环境配置数据处理模型定义损失函数和优化器训练及保存模型测试及验证模型定义深度神经网络以层为核心定义神经网络神经网络层实现原理自定义神经网络层…

Excel 转 JSON by WTSolutions API 文档

Excel 转 JSON by WTSolutions API 文档 简介 Excel 转 JSON API 提供了一种简单的方式将 Excel 和 CSV 数据转换为 JSON 格式。该 API 接受制表符分隔或逗号分隔的文本数据&#xff0c;并返回结构化的 JSON。 接口端点 POST https://mcp.wtsolutions.cn/excel-to-json-api 请求…

git版本发布

cvs和svn都是集中式版本控制系统,而git是分布式版本控制系统。 1、集中式版本控制系统必须联网才能工作&#xff0c;如果在局域网内还好&#xff0c;带宽够大&#xff0c;速度够快&#xff0c;可如果在互联网上&#xff0c;遇到网速慢的话&#xff0c;呵呵。分布式版本控制系统…

138-EMD-KPCA-CPO-CNN-BiGRU-Attention模型!

138-EMD-KPCA-CPO-CNN-BiGRU-Attention基于经验模态分解和核主成分分析的长短期记忆网络改进多维时间序列预测MATLAB代码&#xff01;其中&#xff08;含CPO-CNN-BiGRU-attention、EMD-CPO-CNN-BiGRU-Attention、EMD-KPCA-CPO-CNN-BiGRU-Attention三个模型的对比&#xff09; 可…

系统思考:多元胜过能力

系统思考&#xff1a;从整体出发&#xff0c;打破瓶颈&#xff0c;拥抱多元 我们是否曾经陷入过这样的困境&#xff1f; 1、专注能力提升&#xff0c;却无法突破瓶颈&#xff1a;我和团队日复一日地努力提升个人能力&#xff0c;投入无数时间和精力&#xff0c;但始终无法打破现…

qt样式整合持续更新中(实测有效的)

// 仅显示上边框 一般可以作为直线使用 border-top: 2px solid black; //画虚线 border-bottom: 2px dashed white; //单个圆角 border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; //透明背景 background:rgba(0,0,0,0); //设置字体 font:15pt; //给button设置…

[java][springboot]@PostConstruct的介绍和用法

在 Spring Boot&#xff08;以及整个 Spring Framework&#xff09;中&#xff0c;PostConstruct 是一个非常常用的注解&#xff0c;用于在 依赖注入完成后 执行一些初始化操作。import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component;Co…

Leaflet面试题及答案(41-60)

查看本专栏目录 文章目录 🟢 面试问题及答案(41-60)41. 如何判断某个点是否在地图可视区域内?42. 如何动态更新 Marker 位置?43. 如何清除地图上的所有图层?44. 如何保存地图截图?45. 如何检测浏览器是否支持触摸?46. Leaflet 是否支持 TypeScript?47. 如何修改默认图…

Redis事件机制

Redis 采用事件驱动机制来处理大量的网络IO。它并没有使用 libevent 或者 libev 这样的成熟开源方案&#xff0c;而是自己实现一个非常简洁的事件驱动库 ae_event。事件机制Redis中的事件驱动库只关注网络IO&#xff0c;以及定时器。该事件库处理下面两类事件&#xff1a;文件事…

Linux基础开发工具

目录 1.写在前面 2.权限 3.file命令 4.基础开发工具 1.软件包管理器 5.编辑器vim 1.写在前面 我们在上一讲解中讲解了权限是人事物属性&#xff0c;还知道了拥有者所属组其他人这三个概念&#xff0c;知道了33一组&#xff0c;rwx分别代表什么。那么下面我们继续进行权限…

ICCV2025 特征点检测 图像匹配 RIPE

目测对刚性物体效果比较好代码&#xff1a;https://github.com/fraunhoferhhi/RIPE 论文&#xff1a;https://arxiv.org/abs/2507.04839import cv2 import kornia.feature as KF import kornia.geometry as KG import matplotlib.pyplot as plt import numpy as np import torc…

Ubuntu22.0.4安装PaddleNLP

Ubuntu22.0.4安装PaddleNLP环境说明安装底层框架Paddle安装PddleNLP1. pip安装2. 验证安装3. 最后问题集锦环境说明 1. miniconda 25.5.1 2. python 3.12.11 3. pip 25.1 4. nvidia 570.144 5. cuda 12.8**注意&#xff1a;**安装过程可能遇到的一些问题&#xff0c;参考末尾的…