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全文检索…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...
