SpringBoot使用WebSocket收发实时离线消息

引入maven依赖

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

 WebScoket配置处理器


import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.servlet.ServletContext;/*** WebScoket配置处理器*/
@Configuration
public class WebSocketConfig implements ServletContextInitializer {/*** ServerEndpointExporter 作用** 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint** @return*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}//设置websocket发送内容长度@Overridepublic void onStartup(ServletContext servletContext)  {servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","22428800");}
}

webScoket消息对象

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.Date;/**
* @author: ws
* @date: 20223/10/26 15:59
* @Description: WebSocketMessage
*/
@Data
public class WebSocketMessage {/**
* 用户ID
*/
private String fromId;/**
* 对方ID
*/
private String toOtherId;
//消息内容
private String message;//发送时间
@JSONField(format="yyyy-MM-dd HH:mm:ss")
public Date date;}

WebSocket操作类

import cn.hutool.core.collection.ListUtil;
import com.alibaba.fastjson.JSON;
import com.ws.wxyinghang.entity.WebSocketMessage;
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.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;/*** @author: ws* @date: 20223/10/26 15:59* @Description: WebSocket操作类*/
@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocketSever {// 与某个客户端的连接会话,需要通过它来给客户端发送数据private Session session;private String userId;// session集合,存放对应的sessionprivate static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。private static CopyOnWriteArraySet<WebSocketSever> webSocketSet = new CopyOnWriteArraySet<>();// 用于存放离线消息private static ConcurrentHashMap<String, List<WebSocketMessage>> offlineMessageMap = new ConcurrentHashMap();/*** 建立WebSocket连接** @param session* @param userId  用户ID*/@OnOpenpublic void onOpen(Session session, @PathParam(value = "userId") String userId) {log.info("WebSocket建立连接中,连接用户ID:{}", userId);try {Session historySession = sessionPool.get(userId);// historySession不为空,说明已经有人登陆账号,应该删除登陆的WebSocket对象if (historySession != null) {webSocketSet.remove(historySession);historySession.close();}} catch (IOException e) {log.error("重复登录异常,错误信息:" + e.getMessage(), e);}// 建立连接this.session = session;this.userId = userId;webSocketSet.add(this);sessionPool.put(userId, session);//从离线消息队列里面获取消息if (offlineMessageMap.containsKey(userId)) {List<WebSocketMessage> list = offlineMessageMap.get(userId);Iterator it = list.iterator();while (it.hasNext()) {Object x = it.next();//离线消息接收成功后删除消息Boolean bb = sendOfflineMessageByUser(JSON.toJSONString(x));if (bb) {System.out.println("从队列中删除离线消息" + x);it.remove();}}offlineMessageMap.remove(userId);}log.info("建立连接完成,当前在线人数为:{}", webSocketSet.size());}/*** 发生错误** @param throwable e*/@OnErrorpublic void onError(Throwable throwable) {throwable.printStackTrace();}/*** 连接关闭*/@OnClosepublic void onClose() {webSocketSet.remove(this);sessionPool.remove(this.userId);log.info("连接断开,当前在线人数为:{}", webSocketSet.size());}/*** 接收客户端消息** @param message 接收的消息*/@OnMessagepublic void onMessage(String message) {log.info("收到客户端发来的消息:{}", message);sendMessageByUser(message);}/*** 推送消息到指定用户** @param message 发送的消息*/public static Boolean sendMessageByUser(String message) {WebSocketMessage msg = JSON.parseObject(message, WebSocketMessage.class);log.info("用户ID:" + msg.getToOtherId() + ",推送内容:" + message);Session session = sessionPool.get(msg.getToOtherId());//判断session是否正常if (session == null || !session.isOpen()) {log.info("用户ID:" + msg.getToOtherId() + ",离线,放入离线消息队列中");if (offlineMessageMap.containsKey(msg.getToOtherId())) {List<WebSocketMessage> list = offlineMessageMap.get(msg.getToOtherId());list.add(msg);offlineMessageMap.put(msg.getToOtherId(), list);} else {offlineMessageMap.put(msg.getToOtherId(), ListUtil.toList(msg));}}//发送消息else {try {session.getBasicRemote().sendText(message);} catch (IOException e) {log.error("推送消息到指定用户发生错误:" + e.getMessage(), e);return false;}}return true;}//发送离线消息public static Boolean sendOfflineMessageByUser(String message) {WebSocketMessage msg = JSON.parseObject(message, WebSocketMessage.class);log.info("用户ID:" + msg.getToOtherId() + ",推送内容:" + message);Session session = sessionPool.get(msg.getToOtherId());try {session.getBasicRemote().sendText(message);} catch (IOException e) {log.error("推送消息到指定用户发生错误:" + e.getMessage(), e);return false;}return true;}/*** 群发消息** @param message 发送的消息*/public static void sendAllMessage(String message) {log.info("发送消息:{}", message);for (WebSocketSever webSocket : webSocketSet) {try {webSocket.session.getBasicRemote().sendText(message);} catch (IOException e) {log.error("群发消息发生错误:" + e.getMessage(), e);}}}}

启动项目,使用apiFox测试,新建webScoket接口

新建websocket1,连接后发送消息 

 

新建webScoket2 ,可以看到连接后接收到了消息 

 

如果webScoket2断开连接后, webScoket1继续发送消息,等webScoket2连接后就会收到离线的消息。

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

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

相关文章

如何为你的地图数据设置地图样式?

地图样式设置是GIS系统中非常重要的功能模块&#xff0c;水经微图Web版本最近对符号样式功能模块进行了升级。 你可以通过以下网址直接打开访问&#xff1a; https://map.wemapgis.com 现在我们为大家分享一下水经微图Web版中&#xff0c;如何为你标注的地图数据设置地图样式…

【干货】JVS低代码表单基础组件的配置与应用

表单的基础组件主要用于收集用户输入的数据&#xff0c;并对这些数据进行验证和处理。通过表单组件&#xff0c;用户可以输入各种类型的数据&#xff0c;如文本、数字、日期、选择项等。这些数据可以通过表单的提交按钮提交到服务器进行处理&#xff0c;从而使网站或应用程序能…

【git命令】修改分支名字

修改分支名字的步骤&#xff1a; 首先修改本地分支名字&#xff1a; git branch -m old-branch-name new-branch-name然后修改远程分支的名字&#xff1a; git push origin :old-branch-name new-branch-name将本地修改名字后的本地分支和远程分支进行关联&#xff08;关联后…

LeetCode209——长度最小的子数组

LeetCode209——长度最小的子数组 题目描述&#xff1a; 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, …, numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数…

基于单片机的温湿度检测及远程控制系统设计

目 录 引 言. 2 第一章 绪 论. 2 1.1 单片机简介 2 1.2 传感器简介 2 1.3 LCD液晶显示器简介 2 1.4 本设计的主要内容和目标 2 第二章 系统总体设计. 2 2.1 系统功能要求与技术指标 2 2.1.1 功能要求. 2 2.1.2 技术指标. 2 2.2 系统设计思路 2 2.3系统设计原则 2 2.4 系…

什么是React Router?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

“全数前进”媒体交流会在京举办

10月26日&#xff0c;北京市产业经济研究中心联合升哲科技&#xff08;SENSORO&#xff09;举办了以“全数前进”为题的媒体交流会。 会上&#xff0c;北京市产业经济研究中心副主任薛健为与会的媒体朋友介绍了AIoT智慧院落的建设情况&#xff0c;并阐述了北京市经信局在促进数…

pytorch 入门 (五)案例三:乳腺癌识别识别-VGG16实现

本文为&#x1f517;小白入门Pytorch内部限免文章 &#x1f368; 本文为&#x1f517;小白入门Pytorch中的学习记录博客&#x1f366; 参考文章&#xff1a;【小白入门Pytorch】乳腺癌识别&#x1f356; 原作者&#xff1a;K同学啊 在本案例中&#xff0c;我将带大家探索一下深…

c# .net linux ImageSharp+FastDFS+Base64上传图片,压缩图片大小,图像处理dcoker中使用也可以

.net 以前是用System.Drawing来处理图片&#xff0c;但是在dcoker 、linux上用不了 微软官方推荐用 1、SkiaSharp 调试了挺久,运行也OK 如果项目运行到docker里&#xff0c;需要NUGET安装SkiaSharp.NativeAssets.Linux.NoDependencies 2、ImageSharp 用起来这个方便一些,推…

PostgreSQL 认证方式

一、概述 客户端的身份验证是由配置文件控制的&#xff0c;配置文件为pg_hba.conf&#xff0c;存放位置在数据目录下&#xff08;show data_directory;&#xff09;。 在initdb初始化数据目录时&#xff0c;会生成一个默认的pg_hba.conf文件。也可以将该配置文件存放在其他地方…

uniapp:谷歌地图,实现地图展示,搜索功能,H5导航

页面展示 APP H5 谷歌地图功能记录,谷歌key申请相对复杂一些,主要需要一些国外的身份信息。 1、申请谷歌key 以下是申请谷歌地图 API 密钥的流程教程: 登录谷歌开发者控制台:打开浏览器,访问 Google Cloud Platform Console。 1、创建或选择项目:如果你还没有创建项目…

trucksim常见问题

一、Error: Unable to load .vs data from “D:\Users\Public\Documents\TruckSim2019.0 Data\Results\Run_e24aa2… LastRun.vs”.Reason for failure: Invalid character OxFFFFFFB2 in string"" on line 4.Would you like to continue receiving alerts of this t…

【Python机器学习】零基础掌握GaussianProcessRegressor高斯过程

如何更精确地预测未来的股票价格? 股票市场总是充满变数,投资者经常面临着如何更准确地预测股票价格的问题。传统的预测方法或许可以提供一些线索,但它们往往无法捕捉到市场的所有复杂性。 现代技术提供了一种更高级的预测方法:高斯过程回归(Gaussian Process Regressio…

华为云双十一服务器数据中心带宽全动态BGP和静态BGP区别

2023华为云双十一优惠活动中提供多款云服务器选择&#xff0c;需要注意的是&#xff1a;西南-贵阳一和华北-北京一数据中心是静态BGP带宽&#xff0c;其他数据中心配置全动态独享BGP带宽。 静态BGP和全动态BGP带宽有什么区别&#xff1f;全动态BGP网络线路可用性保障更高&…

Nginx内外网代理配置记录

一、背景 客户现场内网服务器不提供外网环境&#xff0c;仅在另一台服务器上提供外网访问权限&#xff0c;所以需要通过网络代理的方式&#xff0c;将内网服务器需要访问的外网请求代理到外网服务器去受理。 二、解决 如此流程需要用到两台服务器&#xff0c;所以对应的也需要…

【分享】7-Zip压缩包的密码可以取消吗?

7-Zip压缩包设置了“密码保护”&#xff0c;后面又不想要了&#xff0c;可以取消吗&#xff1f; 首先&#xff0c;我们要分两种情况来看&#xff0c;是记得密码&#xff0c;但不想每次打开压缩包都要输入密码&#xff0c;所以想取消密码&#xff0c;还是把密码忘记了所以想取消…

搜维尔科技:【应用】配备MTi-3的轻便型ROV,在水下进行地理标记视觉检测

部署潜水员进行水下摄像&#xff0c;不仅难度高而且费用昂贵&#xff0c;需要受过潜水和摄像两方面培训的专业人员来进行。但有些水下作业任务例如拍摄海底管道内部的照片&#xff0c;由于人员无法进入或危险度高的原因&#xff0c;无法由潜水员完成。 如今&#xff0c;俄罗…

stm32通过AT指令与esp8622通信

stm32通过AT指令与esp8622通信 文章目录 stm32通过AT指令与esp8622通信1.tcp通信2.mqtt通信 1.tcp通信 ATCWMODE1 设置为STA模式ATCWJAP_DEF"langtaotech","langtaotechXXX"ATCIPSTA? 查询ipATCIPMUX0 设置单连接ATCIPSTART"TCP","19…

openEuler 22.03 LTS 环境使用 Docker Compose 一键部署 JumpServer (all-in-one 模式)

环境回顾 上一篇文章中&#xff0c;我们讲解了 openEuler 22.03 LTS 安装 Docker CE 和 Dcoker Compose&#xff0c;部署的软件环境版本分别如下&#xff1a; OS 系统&#xff1a;openEuler 22.03 LTS(openEuler-22.03-LTS-x86_64-dvd.iso)Docker Engine&#xff1a;Docker C…

Linux--进程等待

1.什么是进程等待 1.通过系统调用wait/waitid,来对子进程进行进行检测和回收的功能。 2.为什么有进程等待 1.对于每个进程来说&#xff0c;如果子进程终止&#xff0c;父进程没有停止&#xff0c;就会形成僵尸进程&#xff0c;导致内存泄露&#xff0c;为了防止僵尸进程的形成…