Java:SpringBoot整合SSE(Server-Sent Events)实现后端主动向前端推送数据
SpringBoot整合SSE(Server-Sent Events)可以实现后端主动向前端推送数据
目录
- 核心代码
- 完整代码
- 参考文章
核心代码
依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
后端接收sse连接
@Controller
@RequestMapping("/sse")
public class IndexController {/*** 创建SSE连接** @return*/@GetMapping(path = "/connect")public SseEmitter sse() {SseEmitter sseEmitter = new SseEmitter();// 发送一个注释,响应前端请求sseEmitter.send(SseEmitter.event().comment("welcome"));return sseEmitter;}
}
前端浏览器代码
// 连接服务器
var sseSource = new EventSource("http://localhost:8080/sse/connect");// 连接打开
sseSource.onopen = function () {console.log("连接打开");
}// 连接错误
sseSource.onerror = function (err) {console.log("连接错误:", err);
}// 接收到数据
sseSource.onmessage = function (event) {console.log("接收到数据:", event);handleReceiveData(JSON.parse(event.data))
}
完整代码
项目目录
$ tree -I target -I test
.
├── pom.xml
└── src└── main├── java│ └── com│ └── example│ └── demo│ ├── Application.java│ ├── controller│ │ └── IndexController.java│ ├── entity│ │ └── Message.java│ ├── service│ │ ├── SseService.java│ │ └── impl│ │ └── SseServiceImpl.java│ └── task│ └── SendMessageTask.java└── resources├── application.yml├── static└── templates└── index.html
完整依赖 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.7</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><mybatis-plus.version>3.5.2</mybatis-plus.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
前端代码 index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Demo</title>
</head><body><div id="result"></div><script>// 连接服务器var sseSource = new EventSource("http://localhost:8080/sse/connect");// 连接打开sseSource.onopen = function () {console.log("连接打开");}// 连接错误sseSource.onerror = function (err) {console.log("连接错误:", err);}// 接收到数据sseSource.onmessage = function (event) {console.log("接收到数据:", event);handleReceiveData(JSON.parse(event.data))}// 关闭链接function handleCloseSse() {sseSource.close()}// 处理服务器返回的数据function handleReceiveData(data) {let div = document.createElement('div');div.innerHTML = data.data;document.getElementById('result').appendChild(div);}// 通过http发送消息function sendMessage() {const message = document.querySelector("#message")const data = {data: message.value}message.value = ''fetch('http://localhost:8080/sse/sendMessage', {method: 'POST',headers: {'Content-Type': 'application/json;charset=utf-8'},body: JSON.stringify(data)})}</script>
</body>
</html>
定义一个返回数据 Message.java
package com.example.demo.entity;import lombok.Data;@Data
public class Message {private String data;private Integer total;
}
定义sse接口 SseService.java
package com.example.demo.service;import com.example.demo.entity.Message;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;public interface SseService {SseEmitter connect(String uuid);void sendMessage(Message message);
}
实现sse接口 SseServiceImpl.java
package com.example.demo.service.impl;import com.example.demo.entity.Message;
import com.example.demo.service.SseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@Slf4j
@Service
public class SseServiceImpl implements SseService {/*** messageId的 SseEmitter对象映射集*/private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();/*** 连接sse* @param uuid* @return*/@Overridepublic SseEmitter connect(String uuid) {SseEmitter sseEmitter = new SseEmitter();// 连接成功需要返回数据,否则会出现待处理状态try {sseEmitter.send(SseEmitter.event().comment("welcome"));} catch (IOException e) {e.printStackTrace();}// 连接断开sseEmitter.onCompletion(() -> {sseEmitterMap.remove(uuid);});// 连接超时sseEmitter.onTimeout(() -> {sseEmitterMap.remove(uuid);});// 连接报错sseEmitter.onError((throwable) -> {sseEmitterMap.remove(uuid);});sseEmitterMap.put(uuid, sseEmitter);return sseEmitter;}/*** 发送消息* @param message*/@Overridepublic void sendMessage(Message message) {message.setTotal(sseEmitterMap.size());sseEmitterMap.forEach((uuid, sseEmitter) -> {try {sseEmitter.send(message, MediaType.APPLICATION_JSON);} catch (IOException e) {e.printStackTrace();}});}
}
定时任务 SendMessageTask.java
package com.example.demo.task;import com.example.demo.entity.Message;
import com.example.demo.service.SseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;@Configuration
public class SendMessageTask {@Autowiredprivate SseService sseService;/*** 定时执行 秒 分 时 日 月 周*/@Scheduled(cron = "*/5 * * * * *") // 间隔5秒public void sendMessageTask() {Message message = new Message();DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");message.setData(LocalDateTime.now().format(format));sseService.sendMessage(message);}
}
前端路由 IndexController.java
package com.example.demo.controller;import com.example.demo.entity.Message;
import com.example.demo.service.SseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.util.UUID;@Slf4j
@Controller
@RequestMapping("/sse")
public class IndexController {@Autowiredprivate SseService sseService;/*** 首页** @return*/@GetMapping("/")public String index() {return "index";}/*** 创建SSE连接** @return*/@GetMapping(path = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter sse() {String uuid = UUID.randomUUID().toString();log.info("新用户连接:{}", uuid);return sseService.connect(uuid);}/*** 广播消息** @param message*/@PostMapping("/sendMessage")@ResponseBodypublic void sendMessage(@RequestBody Message message) {sseService.sendMessage(message);}
}
启动类 Application.java
package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication
@EnableScheduling
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}
完整代码:https://github.com/mouday/spring-boot-demo/SpringBoot-SSE
参考文章
- https://developer.mozilla.org/zh-CN/docs/Web/API/EventSource
- Server-Sent Events 教程
- 推送数据?也许你不需要 WebSocket
相关文章:
Java:SpringBoot整合SSE(Server-Sent Events)实现后端主动向前端推送数据
SpringBoot整合SSE(Server-Sent Events)可以实现后端主动向前端推送数据 目录 核心代码完整代码参考文章 核心代码 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</a…...
cmd命令行设置 windows 设置环境变量
cmd命令行设置 windows 设置环境变量 参考 51CTO博客 设置用户级别的环境变量 :: 设置新参数 JAVA_HOME1 setx JAVA_HOME1 "c:\test"; exit; echo "%JAVA_HOME1%";:: 追加参数内容 JAVA_HOME1 setx JAVA_HOME1 "%JAVA_HOME1%;c:\test2\;"; exi…...
基于负载均衡的在线OJ实战项目
前言: 该篇讲述了实现基于负载均衡式的在线oj,即类似在线编程做题网站一样,文章尽可能详细讲述细节即实现,便于大家了解学习。 文章将采用单篇不分段形式(ps:切着麻烦),附图文&#…...
Opencv手工选择图片区域去水印
QT 插件化图像算法研究平台的功能在持续完善,补充了一个人工选择图片区域的功能。 其中,图片选择功能主要代码如下: QRect GLImageWidget::getSeleted() {QRect ajust(0,0,0,0);if(image.isNull() || !hasSelection)return ajust;double w1…...
《向量数据库》——向量数据库跟大模型是什么关系呢?
在人工智能领域,最近的一个重要趋势是大模型的兴起。在大模型的世界里,我们面临着处理和管理大规模向量数据的挑战,而向量数据库,就是为了满足这个需求而不断发展着。 那么,向量数据库跟大模型是什么关系呢?…...
通过这 5 项 ChatGPT 创新增强您的见解
为什么绝大多数的人还不会使用chatGPT来提高工作效能?根本原因就在还不会循序渐进的发问与chatGPT互动。本文总结了5个独特的chatGPT提示,可以帮助您更好地与Chat GPT进行交流,以获得更清晰的信息、额外的信息和见解。 澄清假设和限制 用5种提…...
W5500-EVB-PICO主动PING主机IP检测连通性(十)
前言 上一章我们用W5500_EVB_PICO 开发板做UDP组播数据回环测试,那么本章我们进行W5500_EVB_PICO Ping的测试。 什么是PING? Ping (Packet Internet Groper)是一种因特网包探索器,用于测试网络连接量的程序 。Ping是…...
解释基本的3D理论
推荐:使用 NSDT场景编辑器 快速搭建3D应用场景 坐标系 3D 本质上是关于 3D 空间中形状的表示,并使用坐标系来计算它们的位置。 WebGL 使用右侧坐标系 — 轴指向右侧,轴指向上方,轴指向屏幕外,如上图所示。xyz 对象 …...
C# 练习题
26. Enum(枚举) /*枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。C# 枚举是值类型。换句话说,枚举包含自己的值,且不能继承或传递继承。 */using System;public class EnumTest {enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat };static…...
解决Linux报错:Swap file “xxxxxx.swp“ already exists
出现问题 Swap file “.models_conf.yaml.swp” already exists! 在 Linux 下 vim 编辑过程中,由于某种原因异常退出正在编辑的文件,再次编辑该文件时,会出现如下提示: 一个文件出现了带有.swp的副本文件的时候,会出现…...
基于飞桨图学习框架的空间异配性感知图神经网络
本期文章将为大家分享飞桨社区开发者肖淙曦、周景博发表于数据挖掘顶会KDD2023的论文《Spatial Heterophily Aware Graph Neural Networks》。 肖淙曦 肖淙曦,百度研究院商业智能实验室研究实习生,中国科学技术大学在读博士生,主要从事时空…...
Springboot整合JWT
1. 应用场景 前后端分离项目保持登录状态。 问题:ajax请求如何跨域,将无法携带jsessionid,这样会导致服务器端的session不可用。如何解决? 后端: 登录接口在验证过用户密码后,将用户的身份信息转换成…...
如何使用Python和正则表达式处理XML表单数据
在日常的Web开发中,处理表单数据是一个常见的任务。而XML是一种常用的数据格式,用于在不同的系统之间传递和存储数据。本文通过阐述一个技术问题并给出解答的方式,介绍如何使用Python和正则表达式处理XML表单数据。我们将探讨整体设计、编写思…...
LA@方阵相似@相似矩阵的性质
文章目录 相似矩阵引言相似矩阵定义相似变换相似变换矩阵相似矩阵的矩阵多项式和特征值相同推论:与对角阵相似的矩阵性质定理 相似矩阵性质相似矩阵的乘方性质相似矩阵和矩阵多项式相似对角阵 对角阵多项式的展开小结 相似矩阵 引言 对角阵是矩阵中最简单的一类矩阵 对角阵相…...
ZLMediaKit 各种推拉流
1 用ffmpeg 推音视频流 ./ffmpeg -f dshow -i video"HP Wide Vision HD Camera" -f dshow -i audio"麦克风阵列 (Realtek High Definition Audio)" -rtbufsize 100M -max_delay 100 -pix_fmt yuv420p -tune zerolatency -c:v libx264 -crf 18 -s 1280x720…...
行业追踪,2023-08-29
自动复盘 2023-08-29 凡所有相,皆是虚妄。若见诸相非相,即见如来。 k 线图是最好的老师,每天持续发布板块的rps排名,追踪板块,板块来开仓,板块去清仓,丢弃自以为是的想法,板块去留让…...
【简单】228. 汇总区间
原题链接:https://leetcode.cn/problems/summary-ranges/description/ 228. 汇总区间 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖&…...
Mysql高级语句
高级语句 1.按关键字排序 SELECT column1, column2, ... FROM table_name ORDER BY column1, column2, ... ASC|DESC ASC 是按照升序进行排序的,是默认的排序方式,即 ASC 可以省略。 SELECT 语句中如果没有指定具体的排序方式,则默认按 ASC…...
Python中 re.compile 函数的使用
前言 嗨喽,大家好呀~这里是爱看美女的茜茜呐 以下介绍在python的re模块中怎样应用正则表达式 👇 👇 👇 更多精彩机密、教程,尽在下方,赶紧点击了解吧~ python源码、视频教程、插件安装教程、资料我都准备…...
【分布式搜索引擎es】
文章目录 数据搜索DSL实现查询文档搜索结果处理 RestClient实现 elasticsearch最擅长的是 搜索和 数据分析。 数据搜索 DSL实现 查询文档 常见的查询类型包括: 查询所有:查询出所有数据,一般测试用。例如:match_all全文检索…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
