SpringBoot+vue+SSE+Nginx实现消息实时推送

article/2025/8/2 16:39:33

一、背景

        项目中消息推送,简单的有短轮询、长轮询,还有SSE(Server-Sent Events)、以及最强大复杂的WebSocket。

        至于技术选型,SSE和WebSocket区别,网上有很多,我也不整理了,大佬的链接

《网页端IM通信技术快速入门:短轮询、长轮询、SSE、WebSocket》。

        其实实现很简单,写这篇文章的目的,主要是将处理过程中的一些问题,记录解决方案。

二、后端实现

        其实这里网上也是很多demo,我简写一点demo

1、引入依赖

这里需要用的下面依赖

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

2、sse工具类

package com.asiainfo.common.utils.sse;import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;/*** Des: sse工具类* Author: SiQiangMing 2025/5/28 15:50*/
@Slf4j
public class SseEmitterUtil {/*** 使用map对象,便于根据userId来获取对应的SseEmitter,或者放redis里面*/private final static Map<Long, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();/*** 用户创建sse链接* Author: SiQiangMing 2025/5/28 17:21* @param userId: 用户id* @return org.springframework.web.servlet.mvc.method.annotation.SseEmitter*/public static SseEmitter connect(Long userId) {// 设置超时时间,0表示不过期。默认30S,超时时间未完成会抛出异常:AsyncRequestTimeoutExceptionSseEmitter sseEmitter = new SseEmitter(0L);// 注册回调sseEmitter.onCompletion(completionCallBack(userId));sseEmitter.onError(errorCallBack(userId));sseEmitter.onTimeout(timeoutCallBack(userId));sseEmitterMap.put(userId, sseEmitter);log.info("----------------------------创建新的 SSE 连接,当前用户 {}, 连接总数 {}", userId, sseEmitterMap.size());return sseEmitter;}/*** 给制定用户发送消息* Author: SiQiangMing 2025/5/28 17:21* @param userId: 用户id* @param sseMessage: 消息* @return void*/public static void sendMessage(Long userId, String sseMessage) {if (sseEmitterMap.containsKey(userId)) {try {sseEmitterMap.get(userId).send(sseMessage);log.info("----------------------------用户 {} 推送消息 {}", userId, sseMessage);} catch (IOException e) {log.error("----------------------------用户 {} 推送消息异常", userId);disconnect(userId);}} else {log.error("----------------------------消息推送 用户 {} 不存在,链接总数 {}", userId, sseEmitterMap.size());}}/*** 判断用户是否存在sse链接* Author: SiQiangMing 2025/5/29 10:02* @param userId:* @return boolean*/public static boolean checkSseExist(Long userId) {if (userId == null) {return false;}return sseEmitterMap.containsKey(userId);}/*** 群发所有人,这里用来测试异常的排除链接* Author: SiQiangMing 2025/5/28 17:20* @param message: 消息* @return void*/public static void batchSendMessage(String message) {sseEmitterMap.forEach((k, v) -> {try {v.send(message, MediaType.APPLICATION_JSON);} catch (IOException e) {log.error("----------------------------用户 {} 推送异常", k);disconnect(k);}});}/*** 移除用户连接* Author: SiQiangMing 2025/5/28 17:20* @param userId: 用户id* @return void*/public static void disconnect(Long userId) {if (sseEmitterMap.containsKey(userId)) {sseEmitterMap.get(userId).complete();sseEmitterMap.remove(userId);log.info("----------------------------移除用户 {}, 剩余连接 {}", userId, sseEmitterMap.size());} else {log.error("-----------------------------移除用户 {} 已被移除,剩余连接 {}", userId, sseEmitterMap.size());}}/*** 结束回调* Author: SiQiangMing 2025/5/28 17:19* @param userId: 用户id* @return java.lang.Runnable*/private static Runnable completionCallBack(Long userId) {return () -> {log.info("----------------------------用户 {} 结束连接", userId);};}/*** 超时回调* Author: SiQiangMing 2025/5/28 17:20* @param userId: 用户id* @return java.lang.Runnable*/private static Runnable timeoutCallBack(Long userId) {return () -> {log.error("----------------------------用户 {} 连接超时", userId);disconnect(userId);};}/*** 异常回调* Author: SiQiangMing 2025/5/28 17:20* @param userId: 用户id* @return java.util.function.Consumer<java.lang.Throwable>*/private static Consumer<Throwable> errorCallBack(Long userId) {return throwable -> {log.error("----------------------------用户 {} 连接异常", userId);disconnect(userId);};}
}

三、前端

前端创建链接,请求后端的创建接口,注意页面销毁的时候,关闭sse链接

mounted() {// sse链接createEventSource() {// newthis.eventSource = new EventSource("后端的创建sse路径");// 收到消息this.eventSource.onmessage = (e) => {// 消息处理 e.data};// 异常处理this.eventSource.onerror = (error) => {console.error(error);};},
},
unmounted() {// 组件销毁时关闭 SSE 连接if (this.eventSource) {this.eventSource.close();}
},

3、创建sse

    /*** 处理客户端的连接请求* Author: SiQiangMing 2025/5/26 16:06* @return org.springframework.web.servlet.mvc.method.annotation.SseEmitter*/@GetMapping(value = "/xxx", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter xxx() {// 返回 SseEmitter 给客户端Long userId = SecurityUtils.getUserId();SseEmitter sseEmitter = SseEmitterUtil.connect(userId);// 可以直接带初始化信息返回SseEmitterUtil.sendMessage(userId, "消息");return sseEmitter;}

 四、nginx配置

        在这里遇到了一些问题,记录下解决方案。

1、在idea开发工具都正常,部署到生产环境后,sse后端能推送,前端没有收到消息。排查浏览器网络请求,nginx日志发现,客户端主动关闭了链接。

        在nginx.conf中增加配置

location /精准匹配sse创建路径 {add_header 'Cache-Control' 'no-cache'; //不缓存数据,每次请求时都会从服务器获取最新的内容proxy_set_header Connection ''; // 的作用是清除或覆盖 Connection头proxy_http_version 1.1;proxy_set_header Host $host; //确保后端服务器接收到的 Host 值与客户端原始请求的 Host 一致,或符合后端服务器的预期。proxy_pass http://xxx:port/sse创建路径;
}

2、SSE链接一分钟请求一次,频繁创建。

        在之前的配置中追加配置

location /精准匹配sse创建路径 {proxy_connect_timeout 3600s; // 解决1分钟重连,proxy_send_timeout 3600s; // 解决1分钟重连,proxy_read_timeout 3600s; // 解决1分钟重连,add_header 'Cache-Control' 'no-cache'; //不缓存数据,每次请求时都会从服务器获取最新的内容proxy_set_header Connection ''; // 的作用是清除或覆盖 Connection头proxy_http_version 1.1;proxy_set_header Host $host; //确保后端服务器接收到的 Host 值与客户端原始请求的 Host 一致,或符合后端服务器的预期。proxy_pass http://xxx:port/sse创建路径;
}

3、正常情况,链接保持了40分钟,还正常

4、并发数问题

        因为这里使用的http,所以版本是HTTP/1.1,同一个端口并发sse,只有6个,有两种解决方案,后期使用HTTP/2.0,默认100并发,满足要求。


http://www.hkcw.cn/article/wDEPdLWKZp.shtml

相关文章

软件测试的分类

为什么要软件测试分类呢&#xff1f; 软件测试是软件生命周期中的一个重要的环节&#xff0c;基本伴随着软件整个生命周期&#xff0c;对软件测试分类后&#xff0c;我们可以根据软件生命不同阶段&#xff0c;进行对应的测试&#xff0c;这样就有助于我们条理分明&#xff0c;…

<PLC><socket><西门子>基于西门子S7-1200PLC,实现手机与PLC通讯(通过websocket转接)

前言 本系列是关于PLC相关的博文,包括PLC编程、PLC与上位机通讯、PLC与下位驱动、仪器仪表等通讯、PLC指令解析等相关内容。 PLC品牌包括但不限于西门子、三菱等国外品牌,汇川、信捷等国内品牌。 除了PLC为主要内容外,PLC相关元器件如触摸屏(HMI)、交换机等工控产品,如…

实现一个免费可用的文生图的MCP Server

概述 文生图模型为使用 Cloudflare Worker AI 部署 Flux 模型&#xff0c;是参照视频https://www.bilibili.com/video/BV1UbkcYcE24/?spm_id_from333.337.search-card.all.click&vd_source9ca2da6b1848bc903db417c336f9cb6b的复现Cursor MCP Server实现是参照文章https:/…

Windows安装Miniconda

Windows安装miniconda 下载安装常用命令配置powershellVSCode配置虚拟环境 下载 进入官网 https://www.anaconda.com/download/success 下载windows版本的miniconda Miniconda3-latest-Windows-x86_64.exe 安装 一直点击下一步&#xff0c;可以选择安装路径 配置环境变量…

华为OD机试真题——二叉树中序遍历(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 200分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《二叉树中序遍历》: 目录 …

现代密码学 | 高级加密标准(AES)

接下来我们将讨论目前大多数计算机和硬件基础设施所使用的最重要的加密算法&#xff0c;例如高级加密标准&#xff08;AES&#xff09;、里弗斯特-沙米尔-阿德曼算法&#xff08;RSA&#xff09;、椭圆曲线加密&#xff08;ECC&#xff09;、基于格的加密、&#xff08;环&…

cocos creator资源管理器,资源动态加载和释放

cocos 2.4.11版本 cocos 动态加载的资源需要自己增加引用和减少引用计数 cc.Asset.addRef 和 cc.Asset.decRef 注意&#xff1a; 1.使用当前代码管理资源&#xff0c;要区分项目中的静态资源和动态资源&#xff0c;静态资源就是预制体或者场景中的资源&#xff0c;代码中动态…

认识scratch,scratch是什么,如何使用

scratch是图形编程&#xff0c;将编程简化为积木的堆叠和嵌套&#xff0c;无需手写代码&#xff0c;只需清晰的逻辑即可完成自己的代码设计。通过它可以制作简单的小游戏等。 如图所示&#xff0c;这个就是scratch打开的界面&#xff0c;整个界面分为左中右三个部分&#xff0c…

HarmonyOS实战:腾讯IM之聊天详情页面搭建(二)

前言 鸿蒙版本腾讯 IM 的聊天功能十分复杂&#xff0c;需要开发者手动实现整个聊天对话的业务代码&#xff0c;这对开发者来说是个不小的挑战。本篇文章先从最基础的聊天对话列表开始教你一步一步实现完整的聊天功能&#xff0c;建议点赞收藏&#xff01; 实现效果 先看本文…

IM系统的负载均衡

1.IM场景的负载均衡 2.方案总览 SDK层想要连接一个TCP网关或者WebSocket网关的方案 SDK单地址:在SDK中写死某个网关的IP或者域名,缺点是更换地址需要重新打包SDK SDK多地址:防止某一个地址嗝屁了写上多个地址用足保持高可用 暴露接口给客户端:SDK层访问接口动态获得地址 注…

动态规划之网格图模型(一)

文章目录 动态规划之网格图模型&#xff08;一&#xff09;LeetCode 64. 最小路径和思路Golang 代码 LeetCode 62. 不同路径思路Golang 代码 LeetCode 63. 不同路径 II思路Golang 代码 LeetCode 120. 三角形最小路径和思路Golang 代码 LeetCode 3393. 统计异或值为给定值的路径…

血糖监测仪解决方案推荐芯片-NRF52832/HS6621/OM6626

随着糖尿病患者数量的增加和人们健康意识的提升&#xff0c;血糖监测仪成为了日常健康管理的重要设备。市场对便携、智能且易于使用的血糖监测仪需求持续增长&#xff0c;而无线通信技术&#xff0c;尤其是蓝牙技术&#xff0c;已成为现代血糖监测仪的核心组件&#xff0c;提供…

基于Vite的前端自动化部署方案

&#x1f468; 作者简介&#xff1a;大家好&#xff0c;我是Taro&#xff0c;全栈领域创作者 ✒️ 个人主页&#xff1a;唐璜Taro &#x1f680; 支持我&#xff1a;点赞&#x1f44d;&#x1f4dd; 评论 ⭐️收藏 文章目录 前言一、主流解决方案二、了解SCP概念三、自动化部署…

PlankAssembly 笔记 DeepWiki 正交视图三维重建

manycore-research/PlankAssembly | DeepWiki PlankAssembly项目原理 这个项目是一个基于深度学习的3D重建系统&#xff0c;其核心原理是从三个正交视图的工程图纸中重建出3D形状的结构化程序表示。 核心技术原理 1. 问题定义 PlankAssembly旨在从三个正交视图的工程图纸中…

MQTT协议,EMQX部署,MQTTX安装学习

一、MQTT概述 1.什么是MQTT MQTT是一种基于“发布订阅“”模式的消息传输协议。 消息&#xff1a;设备和设备之间传输的数据&#xff0c;或者服务和服务之间要传输的数据。 协议&#xff1a;传输数据时所遵循的规范。 2.常见的通讯模式 &#xff08;1&#xff09;客户端-服…

多模态大语言模型arxiv论文略读(101)

ML-Mamba: Efficient Multi-Modal Large Language Model Utilizing Mamba-2 ➡️ 论文标题&#xff1a;ML-Mamba: Efficient Multi-Modal Large Language Model Utilizing Mamba-2 ➡️ 论文作者&#xff1a;Wenjun Huang, Jiakai Pan, Jiahao Tang, Yanyu Ding, Yifei Xing, …

论文阅读:ADVWEB : CONTROLLABLE BLACK-BOX ATTACKS ON VLM-POWERED WEB AGENTS

原文&#xff1a;2410.17401 源码&#xff1a;https://ai-secure.github.io/AdvWeb/ 摘要&#xff1a; 本文设计了一种专门针对web agent的黑盒攻击框架&#xff0c;通过训练一个对抗性提示生成模型&#xff0c;在网页中自动生成并注入“隐形”对抗性字符串&#xff0c;引导网…

Wireshark 在 macOS 上使用及问题解决

wireshark概述 Wireshark 是被广泛使用的免费开源网络协议分析软件&#xff08;network protocol analyzer&#xff09;或网络数据包分析工具&#xff0c;它可以让你在微观层面上查看网络上发生的事情。它的主要功能是截取网络数据包&#xff0c;并尽可能详细地展示网络数据包…

企业级安全实践:SSL/TLS 加密与权限管理(一)

引言 ** 在数字化转型的浪潮中&#xff0c;企业对网络的依赖程度与日俱增&#xff0c;从日常办公到核心业务的开展&#xff0c;都离不开网络的支持。与此同时&#xff0c;网络安全问题也日益严峻&#xff0c;成为企业发展过程中不可忽视的重要挑战。 一旦企业遭遇网络安全事…

#Js篇:BlobFile对象URL.createObjectURL()fetchlocationnavigatornew URl

Blob 在 JavaScript 中&#xff0c;Blob 是一个非常重要的对象&#xff0c;用于表示不可变的、原始的二进制数据块&#xff08;Binary Large Object&#xff09; arrayBuffer()&#xff1a;获取 Blob 的二进制数据作为 ArrayBuffer。 stream()&#xff1a;创建一个可读流&…