SSE(Server Sent Event)实战(2)- Spring MVC 实现

一、服务端实现

  1. 使用 @RestController 注解创建一个控制器类(Controller)

  2. 创建一个方法来创建一个客户端连接,它返回一个 SseEmitter,处理 GET 请求并产生(produces)文本/事件流 (text/event-stream)

  3. 创建一个新的 SseEmitter, 保存它并从方法中返回

  4. 在另一个线程中异步发送事件, 先拿到保存的 SseEmitter 并根据需要多次调用调用SseEmitter.send()方法

  5. 完成事件发送, 调用 SseEmitter.complete() 方法

  6. 要异常完成发送事件,请调用 SseEmitter.completeWithError() 方法

/** xxx.com* Copyright (C) 2021-2024 All Rights Reserved.*/
package com.sse.demo.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @author xxx* @version SseController.java, v 0.1 2024-07-11 10:11*/
@Slf4j
@RestController
@RequestMapping("/sse")
public class SseController {private static final Map<String, SseEmitter> SSE_EMITTER_MAP = new ConcurrentHashMap<>();/*** 创建连接*/@GetMapping("/create-connect")public SseEmitter createConnect(@RequestParam("userId") String userId) {try {// 设置超时时间,0表示不过期。默认30秒SseEmitter sseEmitter = new SseEmitter(0L);// 注册回调sseEmitter.onCompletion(() -> removeSseConnection(userId, "SSE连接已关闭"));sseEmitter.onError(throwable -> removeSseConnection(userId, "SSE连接出现错误"));sseEmitter.onTimeout(() -> removeSseConnection(userId, "SSE连接超时"));SSE_EMITTER_MAP.put(userId, sseEmitter);log.info("创建了用户[{}]的SSE连接", userId);return sseEmitter;} catch (Exception e) {log.error("创建新的SSE连接异常,当前用户:" + userId, e);return null;}}/*** 发送消息*/@GetMapping("/send-message")public void sendMessage(@RequestParam("userId") String userId, @RequestParam("message") String message) {SseEmitter sseEmitter = SSE_EMITTER_MAP.get(userId);if (sseEmitter != null) {try {sseEmitter.send(SseEmitter.event().name("message").data(message).reconnectTime(5000));log.info("给用户[{}]发送消息成功: {}", userId, message);} catch (Exception e) {log.error("给用户[{}]发送消息失败: {}", userId, e.getMessage(), e);// 如果发送失败,尝试从map中移除失效的SseEmitterremoveSseConnection(userId, "发送消息失败");}} else {log.info("用户[{}]的SSE连接不存在或已关闭,无法发送消息", userId);}}private void removeSseConnection(String userId, String reason) {SSE_EMITTER_MAP.computeIfPresent(userId, (key, sseEmitter) -> {sseEmitter.complete();log.info("用户[{}]的SSE连接已移除,原因:{}", userId, reason);return null;});}
} 

二、客户端实现

创建多个 index.html文件,放在 static 目录下,用不同的浏览器打开,实现向多个用户推送的场景。
在这里插入图片描述


<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>SSE Demo</title><script>        document.addEventListener('DOMContentLoaded', function () {var userId = "1";// 创建一个新的EventSource对象var source = new EventSource('http://localhost:8080/sse/create-connect?userId=' + userId);// 当连接打开时触发source.onopen = function (event) {console.log('SSE连接已打开');};// 当从服务器接收到消息时触发source.onmessage = function (event) {// event.data 包含服务器发送的文本数据console.log('接收到消息:', event.data);// 在页面上显示消息var messagesDiv = document.getElementById('messages');if (messagesDiv) {messagesDiv.innerHTML += '<p>' + event.data + '</p>'; // 直接使用event.data} else {console.error('未找到消息容器元素');}};// 当发生错误时触发source.onerror = function (event) {console.error('SSE连接错误:', event);};});</script>
</head>
<body>
<div id="messages"><!-- 这里将显示接收到的消息 -->
</div>
</body>
</html>

三、启动项目

  1. 运行 Spring 项目
    在这里插入图片描述
  2. 浏览器打开 index.html文件
    在这里插入图片描述
  3. 调用发送消息接口
    curl http://localhost:8080/sse/send-message\?userId\=1\&message\=test0001
    在这里插入图片描述

打开多个连接,用 userId 就可以实现向不同的用户推送的逻辑了。

四、总结

上面已经实现了最基本的消息推送需求,但是我们还可以思考一下实际生产中,我们还需要做哪些优化?

  1. 如果我们服务设置了最大连接时间,比如 3 分钟,而服务端又长时间没有消息推送给客户端,导致长连接被关闭该怎么办?
  2. 实际生产环境,我们肯定是多个实例部署,那么怎么保证创建连接和发送消息是在同一个实例完成?如果不是一个实例,就意味着用户没有建立连接,消息肯定发送失败。

下一篇博客,再做具体优化。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode145. 二叉树的后序遍历,递归法+迭代法,全过程图解+步步解析,一点点教会你迭代法后序遍历

leetcode145. 二叉树的后序遍历&#xff0c;递归法迭代法 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[3,2,1] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#…

vue、js截取视频任意一帧图片

html有本地上传替换部分&#xff0c;可以不看 原理&#xff1a;通过video标签对视频进行加载&#xff0c;随后使用canvas对截取的视频帧生成需要的图片 <template> <el-row :gutter"18" class"preview-video"><h4>视频预览<span&…

LabVIEW电路产品功能自动检测系统

开发基于LabVIEW的电路产品功能自动检测系统。该系统通过整合先进的硬件和软件技术&#xff0c;实现了电路产品的自动化测试&#xff0c;显著提高了测试效率和准确性&#xff0c;对于提升电子产品的可靠性和工作效率具有重要意义。 项目背景 在电子制造业中&#xff0c;电路产…

PyCharm查看文件或代码变更记录

背景&#xff1a; Mac笔记本上有一个截图的定时任务在运行&#xff0c;本地Python使用的是PyCharm IDE&#xff0c;负责的同事休假&#xff0c;然后定时任务运行的结果不符合预期&#xff0c;一下子不知道问题出现在哪里。 定位思路&#xff1a; 1、先确认网络、账号等基本的…

git使用以及理解

git练习网站 Learn Git Branching git操作大全Oh Shit, Git!?! git commit git branch name git merge bugFix 合并俩个分支 git rebase main git checkout headgit switch head 会导致HEAD分离 &#xff0c;就是指head->HEAD->c1 相对引用 ------------------- …

测试面试宝典(十四)—— 你觉得软件测试的核心竞争力是什么?

回答一&#xff1a; 软件测试的核心竞争力在于其能够保障软件产品的质量和可靠性。 首先&#xff0c;测试人员需要具备敏锐的观察力和细致入微的分析能力&#xff0c;能够在复杂的系统中发现潜在的缺陷和问题。例如&#xff0c;在测试一款电商平台时&#xff0c;不仅要关注订…

Apache AGE的MATCH子句

MATCH子句允许您在数据库中指定查询将搜索的模式。这是检索数据以在查询中使用的主要方法。 通常在MATCH子句之后会跟随一个WHERE子句&#xff0c;以添加用户定义的限制条件到匹配的模式中&#xff0c;以操纵返回的数据集。谓词是模式描述的一部分&#xff0c;不应被视为仅在匹…

【TDA4板端部署】基于 Pytorch 训练并部署 ONNX 模型在 TDA4

1 将torch模型转onnx模型 Ti转换工具只支持以下格式&#xff1a; Caffe - 0.17 (caffe-jacinto in gitHub) Tensorflow - 1.12 ONNX - 1.3.0 (opset 9 and 11) TFLite - Tensorflow 2.0-Alpha 基于 Tensorflow、Pytorch、Caffe 等训练框架&#xff0c;训练模型&#xff1a;选择…

多多OJ评测系统 前端项目环境初始化 安装Vue脚手架 引入Arco Design组件

目录 确定环境 命令行输入 装一下脚手架 监测一下是否安装成功 创建一个项目 选择一系列的配置后 我们打开webStorm 配置脚手架后我们先运行 我们这边能获取到网址 其实我们脚手架已经帮我们做到了 接下来要引入相关的组件 选择用npm进行安装 我们建议的是完整引入…

姓名配对测试源码

源码简介 姓名配对测试源码&#xff0c;输入两人姓名即可测试缘分&#xff0c;可查看朋友到底喜欢谁的趣味源码。 自己手动在数据库里修改数据&#xff0c;数据库里有就会优先查询数据库的信息&#xff0c; 没设置的话第一次查询缘分都是非常好的 95-99&#xff0c;第二次查…

Spring Web MVC(常用的注解@RequestMapping,@RequestParam,@RequestBody等)

一、Spring MVC spring的启动类 启动类是看这个 SpringBootApplication 注解&#xff0c;而不是 类的名字 这个注解在哪&#xff0c;哪个类就是启动类 1.MVC思想 举例 二、Spring MVC mvc 是一种思想&#xff0c;而spring mvc是对mvc思想的一种实现。全称是 spring web mvc…

pytorch学习(四)绘制loss和correct曲线

这一次学习的时候静态绘制loss和correct曲线&#xff0c;也就是在模型训练完成后&#xff0c;对统计的数据进行绘制。 以minist数据训练为例子 import torch from torch import nn from torch.utils.data import DataLoader from torchvision import datasets from torchvisi…

Prometheus智能化监控介绍

Prometheus智能化监控介绍 官方网站特点&#xff1a;样本 Prometheus组件Prometheus工作流程Prometheus和zabbix对比分析Prometheus的几种部署模式Prometheus的四种数据类型CounterGaugehistogram为什需要用histogram柱状图&#xff1f; summary Prometheus对kubernetes的监控介…

文献阅读:tidyomics 生态系统:增强组学数据分析

文献介绍 文献题目&#xff1a; The tidyomics ecosystem: enhancing omic data analyses 研究团队&#xff1a; Stefano Mangiola&#xff08;澳大利亚沃尔特和伊丽莎霍尔医学研究所&#xff09;、Michael I. Love&#xff08;美国北卡罗来纳大学教堂山分校&#xff09;、Ant…

C#开发:PowerDesigner建表和Navicat导入数据

一、打开Powerdesigner&#xff0c;新建一个模型&#xff0c;点击ok 二、用工具面板拖拽出一个数据表 &#xff08;如果没有工具面板&#xff0c;请在如下操作中开启&#xff09; 三、双击刚刚的拖拽出来的表&#xff0c;设计表的字段&#xff0c;可以添加注释说明 【备注】…

基于Vue CLI 3构建Vue3项目(Vue2也可参考)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

完美解决An error occurred while starting the kernel的正确解决方法,亲测有效!!!

完美解决An error occurred while starting the kernel的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 亲测有效 完美解决An error occurred while starting the kernel的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01;报错…

03 Git的基本使用

第3章&#xff1a;Git的基本使用 一、创建版本仓库 一&#xff09;TortoiseGit ​ 选择项目地址&#xff0c;右键&#xff0c;创建版本库 ​ 初始化git init版本库 ​ 查看是否生成.git文件&#xff08;隐藏文件&#xff09; 二&#xff09;Git ​ 选择项目地址&#xff0c…

Chromium CI/CD 之Jenkins实用指南2024- Windows节点开启SSH服务(七)

1.引言 在现代软件开发和持续集成的过程中&#xff0c;自动化部署和远程管理是不可或缺的关键环节。SSH&#xff08;Secure Shell&#xff09;协议以其强大的安全性和灵活性&#xff0c;成为连接和管理远程服务器的首选工具。对于使用Windows虚拟机作为Jenkins从节点的开发者而…

【2024】springboot Home F家居系统的设计与管理

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…