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全文检索…...
从拒稿到录用:我的TOMM投稿实战复盘与经验分享
1. 从TMM拒稿到TOMM录用的心路历程 第一次收到TMM的拒稿邮件时,我正在实验室熬夜改代码。邮件弹出来的那一刻,整个人就像被泼了一盆冷水。那篇论文已经经历了三轮大修,每次都是几十条审稿意见,我们团队前前后后修改了上百个细节。…...
Cursor Pro功能解锁全攻略:从免费版到专业体验的完整指南
Cursor Pro功能解锁全攻略:从免费版到专业体验的完整指南 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能: Youve reached your …...
HelixDB部署与运维:从本地开发到生产环境的完整流程
HelixDB部署与运维:从本地开发到生产环境的完整流程 【免费下载链接】helix-db HelixDB is a powerful, graph-vector database built entirely in Rust for millisecond query latency and ease of use. 项目地址: https://gitcode.com/gh_mirrors/he/helix-db …...
万物识别在智能体(Skills Agent)中的集成应用
万物识别在智能体(Skills Agent)中的集成应用 想象一下,你正在开发一个智能客服机器人,用户发来一张照片,里面是自家厨房水槽下漏水的一堆零件。用户问:“这是什么东西坏了?我该买什么配件?” 传统的文本对…...
GPEN肖像增强使用技巧:自然、强力、细节三种模式适用场景解析
GPEN肖像增强使用技巧:自然、强力、细节三种模式适用场景解析 1. 认识GPEN的三种处理模式 GPEN作为当前最先进的肖像增强工具之一,其核心价值在于提供了三种差异化的处理模式:自然、强力和细节。这三种模式不是简单的强度差异,而…...
在GCP上运行autoresearch
Andrej Karpathy最近开源了autoresearch,这是一个将真实LLM训练环境交给AI代理并让它自主实验的项目。代理修改模型代码,训练恰好5分钟,检查验证损失是否改善,保留或丢弃更改,然后重复。你去睡觉;醒来时会看…...
Heygem数字人系统效果展示:看一段音频如何驱动多个数字人视频
Heygem数字人系统效果展示:看一段音频如何驱动多个数字人视频 1. 系统核心能力概览 Heygem数字人视频生成系统批量版webui版是一款基于AI技术的创新工具,能够将单一音频源同步驱动多个数字人视频生成。系统采用先进的语音驱动口型同步技术,…...
太原烘焙培训排名
在太原选择烘焙培训机构时,许多朋友会关注不同机构的教学质量与特色。以下整理了一些选择时可以考虑的方面,供您参考。教学方式与内容部分机构采用以实操为主的教学模式,例如山西旭梦圆食品有限公司的课程安排中,实践操作占较大比…...
别再为发票报销发愁!用Python+EasyOFD库,5分钟搞定OFD转PDF/图片(附完整代码)
5分钟极速解决发票报销难题:PythonEasyOFD高效转换实战指南 每次月底报销时,面对邮箱里堆积如山的OFD格式电子发票,你是否也感到头疼?手动一张张下载、转换、打印不仅耗时耗力,还容易出错。今天我们就来彻底解决这个困…...
TinyCheck开发指南:从源码结构到核心类设计,理解网络安全检测平台架构
TinyCheck开发指南:从源码结构到核心类设计,理解网络安全检测平台架构 【免费下载链接】TinyCheck TinyCheck allows you to easily capture network communications from a smartphone or any device which can be associated to a Wi-Fi access point …...
