Spring Boot 整合 socket 实现简单聊天

来看一下实现的界面效果
在这里插入图片描述
pom.xml的maven依赖

 <!-- 引入 socket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- 引入 Fastjson ,实现序列化使用  --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>

配置类

@Configuration
public class WebSocketConfiguration {/*** 给 spring 容器注入这个 ServerEndpointExporter对象* <p>* 这个bean会检测所有带有 @ServerEndpoint 注解的 bean 并注册他们。* ps:* 如果使用的是外置的 Tomcat 容器,则不需要自己提供 ServerEndpointExporter,因为它将由 Tomcat 容器自己提供和管理。** @return ServerEndpointExporter*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

消息接口

public interface Message {
}

WebSocket 会话上下文工具

@Slf4j
public class WebSocketContext {/*** Session 与用户的映射*/private static final Map<Session, String> SESSION_USER_MAP = new ConcurrentHashMap<>();/*** 用户与 Session 的映射*/private static final Map<String, Session> USER_SESSION_MAP = new ConcurrentHashMap<>();/*** 添加 Session 在这个方法中,会绑定用户和 Session 之间的映射** @param session Session* @param user    用户*/public static void add(Session session, String user) {// 更新 USER_SESSION_MAP , 这里的 user 正常来讲应该是具体的用户(id),而不是单纯的 session.getId()USER_SESSION_MAP.put(user, session);// 更新 SESSION_USER_MAPSESSION_USER_MAP.put(session, user);}/*** 移除 Session** @param session Session*/public static void remove(Session session) {// 从 SESSION_USER_MAP 中移除String user = SESSION_USER_MAP.remove(session);// 从 USER_SESSION_MAP 中移除if (user != null && user.length() > 0) {USER_SESSION_MAP.remove(user);}}/*** 广播发送消息给所有在线用户** @param type    消息类型* @param message 消息体* @param <T>     消息类型* @param me      当前消息的发送者,不会将消息发送给自己*/public static <T extends Message> void broadcast(String type, T message, Session me) {// 创建消息String messageText = buildTextMessage(type, message);// 遍历 SESSION_USER_MAP ,进行逐个发送for (Session session : SESSION_USER_MAP.keySet()) {if (!session.equals(me)) {sendTextMessage(session, messageText);}}}/*** 发送消息给单个用户的 Session** @param session Session* @param type    消息类型* @param message 消息体* @param <T>     消息类型*/public static <T extends Message> void send(Session session, String type, T message) {// 创建消息String messageText = buildTextMessage(type, message);// 遍历给单个 Session ,进行逐个发送sendTextMessage(session, messageText);}/*** 发送消息给指定用户** @param user    指定用户* @param type    消息类型* @param message 消息体* @param <T>     消息类型* @return 发送是否成功*/public static <T extends Message> boolean send(String user, String type, T message) {// 获得用户对应的 SessionSession session = USER_SESSION_MAP.get(user);if (session == null) {log.error("==> user({}) 不存在对应的 session", user);return false;}// 发送消息send(session, type, message);return true;}/*** 构建完整的消息** @param type    消息类型* @param message 消息体* @param <T>     消息类型* @return 消息*/private static <T extends Message> String buildTextMessage(String type, T message) {JSONObject messageObject = new JSONObject();messageObject.put("type", type);messageObject.put("body", message);return messageObject.toString();}/*** 真正发送消息** @param session     Session* @param messageText 消息*/private static void sendTextMessage(Session session, String messageText) {if (session == null) {log.error("===> session 为 null");return;}RemoteEndpoint.Basic basic = session.getBasicRemote();if (basic == null) {log.error("===> session.basic 为 null");return;}try {basic.sendText(messageText);} catch (IOException e) {log.error("===> session: {} 发送消息: {} 发生异常", session, messageText, e);}}/*** 在线人数通知*/public static void countNotice() {Integer count = SESSION_USER_MAP.size();ChatCountMessage message = new ChatCountMessage();message.setCount(count);broadcast(MsgTypeEnum.CHAT_COUNT.getCode(), message, null);}
}

消息类型枚举

@Getter
@AllArgsConstructor
public enum MsgTypeEnum {/*** 同于标识 当前消息是 聊天消息*/CHAT_MSG("1", "聊天消息"),/*** 用于标识 当前消息是 人数消息*/CHAT_COUNT("2", "聊天室人数");private final String code;private final String desc;
}

配置接入点
ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端

@Component
@ServerEndpoint("/chat")
@Slf4j
public class WebSocketServer {/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session) {log.info("===> onOpen:{}", session.getId());// 上线,并且通知到其他人WebSocketContext.add(session, session.getId());WebSocketContext.countNotice();}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(Session session) {log.info("===> onClose:{}", session.getId());// 下线,并且通知到其他人WebSocketContext.remove(session);WebSocketContext.countNotice();}/*** 收到客户端消息后调用的方法** @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message, Session session) {log.info("===> onMessage:{},message:{}", session.getId(), message);// 进行消息的转发,同步到其他的客户端上ChatMsgMessage msg = JSON.parseObject(message, ChatMsgMessage.class);WebSocketContext.broadcast(MsgTypeEnum.CHAT_MSG.getCode(), msg, session);}/*** 监听错误** @param session session* @param error   错误*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("SessionId:{},出现异常:{}", session.getId(), error.getMessage());error.printStackTrace();}}

在线人数消息实体

@Data
@Accessors(chain = true)
public class ChatCountMessage implements Message {public static final String TYPE = MsgTypeEnum.CHAT_COUNT.getCode();/*** 消息编号*/private String msgId;/*** 内容*/private Integer count;}

消息发送实体

@Data
@Accessors(chain = true)
public class ChatMsgMessage implements Message {public static final String TYPE = MsgTypeEnum.CHAT_MSG.getCode();/*** 消息编号*/private String msgId;/*** 内容*/private String msg;}

在resources下新建static静态文件夹
在这里插入图片描述
index.css文件

@font-face {font-family: "pix";src: url("../DottedSongtiSquareRegular.otf");
}html, body, pre, code, kbd, samp {font-family: "pix", serif;font-weight: bold;font-size: 35px;
}.all-div {display: flex;flex-direction: column;width: 800px;margin: 20px auto;overflow-scrolling: auto;
}/*.message {*/
/*    overflow: auto;*/
/*    width: 800px;*/
/*    height: 400px;*/
/*    margin-top: 20px;*/
/*}*/.send-btns {display: flex;
}/*自己发送聊天的样式*/
.message-me {color: red;text-align: right
}.message-list {display: flex;flex-direction: column;
}.message-left {display: flex;margin-top: 2rem;align-self: flex-start;
}
.message-right {display: flex;margin-top: 2rem;align-self: flex-end;
}

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>在线聊天</title><link href="NES.css" rel="stylesheet"/><link href="/css/index.css" rel="stylesheet">
</head>
<style>
</style>
<body>
<div class="all-div"><!--  头部区域  --><div style="margin-left: 10px">Spring Boot 集成 WebSocket 示例;<span class="nes-text is-primary">在线人数:</span><span class="nes-text is-error" id="count">0</span></div><!--  内容显示区域  --><div class="nes-container is-rounded is-dark message-list" id="message"></div><!--  操作区域  --><div class="nes-field is-inline"><br/><input id="text" type="text" class="nes-input" style="padding: .2rem 1rem !important;"/><button onclick="send()" class="nes-btn is-success">发送</button><button onclick="closeWebSocket()" class="nes-btn is-error">关闭WebSocket连接</button></div>
</div>
</body><script type="text/javascript">let websocket = null;//判断当前浏览器是否支持WebSocketif ('WebSocket' in window) {//改成你的地址websocket = new WebSocket("ws://127.0.0.1:8080/chat");} else {alert('当前浏览器不支持 websocket')throw "当前浏览器不支持 websocket"}//连接发生错误的回调方法websocket.onerror = function () {setMessageInnerHTML("WebSocket连接发生错误" + "&#13;");};//连接成功建立的回调方法websocket.onopen = function () {setMessageInnerHTML("WebSocket连接成功" + "&#13;");}//接收到消息的回调方法websocket.onmessage = function (event) {let jsonData = event.data;let data = JSON.parse(jsonData);console.log("收到消息==", event);if (data.type === "1") {let msg = otherPersonShowMsg(data.body.msg)setMessageInnerHTML(msg);}if (data.type === "2") {setChatCountInnerHTML(data.body.count)}}//连接关闭的回调方法websocket.onclose = function () {setMessageInnerHTML("WebSocket连接关闭" + "&#13;");}//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。window.onbeforeunload = function () {closeWebSocket();}//将消息显示在网页上function setMessageInnerHTML(innerHTML) {document.getElementById('message').innerHTML += innerHTML;}//将消息显示在网页上function setChatCountInnerHTML(innerHTML) {document.getElementById('count').innerHTML = innerHTML;}//关闭WebSocket连接function closeWebSocket() {websocket.close();setChatCountInnerHTML(0)}//发送消息function send() {var message = document.getElementById('text').value;websocket.send('{"msg":"' + message + '"}');document.getElementById('text').value = '';message = this.meShowMsg(message);setMessageInnerHTML(message);}// 显示别人发送的消息function otherPersonShowMsg(str) {return ` <section class="message-left"><i class="nes-bcrikko"></i><div class="nes-balloon from-left is-dark" style="padding: .2rem 1rem !important;"><p>${str}</p></div></section>`}// 显示自己发送的消息function meShowMsg(str) {return ` <section class="message-right"><div class="nes-balloon from-right is-dark" style="padding: .2rem 1rem !important;"><p>${str}</p></div><i class="nes-bcrikko"></i></section>`}
</script>
</html>

以上的是Spring Boot 整合 socket 实现简单聊天 若需完整代码 可识别二维码后 给您发代码。
在这里插入图片描述

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

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

相关文章

ifconfig命令找不到 command not found

问题 今天解决虚拟机的网络问题后&#xff0c;使用ifconfig发现报错命令未找到 解决方案 输入yum install ifconfi的程序安装包 yum install ifconfig 如果显示没有可用软件包 ifconfig&#xff0c;错误&#xff1a;。 就输入yum search ifconfig匹配安装包程序 yum searc…

【有趣的透镜】1.透镜初相识

1.透镜的外形和材料 (1)透镜由玻璃或者塑料制成&#xff1b; (2)透镜一般为圆型&#xff0c;其单面或双面为球面&#xff1b; 2.透镜的类型和折射 (1)球面外凸为凸透镜(聚光)&#xff0c;球面内凹为凹透镜(散光)&#xff1b; (2)透镜是基于光的折射&#xff0c;只要光从一…

ChatPPT开启高效办公新时代,AI赋能PPT创作

目录 一、前言二、ChatPPT的几种用法1、通过在线生成2、通过插件生成演讲者模式最终成品遇到问题改进建议 三、ChatPPT其他功能 一、前言 想想以前啊&#xff0c;为了做个PPT&#xff0c;我得去网上找各种模板&#xff0c;有时候还得在某宝上花钱买。结果一做PPT&#xff0c;经…

双层嵌线和线径的替代方案

电机只有三种嵌线方式 1.单层嵌线 2.双层嵌线 3.单双层嵌线 前面说的都是单层嵌线&#xff0c;下面介绍双层嵌线&#xff01; 双层嵌线一般线径都比较粗&#xff01; 线径只有几种规格的&#xff0c;大线径可用几根小线径替代&#xff01; 满足的原则&#xff1a;大线径A的…

【华为】路由综合实验(OSPF+BGP基础)

【华为】路由综合实验 实验需求拓扑配置AR1AR2AR3AR4AR5PC1PC2 查看通信OSPF邻居OSPF路由表 BGPBGP邻居BGP 路由表 配置文档 实验需求 ① 自行规划IP地址 ② 在区域1里面 启用OSPF ③ 在区域1和区域2 启用BGP&#xff0c;使AR4和AR3成为eBGP&#xff0c;AR4和AR5成为iBGP对等体…

2024面试自动化测试面试题【含答案】

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

C/C++ BM30 二叉搜索树与双向链表

文章目录 前言题目解决方案一1.1 思路阐述1.2 源码 解决方案二2.1 思路阐述2.2 源码 总结 前言 这道题要明白二叉搜索树的概念&#xff0c;同时还要对链表的知识比较熟悉。 题目 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的双向链表。如下图所示 数据范…

网页主题自动适配:网页跟随系统自动切换主题

主题切换是网站设计中一个非常有趣的功能&#xff0c;它允许用户在多种预先设计的样式之间轻松切换&#xff0c;以改变网站的视觉表现。最常见的就是白天和黑夜主题的切换&#xff0c;用户可以根据自己的喜好进行设置。 除了让用户手动去切换主题外&#xff0c;如果能够让用户第…

TypeScript学习日志-第十九天(namespace命名空间)

namespace命名空间 一、基本用法 namespace 所有的变量以及方法必须要导出才能访问&#xff0c;如图&#xff1a; 二、 嵌套 namespace 可以进行嵌套使用&#xff0c;如图&#xff1a; 它也必须需要导出才能访问 三、合并 当我们出现两个同名的 namespace 它就会合并这两…

docker Harbor私有仓库部署管理

搭建本地私有仓库&#xff0c;但是本地私有仓库的管理和使用比较麻烦&#xff0c;这个原生的私有仓库并不好用&#xff0c;所以我们采用harbor私有仓库&#xff0c;也叫私服&#xff0c;更加人性化。 一、什么是Harbor Harbor是VWware 公司开源的企业级Docker Registry项…

【SpringBoot记录】自动配置原理(1):依赖管理

前言 我们都知道SpringBoot能快速创建Spring应用&#xff0c;其核心优势就在于自动配置功能&#xff0c;它通过一系列的约定和内置的配置来减少开发者手动配置的工作。下面通过最简单的案例分析SpringBoot的功能特性&#xff0c;了解自动配置原理。 SpringBoot简单案例 根据S…

百面算法工程师 | 支持向量机面试相关问题——SVM

本文给大家带来的百面算法工程师是深度学习支持向量机的面试总结&#xff0c;文章内总结了常见的提问问题&#xff0c;旨在为广大学子模拟出更贴合实际的面试问答场景。在这篇文章中&#xff0c;我们还将介绍一些常见的深度学习算法工程师面试问题&#xff0c;并提供参考的回答…

python代码无法点击进入,如何破???

python代码无法点击进入&#xff0c;如何破&#xff1f;&#xff1f;&#xff1f; 举个栗子&#xff1a; model.chat是无法进入的&#xff0c;这时可以使用如下的命令进行操作&#xff1a; ?model.chat

Jmeter用jdbc实现对数据库的操作

我们在用Jmeter进行数据库的操作时需要用到配置组件“JDBC Connection Configuration”&#xff0c;通过配置相应的驱动能够让我们通过Jmeter实现对数据库的增删改查&#xff0c;这里我用的mysql数据库一起来看下是怎么实现的吧。 1.驱动包安装 在安装驱动之前我们要先查看当前…

【LAMMPS学习】八、基础知识(5.9)LAMMPS 近场动力学

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语,以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各种模拟。 …

这 7 道 Redis 基础问题,很常见!!

后端项目如果用到分布式缓存的话&#xff0c;一般用的都是 Redis。不过&#xff0c;Redis 不仅仅能做缓存&#xff0c;还能用作分布式锁、延时队列、限流等等。 什么是 Redis&#xff1f; Redis[1] &#xff08;REmote DIctionary Server&#xff09;是一个基于 C 语言开发的…

基于Springboot+Vue+Java的校园资料分享平台

&#x1f49e; 文末获取源码联系 &#x1f649; &#x1f447;&#x1f3fb; 精选专栏推荐收藏订阅 &#x1f447;&#x1f3fb; &#x1f380;《Java 精选实战项目-计算机毕业设计题目推荐-期末大作业》&#x1f618; 更多实战项目~ https://www.yuque.com/liuyixin-rotwn/ei3…

springboot3项目练习详细步骤(第一部分:用户业务模块)

目录 环境准备 用户模块 注册 注册接口文档 ​编辑 实现结构 Spring Validation 登录 登录的接口文档 实现登录逻辑 JWT令牌 完善登录认证 拦截器 获取用户详细信息 接口文档 Usercontroller类中编写方法接口 忽略属性返回 优化代码ThreadLocal 更新用户基本信…

点击短信链接唤起Android App实战

一.概述 在很多业务场景中,需要点击短信链接跳转到App的指定页面。在Android系统中,想要实现这个功能,可以通过DeepLink或AppLink实现。二.方案 2.1 DeepLink 2.1.1 方案效果 DeepLink是Android系统最基础、最普遍、最广泛的外部唤起App的方式,不受系统版本限制。当用户…

YOLOv8+PyQt5蔬菜识别检测(26种不同蔬菜类型,yolov8模型,从图像、视频和摄像头三种路径识别检测)

1.基于最新的YOLOv8训练的蔬菜检测模型&#xff0c;和基于PyQt5制作的可视蔬菜检测系统&#xff0c;该系统可自动检测和识别图片或视频当中出现的26种蔬菜&#xff1a;鸡蛋, 姜, 菜椒, 南瓜, 山药, 辣椒, 霉豆, 蘑菇, 香菜, 茼蒿, 油菜, 黄瓜, 角瓜, 莲藕, 西兰花, 菜花, 土豆,…