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全文检索…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
