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

Springboot整合SSE实现实时消息推送

SSE详细介绍传送门:SSE实时消息推送

简单描述一下SSE推送在实际项目中应用的常见场景

1,项目页面中有消息通知板块,当信息有变化时,只有手动刷新页面,才会看到最新的数据,这里可以采用SSE技术实时推送最新消息
.
2,大屏数据,这种场景是可以用SSE进行推送,但是需要注意的是SSE是单向的服务端向前端推数据,一般要求的是大屏基本没有查询框条件这种,比较合适。

注意点:如果对于实时数据要求很高并且连接要求做到安全稳定,这里推荐用WebSocket,一般来说对于数据量小,并发连接不是很高要求的情况下,SSE足够,用而且SSE的配置对于前后端都比较简单,但是WebSocket的配置对于后端来说需要花费比较多的时间去完善,而且WebSocket是比较消耗服务器资源和网络带宽资源的,另外一个,如果项目中运维配置了代理服务器的话,可能代理服务器也要配置一些支持WebSocket的属性,总体来说WebSocket配置的位置比较多,容易出现各种坑bug,这里注意一下即可。

话不多说,总结一下Springboot整合SSE需要的步骤如下:

1,编写SSE的服务类:主要包括建立连接、关闭连接、异常连接、心跳检测、推送消息等
.
2,controller层写入SSE连接和关闭接口
.
3,在所需要的业务模块中直接调用SSE服务类中推送消息功能即可

SSE步骤简单,无需导入maven依赖,踩坑bug少,主要是SSE内部支持断线重连,爽爽爽

1,SSE服务类

package com.bosera.salesioc.home.sse;
import com.alibaba.fastjson.JSONObject;
import com.bosera.salesioc.domain.home.vo.MessageVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;@Slf4j
@Component
public class SseEmitterServer{private static final ConcurrentHashMap<String, Map<String,SseEmitter>> sseEmitterPool = new ConcurrentHashMap<>();private static final ConcurrentHashMap<String, Timer>  headerPool = new ConcurrentHashMap<>();public  static ConcurrentHashMap<String, Map<String, SseEmitter>> getSseEmitterPool(){return sseEmitterPool;}/*** 建立连接*/public  SseEmitter connect(String  userCode, String userId){log.info("******************开始建立连接*****************");//设置超时时间,0表示不过期,默认是30秒,超过时间未完成会抛出异常SseEmitter sseemitter = new SseEmitter(0L);//注册回调sseemitter.onCompletion(completionCallBack(userCode,userId));sseemitter.onError(errorCallBack(userCode,userId));sseemitter.onTimeout(timeoutCallBack(userCode,userId));sseEmitterPool.computeIfAbsent(userCode, k -> new ConcurrentHashMap<>()).put(userId, sseemitter);// 开启心跳活跃startHeartbeat(sseemitter,userId);return sseemitter;}/*** 关闭当前连接*/public void complete(String userCode, String userId){Map<String, SseEmitter> map = sseEmitterPool.get(userCode);if (map != null)map.get(userId).complete();}/*** 关闭所有连接*/public void completeAll(){if(!sseEmitterPool.isEmpty()){for (Map.Entry<String, Map<String, SseEmitter>> entry : sseEmitterPool.entrySet()) {Map<String, SseEmitter> userIdMap = entry.getValue();if(!userIdMap.isEmpty()){for (Map.Entry<String, SseEmitter> userIdEntry : userIdMap.entrySet()) {userIdEntry.getValue().complete();}}}sseEmitterPool.clear();}}private  Runnable completionCallBack(String userCode, String userId) {return () -> {removeUser(userCode,userId);log.info("{}结束连接:{}",userCode,userId);};}private  Runnable timeoutCallBack(String userCode, String userId){return ()->{removeUser(userCode,userId);log.error("{}连接超時:{}",userCode,userId);};}private  Consumer<Throwable> errorCallBack(String userCode, String userId){return throwable -> {log.error("{}连接异常:{}",userCode,userId);stopHeartbeat(userId);};}/*** 推送消息*/public  void sendMessage(String userCode, MessageVO message){Map<String, SseEmitter> map = sseEmitterPool.get(userCode);if (map != null) {for (Map.Entry<String, SseEmitter> entry : map.entrySet()) {try {// 发送事件entry.getValue().send(JSONObject.toJSONString(message));}catch (Exception e){log.error("{}连接信息:{}, 错误消息:{}",userCode,entry.getKey(),e.getMessage());}}}}private void removeUser(String userCode, String userId){try {Map<String, SseEmitter> map = sseEmitterPool.get(userCode);if (map != null) {map.remove(userId);// 如果该用户的所有会话都已关闭,则移除整个映射if (map.isEmpty())sseEmitterPool.remove(userCode);}// 关闭心跳stopHeartbeat(userId);}catch (Exception e){log.error("关闭连接异常{}",e.getMessage());}}/*** 开启心跳*/public void startHeartbeat(SseEmitter sseemitter, String userId) {Timer heartbeatTimer = new Timer();headerPool.put(userId,heartbeatTimer);heartbeatTimer.schedule(new TimerTask() {@Overridepublic void run() {if (Objects.nonNull(headerPool.get(userId))) {// 发送心跳:保持长连接try {sseemitter.send("connect active");} catch (Exception e) {log.error("connect active error");}}}}, 25000, 25000);}/*** 关闭心跳* @param userId*/public void stopHeartbeat(String userId) {Timer timer = headerPool.get(userId);if (timer!= null)timer.cancel();headerPool.remove(userId);}
}

推送的消息可以统一定义一个类来封装信息
2,消息推送响应体

/*** @Author xiaozq* @Date 2024/2/21* @Description: 消息推送响应体*/
public class MessageVO<T> {// 主题:不同位置推送的内容不同private String topic;// 推送消息private T data;public void setTopic(String topic) {this.topic = topic;}public void setData(T data) {this.data = data;}public String getTopic() {return topic;}public T getData() {return data;}
}

3,controller层编写连接和关闭接口

@RestController
@RequestMapping("/sse")
@Slf4j
public class SSEController{@Autowiredprivate SseEmitterServer sseEmitterServer;/*** 用于创建连接*/@GetMapping(value = "/connect/{userCode}/{userId}",produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter connect(@PathVariable("userCode") String userCode, @PathVariable("userId") String userId){return sseEmitterServer.connect(userCode, userId);}/*** 关闭连接*/@GetMapping(value = "/close/{userCode}/{userId}")public void close(@PathVariable("userCode") String userCode,@PathVariable("userId") String userId ) {sseEmitterServer.complete(userCode, userId);}}

4,业务中实际应用:推送消息

@Autowired
SseInfoService  sseInfoService;
private void handlerMessageInform() {ConcurrentHashMap<String, Map<String, SseEmitter>> sessionPool = SseEmitterServer.getSseEmitterPool();for (Map.Entry<String, Map<String, SseEmitter>> entry : sessionPool.entrySet()) {// 封装消息MessageVO<List<MessageNotificationVO>> messageVO = new MessageVO();messageVO.setTopic(TopicTypeEnum.MESSAGE_INFORM.getTopic());messageVO.setData(messageService.getMessageList(request));// 推送消息sseEmitterServer.sendMessage(entry.getKey(), messageVO);}}

在实践过程中存在的问题:

1,报错504 gateway timeout:这里主要是原项目中配置了响应超时时间,不支持长连接,这里的做法是心跳活跃,保证连接不会被掐断,可以写一个定时任务,每天晚上定时去关闭所有连接,第二天用新的连接,这样可以尽量保证内存的连接数不会过多占用内存,因为夜深人静的时候谁还会打开web项目工作啊,哈哈太卷了吧,所以把时间定在晚上最好。
.
如果项目是集群模式的话,上述代码就得改造了,建议是把消息推送这块单独抽出一个微服务模块来,这样子保证所有的连接统一走单独的一个服务,因为SSE不是双向的,既然是单项连接,与后端集群下的其中一个服务建立连接产生的IO流这是只属于当前服务的本地IO,关闭IO只能连接对应的这台服务去关闭,否则关闭失效。总之,考虑的点还有很多,一般情况下,SSE够用啦

总体来说,应用是比较简单的,涉及到消息实时推送相关的业务,可以尝试SSE

相关文章:

Springboot整合SSE实现实时消息推送

SSE详细介绍传送门&#xff1a;SSE实时消息推送 简单描述一下SSE推送在实际项目中应用的常见场景 1&#xff0c;项目页面中有消息通知板块&#xff0c;当信息有变化时&#xff0c;只有手动刷新页面&#xff0c;才会看到最新的数据&#xff0c;这里可以采用SSE技术实时推送最新…...

在pytorch中利用GPU训练神经网络时代码的执行顺序并提高训练效率

在pytorch中利用GPU训练神经网络时代码的执行顺序并提高训练效率 在 PyTorch 中&#xff0c;大多数操作在 GPU 上默认是异步执行的&#xff0c;但这并不意味着它们是并行执行的。要理解代码是同步还是异步执行&#xff0c;以及是串行还是并行执行&#xff0c;我们需要考虑几个…...

vue3学习

距离vue2学习已经一年度了&#xff0c;现在开始vue3的学习。 一、webpack &#xff08;1&#xff09;创建列表隔行变色项目及webpack使用&#xff1a; 新建项目空白目录&#xff0c;并运行npm init -y命令&#xff0c;初始化包管理配置文件package.json&#xff1b; 新建sr…...

毫秒生成的时间戳如何转化成东八区具体时间

假设现在有一个时间是1709101071419L 后端代码实现 Java代码&#xff08;东八区时间&#xff09; 在Java代码中&#xff0c;我们将时区从UTC调整为东八区&#xff08;UTC8&#xff09;&#xff1a; import java.time.Instant; import java.time.ZoneId; import java.time.Z…...

02. Nginx入门-Nginx安装

Nginx安装 yum安装 编辑yum环境 cat > /etc/yum.repos.d/nginx.repo << EOF [nginx-stable] namenginx stable repo baseurlhttp://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck1 enabled1 gpgkeyhttps://nginx.org/keys/nginx_signing.key module_…...

leetcode73. 矩阵置零

链接见&#xff1a;https://leetcode.cn/problems/set-matrix-zeroes/description/ 题目描述 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 AC代码 class Solution { public:void setZeroes(vec…...

【中间件】RabbitMQ入门

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;中间件 ⛺️稳中求进&#xff0c;晒太阳 MQ的优劣&#xff1a; 优势 应用解耦&#xff1a;提升了系统容错性和可维护性异步提速&#xff1a;提升用户体验和系统吞吐量消峰填谷&#xff1…...

rtt的io设备框架面向对象学习-电阻屏LCD设备

目录 1.8080通信的电阻屏LCD设备1.1 构造流程1.2 使用2.i2c和spi通信的电阻屏LCD 电阻屏LCD通信接口有支持I2c、SPI和8080通信接口的。 1.8080通信的电阻屏LCD设备 lcd这块不像其他设备类&#xff0c;rtt没有实现的设备驱动框架层&#xff0c;那么是在驱动层直接实现的。 以…...

商城免费搭建之java商城 java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…...

蓝桥杯刷题--python-16

562. 壁画 - AcWing题库 Tint(input()) j1 while(j<T): N int(input()) ainput() s [0]*(N1) # 求前戳和 for i in range(1, N 1): s[i] int(a[i-1]) s[i - 1] # 枚举 # 区间 max_ float(-inf) k (N 2 - 1) // 2 for i in …...

闰年计算中的计算机Bug

不知道你有没有看过凯瑟琳泽塔琼斯主演的《偷天陷阱》&#xff0c;里面主题思想是用银行结算系统的千年虫bug&#xff0c;精心设计&#xff0c;盗取银行几十亿的精彩动作片。所谓2000 年千禧年的千年虫&#xff0c;其实就是计算机计算闰年的bug。 这个闰年计算的历史源远流长&…...

python水表识别图像识别深度学习 CNN

python水表识别&#xff0c;图像识别深度学习 CNN&#xff0c;Opencv,Keras 重点&#xff1a;项目和文档是本人近期原创所作&#xff01;程序可以将水表图片里面的数据进行深度学习&#xff0c;提取相关信息训练&#xff0c;lw1.3万字重复15%&#xff0c;可以直接上交那种&…...

Java对接快递100实时快递单号查询API接口

目录 1.引入依赖 2.定义配置信息 3.模块结构 4.Controller 5.Service实现类 6.返回数据dto以及dto中的数据dto 7.测试运行 今天也是接到了这个任务&#xff0c;官网有小demo&#xff0c;可以下载下来参考test中代码 官方文档地址&#xff1a; 实时快递查询接口技术文档…...

Redis常见的15个【坑】,避坑指南

一、常见命令 1.1 过期时间意外丢失 原因&#xff1a; SET命令如果不设置过期时间&#xff0c;那么Redis会自动【擦除】这个key的过期时间 1.2 DEL命令阻塞redis key是String类型时&#xff0c;DEL时间复杂度是O(1)key是List/Hash/Set/ZSet类型&#xff0c;DEL时间复杂度是…...

04. Nginx入门-Nginx WEB模块

测试环境 此处使用的yum安装的Nginx路径。 此处域名均在本地配置hosts。 主配置文件 路径&#xff1a;/etc/nginx/nginx.conf user nginx; worker_processes auto;error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid;events {worker_connection…...

Python在信息安全领域中具有重要的作用

Python在信息安全领域中具有重要的作用。下面是几个方面的说明&#xff1a; 网络安全&#xff1a;Python提供了一系列用于网络安全的库和工具&#xff0c;例如Scapy、Nmap等。这些工具可以应用于漏洞扫描、网络流量分析、数据包嗅探等操作&#xff0c;帮助检测和防御网络攻击。…...

Linux 定时备份文件到另一台服务器

1. 需求 用户要求将 Tomcat 的日志文件定时备份到另一台服务器。同事给我提供了一个写好的 java 框架&#xff0c;但实在不想给用户再维护另一个服务了&#xff0c;所以另寻他法。 2. 问题 使用 scp 等跨服务器传输命令时需要手动输入用户名的密码才可进行文件传输&#xff…...

C++输入输出(I\O)

我们知道C是由C语言发展而来的&#xff0c;几乎完全兼容C语言&#xff0c;换句话说&#xff0c;你可以在C里面编译C语言代码。如下图: C语言是面向过程的语言&#xff0c;C在C语言之上增加了面向对象以及泛型编程机制&#xff0c;因此C更适合中大型程序的开发&#xff0c;然而C…...

基本设计模式

单例模式 ES5 function Duck1(name:string){this.namenamethis.instancenull }Duck1.prototype.getNamefunction(){console.log(this.name) }Duck1.getInstancefunction(name:string){if(!this.instance){this.instance new Duck1(name)} } const aDuck1.getInstance(a) const…...

双通道音频功率放大电路,外接元件少, 通道分离性好,3V 的低压下可正常使用——D2025

D2025 为立体声音频功率放大集成电路&#xff0c;适用于各类袖珍或便携式立体声 收录机中作功率放放大器。 D2025 采用 DIP16 封装形式。 主要特点&#xff1a;  适用于立体声或 BTL 工作模式  外接元件少  通道分离性好  电源电压范围宽&#xff08;3V~12V &#xff…...

【Linux驱动开发】第11天:设备树(Device Tree)超详细全解:从诞生背景到工作原理

一、设备树的诞生背景&#xff1a;传统驱动的致命痛点 在设备树出现之前&#xff08;Linux 3.0之前&#xff09;&#xff0c;Linux内核采用硬编码的方式描述所有硬件信息。这意味着&#xff1a; 每一个开发板的寄存器地址、中断号、GPIO号&#xff0c;都直接写死在驱动代码里换…...

手写NumPy版RBM:从能量函数到吉布斯采样的可调试实现

1. 项目概述&#xff1a;这不是又一个“RBM扫盲帖”&#xff0c;而是一次亲手拆解神经网络祖师爷级模型的实操复盘Restricted Boltzmann Machine&#xff08;受限玻尔兹曼机&#xff09;&#xff0c;简称RBM&#xff0c;不是教科书里那个被反复引用却没人真去跑通的抽象符号&am…...

2026年长沙美缝施工团队哪家强?专业之选等你来揭秘!

在长沙高端住宅、别墅装修领域&#xff0c;美缝施工是提升家居质感的关键环节。面对众多美缝施工团队&#xff0c;业主们常常不知如何选择。今天&#xff0c;我们就来揭秘2026年长沙值得信赖的美缝施工团队——长沙匠心徐师傅美缝团队&#xff0c;看看它有哪些独特的优势。一、…...

LLM处理半结构化数据,csv数据 :在序列化层对字段按熵分层路由——把每个低熵层一次性全局总结、把高熵 TEXT 用“质心+样例“做率最优覆盖、把寻址 α 显式落进 prompt

怎么给LLM 总结结论进行溯源 先搞清「寻址函数 α」是什么 L3 / L4 已经把 12 万条文本压成 8 类模式 + 几条原话证据。可这时候 LLM 看到的只是抽象论断: 「机型 X1C 的喷头堵塞,主要原因是耗材含水(占该类 18%)」 分析师马上会追问:“这 18% 具体是哪 5,200 条工单?给…...

Wireshark深度解析:HTTP/1.1协议层隐写与pcapng元数据取证

1. 这不是一次普通的数据包分析&#xff0c;而是一场“协议层藏宝游戏”Wireshark实战&#xff1a;解密http1.pcapng中的隐藏flag——光看标题&#xff0c;你可能以为这只是又一篇教你怎么点开Filter框、输http然后截图的入门教程。但实际操作中&#xff0c;我连续三次在http1.…...

实体门店低获客成本增长案例:3 人转介绍模型 + 消费返还机制落地分析

一、案例背景该门店为 60㎡社区夫妻店&#xff0c;位于成熟居住商圈&#xff0c;周边覆盖 3 个社区共 3000 余户居民。此前门店采用传统公域投放 线下发单的获客模式&#xff0c;获客成本偏高&#xff0c;用户留存与老客转介绍率存在较大提升空间。二、核心运营方案设计本次方…...

如何快速掌握《鸣潮》游戏模组开发:专业逆向工程与AES加密技术完整指南

如何快速掌握《鸣潮》游戏模组开发&#xff1a;专业逆向工程与AES加密技术完整指南 【免费下载链接】wuwa-mod Wuthering Waves pak mods 项目地址: https://gitcode.com/GitHub_Trending/wu/wuwa-mod WuWa-Mod是一个专门为热门游戏《鸣潮》&#xff08;Wuthering Waves…...

Redux Framework未来展望:探索v5版本的新特性和发展方向

Redux Framework未来展望&#xff1a;探索v5版本的新特性和发展方向 【免费下载链接】redux-framework Redux is a simple, truly extensible options framework for WordPress themes and plugins! 项目地址: https://gitcode.com/gh_mirrors/re/redux-framework Redux…...

为什么你的蓝晒图总像“褪色老照片”?3个被忽略的--stylize权重陷阱,今晚失效前速查

更多请点击&#xff1a; https://kaifayun.com 第一章&#xff1a;蓝晒法的光学本质与数字转译悖论 蓝晒法&#xff08;Cyanotype&#xff09;作为一种1842年诞生的古典摄影工艺&#xff0c;其核心依赖于铁盐在紫外光照射下发生的光还原反应&#xff1a;柠檬酸铁铵与铁氰化钾…...

Cadence SPB17.4 S032实战:用Room功能搞定多模块PCB的快速布局(附防闪退技巧)

Cadence SPB17.4 S032高效布局实战&#xff1a;Room功能在多模块PCB设计中的深度应用 面对包含80个子原理图的复杂PCB设计项目&#xff0c;传统的手工拖拽元件布局方式不仅效率低下&#xff0c;还容易因软件交互问题导致崩溃。Cadence Allegro的Room功能为解决这一痛点提供了系…...