当前位置: 首页 > news >正文

Spring定时任务+webSocket实现定时给指定用户发送消息

生命无罪,健康万岁,我是laity。

我曾七次鄙视自己的灵魂:

第一次,当它本可进取时,却故作谦卑;

第二次,当它在空虚时,用爱欲来填充;

第三次,在困难和容易之间,它选择了容易;

第四次,它犯了错,却借由别人也会犯错来宽慰自己;

第五次,它自由软弱,却把它认为是生命的坚韧;

第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;

第七次,它侧身于生活的污泥中,虽不甘心,却又畏首畏尾。

Spring定时任务 + webSocket实现定时给指定用户发送消息:类似于消息中心;
相信有需求的小伙伴读此文章可以有一定的帮助或者思路

逻辑思路

在做这个业务的时候也遇到了很多的坑,但是现在我帮你踩完了。

  • 使用Spring定时任务框架(@Scheduled)和websocket网络通信协议框架来进行定时向指定用户的客户端推送消息;
  • 编写并设置webSocketConfig配置文件(百度上的常规配置即可);
  • 基于@ServerEndpoint(value = “/websocket/{userId}”)注解标记路径并让前端进行连接服务器端
  • SocketServer中都是使用ConcurrentHashMap集合来进行接收和存储
    • 它是Java 5中引入了ConcurrentHashMap这个并发集合类
    • ConcurrentHashMap通过将一个大的数据结构分解为多个小的数据结构,然后对每个小的数据结构加锁来实现线程安全。这种方式称为分段锁。分段锁我记得是有问题的:性能不好(什么原因导致的有时间去查:查询热点数据),在JDK8时弃用后采用CAS和synchronized组合的方式来保证并发安全。
    • Java 8中的ConcurrentHashMap在内部实现上采用了数组+链表+红黑树的数据结构,也使得其具有较好的查找和遍历性能
    • 有兴趣的小伙伴可以去阅读下:一文看懂 jdk8 中的 ConcurrentHashMap,个人感觉写的很不错。
  • 集合clients用于记录连接的客户端(session)、key为生成的uuid;集合conn用于存放clients数据,key为用户唯一标识(id)。同时也充分使用了websocket的生命周期进行资源释放等操作。
  • 自定义方法sendMessageByUserId(Integer userId, String message),基于userId获取conn中的set集合,基于iterator进行集合迭代,使用hasNext()返回的Boolean进行判断是否继续进行循环遍历,.next()获取其中uuid数据(指针移动),通过uuid取出存在clients集合中的session进行发送数据
  • ===========================================以上是WebSocket实现思路
  • 基于@EnableScheduling注解开启定时任务,基于@Scheduled(cron = “……”)注解和cron表达式进行任务执行。
  • 业务逻辑:不用的人业务不同,所以这里我省略掉了我的业务逻辑,有需求的小伙伴可以私信我进一步沟通;
  • 调用websocket的sendMessageUserById()方法来进行返回数据;

代码实现

依赖

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

webSocketServer.java

/*** @Project: JavaLaity* @Description: 服务端*/
@Slf4j
@Component
@ServerEndpoint(value = "/websocket/{userId}")
public class WebSocketServer {/*** concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象,若要实现服务器端与单一客户端通信的话,可以使用Map来存放,其中key可以为用户标识*/private static final CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();// public static ThreadLocal<SysUser> clientUser = new ThreadLocal<>();/*** 用于记录连接的客户端 (sid,session)*/public static Map<String, Session> clients = new ConcurrentHashMap<>();/*** 基于userId关联sid(用于解决同一用户id,在多个web端连接的问题)*/public static Map<Integer, Set<String>> conn = new ConcurrentHashMap<>();/*** 与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;private String sid = null;// 当WebSocket连接建立时,会调用标注有@OnOpen的方法@OnOpenpublic void onOpen(Session session, @PathParam("userId") Integer userId) {System.out.println("WebSocket opened: " + session.getId());this.session = session;this.sid = UUID.randomUUID().toString();clients.put(this.sid, session);Set<String> clientSet = conn.get(userId);if (clientSet == null) {clientSet = new HashSet<>();conn.put(userId, clientSet);}clientSet.add(this.sid);// 加入set中webSocketSet.add(this);}// 给所有当前连接的用户发送消息public void sendMessageAll(String message) {try {if (webSocketSet.size() != 0) {for (WebSocketServer item : webSocketSet) {if (item != null) {// 同步锁,解决多线程下发送消息异常关闭// 避免高并发下多处频繁调用sendMessage()方法发送消息而导致的webSocket挂掉,我们在sendMessage这个方法里面加入同步锁,// 锁住session,这样就能保障webSocket有条不絮的向前端推送消息synchronized (item.session) {item.session.getBasicRemote().sendText(message);}}}}} catch (IOException e) {e.printStackTrace();}}/*** 根据用户ID发送给某一个用户*/public void sendMessageByUserId(Integer userId, String message) {if (userId != null) {Set<String> clientSet = conn.get(userId);if (clientSet != null) {Iterator<String> iterator = clientSet.iterator();while (iterator.hasNext()) {String sid = iterator.next();Session session = clients.get(sid);if (session != null) {try {session.getBasicRemote().sendText(message);} catch (IOException e) {e.printStackTrace();}}}}}}// TODO 当浏览器关闭触发该事件@OnClosepublic void OnClose(Session session) {log.info(this.sid + "断开连接");clients.remove(this.sid);webSocketSet.remove(this);}// 当收到客户端(前端)发送的消息时,会调用@OnMessage方法@OnMessagepublic void OnMessage(String message, Session session) {System.out.println("Received message:" + message);}// TODO 发生错误时调用@OnErrorpublic void OnError(Session session, Throwable error) {System.out.println("发生错误!");error.printStackTrace();}
}

AideScheduleTask.java

/*** @Project: JavaLaity* @Description: 实现业务逻辑的位置*/
@Configuration
public class AideScheduleTask {@AutowiredWebSocketServer webSocketServer;@AutowiredRedisCache redisCache;// 测试@Scheduled(cron = "0/20 * * * * ?")private void testTask() throws Exception {// 使用ThreadLocal - 哈哈哈,弃用了,行不通!。// TODO: 你需要的业务逻辑SysUser sysUser = new SysUser().setId(1)if (sysUser.getId() != null) {webSocketServer.sendMessageByUserId(sysUser.getId(), "这个是老大可以发:role = ");}}
}

注意问题

  • 在写Corn表达式时需要注意的点:

    • 外国对于周几的定义和中国是不一样的,中国1-7对应周一到周日,外国的1-7对应的是周日到周六
    • 国内:1-7 MON,TUE,WED,THU,FRI,SAT,SUN
    • 国外:1-7 SUN,MON,TUE,WED,THU,FRI,SAT √
  • 个人认为有性能问题以及数据库压力的大问题,这里本人就只是简单的分享下设计思路,不同业务需求,实现策略不同。也有肯定公司不在乎这种性能问题。

愿每个人都能遵循自己的时钟,做不后悔的选择。我是Laity,正在前行的Laity。

相关文章:

Spring定时任务+webSocket实现定时给指定用户发送消息

生命无罪&#xff0c;健康万岁&#xff0c;我是laity。 我曾七次鄙视自己的灵魂&#xff1a; 第一次&#xff0c;当它本可进取时&#xff0c;却故作谦卑&#xff1b; 第二次&#xff0c;当它在空虚时&#xff0c;用爱欲来填充&#xff1b; 第三次&#xff0c;在困难和容易之…...

C语言学习笔记(六):数组(1)

0,问题的引入 怎么保存一个学生的成绩 float a; 怎么保存一个班&#xff08;10人&#xff09;的学生的成绩 float a,b,c,d......; float a1,a2,a3,........; 这样太麻烦了 -》“数组” 1&#xff0c;数组 什么是数组&#xff…...

apk反编译修改教程系列-----修改apk中的图片 任意更换apk桌面图片【三】

往期教程&#xff1a; apk反编译修改教程系列-----修改apk应用名称 任意修改名称 签名【一】 apk反编译修改教程系列-----任意修改apk版本号 版本名 防止自动更新【二】 这次实例演示下如何更换apk安装后的桌面图标图片。其实这个步骤前面我有一个教程贴。这次针对步骤做个补…...

【IO面试题 五】、 Serializable接口为什么需要定义serialVersionUID变量?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a; Serializable接口为什么…...

san.js源码解读之模版解析(parseTemplate)篇——readIdent函数

一、源码分析 /*** 读取ident* 这里的 ident 指标识符(identifier)&#xff0c;也就是通常意义上的变量名* 这里默认的变量名规则为&#xff1a;由美元符号($)、数字、字母或者下划线(_)构成的字符串** inner* param {Walker} walker 源码读取对象* return {string}*/ functio…...

【excel技巧】excel单元格内如何换行?

Excel表格&#xff0c;在制作完成之后&#xff0c;在输入数据的时候&#xff0c;总是会遇到内容长度太长导致无法全部显示或者破坏表格整体格式。几天分享4个单元格换行的方法给大家。 方法一&#xff1a; 首先我们先介绍一个&#xff0c;通过调整列宽的方式来达到显示全部内…...

SSD1306 oled显示屏的驱动SPI接口

有IIC接口 和SPI接口 还有8080,6080接口等 arduino SPI接口 直接使用u8g2库实现 //U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock*/ 13, /* data*/ 11, /* cs*/ 10, /* dc*/ 9, /* reset*/ 8); asrpro(SPI接口按下方修改&#xff0c;IIC接口官方有驱动&…...

RSA:基于小加密指数的攻击方式与思维技巧

目录 目录 目录 零、前言 一、小加密指数爆破 [FSCTF]RSA签到 思路&#xff1a; 二、基于小加密指数的有限域开根 [NCTF 2019]easyRSA 思路&#xff1a; 三、基于小加密指数的CRT [0CTF 2016] rsa 思路&#xff1a; 零、前言 最近&#xff0c;发现自己做题思路比较…...

Vuex 和 Redux 的区别?

Vuex和Redux是两个流行的JavaScript状态管理库&#xff0c;它们有一些相似之处&#xff0c;但也有一些区别。 区别&#xff1a; 语言&#xff1a;Vuex是为Vue.js框架设计的&#xff0c;而Redux是一个独立的库&#xff0c;可用于多种JavaScript框架或库。生态系统&#xff1a;…...

软考高级系统架构师冲关预测

[ – 2023年10月27日 – ] 去年11月通过了软考高级系统架构师的考试&#xff0c;原本想立即分享下过关的总结回顾&#xff0c;但是随着软考新版大纲及教程的发布&#xff0c;也意味着题目及内容的复盘总结经验便不那么适用。在即将迎来今年的软考高架的时候&#xff0c;想着透…...

华为实验基础(1):交换机基础

一、交换机的分类 1、 根据交换方式划分&#xff1a; 存储转发式交换 (Store and Forward) 直通式交换 (Cut-through) 碎片过滤式交换 (Fragment Free) 2、 根据交换的协议层划分&#xff1a; 第二层交换&#xff1a;根据 MAC 地址进行交换 第三层交换&…...

bitlocker 加密锁定的固态硬盘,更换到别的电脑上,怎么把原密钥写进新电脑TPM芯片内,开启无需手动填密钥

环境: Win11 专业版 联想E14笔记本 512G ssd 问题描述: 一台笔记本因充电故障,需要拿去维修,不想重装系统,将bitlocker 加密锁定的固态硬盘拆下更换到别的笔记本电脑上,现在开机要手动填密钥,怎么把原密钥写进新电脑TPM芯片内,开启无需手动填密钥和之前那台电脑一…...

C语言之错误处理

在C语言中&#xff0c;错误处理是一种重要的编程技术&#xff0c;用于处理程序运行过程中可能出现的错误情况。C语言提供了几种处理错误的机制&#xff0c;包括返回错误码、使用全局变量、异常处理等。 1、返回错误码&#xff1a; 在函数执行过程中&#xff0c;如果发生错误&a…...

IO流框架,缓冲流

一.缓冲流有什么优点 Java中的缓冲流&#xff08;Buffered Stream&#xff09;具有以下优势&#xff1a; 提高效率&#xff1a;缓冲流通过在内存中缓存一部分数据&#xff0c;减少了直接从内存到磁盘或从磁盘到内存的频繁IO操作&#xff0c;从而提高了读写效率。缓冲区大小调整…...

数字音频工作站软件 Ableton Live 11 mac中文软件特点与功能

Ableton Live 11 mac是一款数字音频工作站软件&#xff0c;用于音乐制作、录音、混音和现场演出。它由Ableton公司开发&#xff0c;是一款极其流行的音乐制作软件之一。 Ableton Live 11 mac软件特点和功能 Comping功能&#xff1a;Live 11增加了Comping功能&#xff0c;允许用…...

【PyQt】调整子控件的层级以调整绘制的先后顺序

简述 qt中貌似没有直接设置z序的函数&#xff0c;但对应的有其他调整z序的方法&#xff1a; QWidget.raise_()&#xff1a;置顶 QWidget.lower()&#xff1a;置底 QWidget.stackUnder(wid)&#xff1a;置于指定控件之下 其中关键函数是QWidget.stackUnder(wid)&#xff0c;利…...

js中数组的相关方法

引言&#xff1a; 数组&#xff08;Array&#xff09;是有序的元素序列。 [1]若将有限个类型相同的变量的集合命名&#xff0c;那么这个名称为数组名。组成数组的各个变量称为数组的分量&#xff0c;也称为数组的元素&#xff0c;有时也称为下标变量 方法&#xff1a; push()…...

深入浅出排序算法之直接插入排序(拓展:折半插入排序)

目录 1. 图示解析 2. 原理解析 3. 代码实现 4. 性能分析 5. 折半插入排序&#xff08;拓展&#xff09; 直接插入排序和选择排序的第一趟就是第一个关键字 &#xff01; 1. 图示解析 2. 原理解析 整个区间被分为&#xff1a;① 有序区间&#xff1b;② 无序区间 每次选…...

皮卡丘RCE靶场通关攻略

皮卡丘RCE靶场通关攻略 文章目录 皮卡丘RCE靶场通关攻略RCE(remote command/code execute)概述远程系统命令执行启动环境漏洞练习第一关exec "ping"第二关 exec "eval" RCE(remote command/code execute)概述 RCE漏洞&#xff0c;可以让攻击者直接向后台服…...

Mysql binlog日志功能使用,简单易懂

一、简单了解binlog MySQL的二进制日志binlog可以说是MySQL最重要的日志&#xff0c;它记录了所有的DDL和DML语句&#xff08;除了数据查询语句select&#xff09;。因此binlog日志文件我们用cat等查看文件的命令是打不开的&#xff0c;但是mysql提供了专门看binlog文件的命令…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】

1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件&#xff08;System Property Definition File&#xff09;&#xff0c;用于声明和管理 Bluetooth 模块相…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)

考察一般的三次多项式&#xff0c;以r为参数&#xff1a; p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]&#xff1b; 此多项式的根为&#xff1a; 尽管看起来这个多项式是特殊的&#xff0c;其实一般的三次多项式都是可以通过线性变换化为这个形式…...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...