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.mzph.cn/pingmian/9074.shtml

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

相关文章

bert-NER 转化成 onnx 模型

保存模型 加载模型 from transformers import AutoTokenizer, AutoModel, AutoConfigNER_MODEL_PATH ./save_model ner_tokenizer AutoTokenizer.from_pretrained(NER_MODEL_PATH) ner_config AutoConfig.from_pretrained(NER_MODEL_PATH) ner_model AutoModelForTokenCl…

【雅思写作】Vince9120雅思小作文笔记——P1 Intro(前言)

文章目录 链接P1 Intro&#xff08;前言&#xff09;字数限制题型综述&#xff08;problem types overview&#xff09;1. **柱状图&#xff08;Bar Chart&#xff09;** - 描述不同类别在某个或多个变量上的数据量比较。2. **线图&#xff08;Line Graph&#xff09;** - 展示…

冯喜运:5.10黄金反弹受阻,原油EIA库存激增引发市场情绪

【黄金消息面分析】&#xff1a;据最新市场数据显示&#xff0c;现货黄金在周四欧市早盘经历了显著下滑&#xff0c;价格一度跌破2310美元/盎司的关口&#xff0c;日内高点回落达10美元&#xff0c;截至发稿&#xff0c;黄金小幅反弹&#xff0c;交投于2312美元/盎司附近。此番…

【工具】如何提取一个mp4文件的关键帧

文章目录 怎么做如何安装ffmepgUbuntu 或 DebianCentOS 或 FedoramacOSWindows其他 Linux 发行版 实践什么是关键帧 怎么做 你可以使用ffmpeg这个强大的多媒体处理工具来提取mp4文件中的关键帧。以下是一个示例命令&#xff0c;可以使用ffmpeg从mp4文件中提取关键帧&#xff1…

即将开幕,邀您共赴创新之旅“2024上海国际消费者科技及创新展览会”

备受期待的2024上海国际消费者科技及创新展览会&#xff08;以下简称“CTIS”&#xff09;即将于6月13日至15日亮相上海新国际博览中心N1-N3馆。 2024上海国际消费者科技及创新展览会总面积达40,000平方米&#xff0c;涵盖600余家展商&#xff0c;预计吸引40,000多位观众莅临现…

单片机——直流电机

1 .关于4线直流电机 两根12v供电线&#xff0c;通入12v&#xff0c;风扇以最高转速工作。 一根测速线&#xff0c;电机工作时输出测速信号&#xff0c;提供转速反馈。一根PWM控制信号线&#xff0c;电机工作时控制器输入PWM控制信号&#xff0c;以控制风扇转速(通常为占空比可…

Python爬虫基础知识学习(以爬取某二手房数据、某博数据与某红薯(书)评论数据为例)

一、爬虫基础流程 爬虫的过程模块化&#xff0c;基本上可以归纳为以下几个步骤&#xff1a; 1、分析网页URL&#xff1a;打开你想要爬取数据的网站&#xff0c;然后寻找真实的页面数据URL地址&#xff1b; 2、请求网页数据&#xff1a;模拟请求网页数据&#xff0c;这里我们介…

双翻斗雨量计学习

双翻斗雨量计用户手册&#xff08;脉冲型&#xff09; 本仪器由雨量计壳体、承雨口、漏斗、翻斗支撑、上漏斗雨量调节支架、上漏斗、汇集漏斗、计数翻斗雨量调节支架、计数翻斗、干簧管安装架、轴承螺钉、出水漏斗、腿部支架、干簧管、水平泡、调节支撑板、控制盒、调平装置、接…

安装oh-my-zsh(命令行工具)

文章目录 一、安装zsh、git、wget二、安装运行脚本1、curl/wget下载2、手动下载 三、切换主题1、编辑配置文件2、切换主题 四、安装插件1、zsh-syntax-highlighting&#xff08;高亮语法错误&#xff09;2、zsh-autosuggestions&#xff08;自动补全&#xff09; 五、更多优化配…

MySQL#MySql表的操作

目录 一、创建表 二、查看表结构 三、修改表 1.修改表的名字 2.新增一个列 3.修改列 4.删除列 5.修改列的名称 四、删除表 一、创建表 语法&#xff1a; CREATE TABLE table_name (field1 datatype,field2 datatype,field3 datatype ) character set 字符集 collate 校…

element-ui skeleton 组件源码分享

今日简单分享 skeleton 骨架屏组件源码&#xff0c;主要从以下四个方面来讲解&#xff1a; 1、skeleton 组件的页面结构 2、skeleton 组件的属性 3、skeleton item 组件的属性 4、skeleton 组件的 slot 一、skeleton 组件的页面结构 二、skeleton 组件的属性 2.1 animate…

漏洞管理是如何在攻击者之前识别漏洞从而帮助人们阻止攻击的

漏洞管理 是主动查找、评估和缓解组织 IT 环境中的安全漏洞、弱点、差距、错误配置和错误的过程。该过程通常扩展到整个 IT 环境&#xff0c;包括网络、应用程序、系统、基础设施、软件和第三方服务等。鉴于所涉及的高成本&#xff0c;组织根本无法承受网络攻击和数据泄露。如果…

JUC下的ForkJoinPool详解

详细介绍 ForkJoinPool 是 Java 并发包 (java.util.concurrent) 中的一个特殊线程池&#xff0c;专为分治算法设计&#xff0c;能够高效地处理大量可分解的并行任务。它基于工作窃取&#xff08;work-stealing&#xff09;算法&#xff0c;当一个工作线程的任务队列为空时&…

HFSS学习-day3-HFSS的工作界面

工作界面也称为用户界面&#xff0c;是HFSS软件使用者的工作环境:了解、熟悉这个工作环境是掌握HFSS软件使用的第一步 HFSS工作环境介绍 1.HFSS工作界面简单的组成说明2.工作界面中各个工作窗口功能主菜单工具栏项目管理窗口属性窗口信息管理窗口进程窗口三维模型窗口 3.HFSS主…

数据结构_栈和队列(Stack Queue)

✨✨所属专栏&#xff1a;数据结构✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 栈&#xff1a; 代码&#xff1a;function/数据结构_栈/stack.c 钦某/c-language-learning - 码云 - 开源中国 (gitee.com)https://gitee.com/wang-qin928/c-language-learning/blob/master/function/…

java中的oop(三)、构造器、javabean、uml类图、this、继承

!! 有get/set方法的情况基本就是说要搞个私有属性&#xff0c;不直接对外开放&#xff1b; 构造器 Person p new Person(); //其中的Person();就是构造器&#xff1b;---造对象&#xff1b;Constructor–建设者&#xff0c;建造者&#xff1b; 作用 搭配new 创建类的&…

docker学习-docker常用其他命令整理

随便写写&#xff0c;后面有空再更新 镜像命令&#xff0c;容器命令已在之前略有更新&#xff0c;这次不写&#xff0c; 一、后台启动命令 # 命令 docker run -d 容器名 # 例子 docker run -d centos # 启动centos&#xff0c;使用后台方式启动 # 问题&#xff1a; 使用doc…

大数据手册(Spark)--Spark 简介

Spark 简介 Apache Spark 是一种用于大数据工作负载的分布式开源处理系统。它使用内存中缓存和优化的查询执行方式&#xff0c;可针对任何规模的数据进行快速分析查询。Apache Spark 提供了简明、一致的 Java、Scala、Python 和 R 应用程序编程接口 (API)。 Apache Spark 是专…

代码随想录第四十三天|最后一块石头的重量 II 、目标和

题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 代码如下&#xff1a; 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 代码如下&#xff1a;

用户行为分析与内容创新:Kompas.ai的数据驱动策略

在数字化营销的今天&#xff0c;用户行为数据分析已成为内容创新和策略调整的核心。通过深入理解用户的行为模式和偏好&#xff0c;品牌能够创造出更具吸引力和相关性的内容&#xff0c;从而实现精准营销。本文将探讨用户行为数据分析在内容创新和策略调整中的价值&#xff0c;…