SpringBoot使用SSE进行实时通知前端

news/2025/1/11 17:50:08/

SpringBoot使用SSE进行实时通知前端

  • 说明
  • maven依赖
    • SSE工具类代码
  • Controller测试代码
  • 测试结果如下:
  • 注意
    • 将超时时间由原来的0改为默认的30秒,会报错。
    • 将springboot降为低版本如1.4.2.RELEASE。

说明

项目有个需求是要实时通知前端,告诉前端这个任务加载好了。然后想了2个方案,一种是用websocket进行长连接,一种是使用SSE(Sever Send Event),是HTTP协议中的一种,Content-Type为text/event-stream,能够保持长连接。
websocket是前端既能向后端发送消息,后端也能向前端发送消息。
SSE是只能后端向前端发送消息。
因为只需要后端通知,所以我这里选择了使用SSE实现。
这里先做个笔记,怕以后忘记怎么使用。

maven依赖

<?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.3</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.project</groupId><artifactId>test</artifactId><version>0.0.1-SNAPSHOT</version><name>test</name><description>test</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--web依赖,内嵌入tomcat,SSE依赖于该jar包,只要有该依赖就能使用SSE--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--lombok依赖,用来对象省略写set、get方法--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

SSE工具类代码

package com.etone.project.utils;import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;@Slf4j
public class SseEmitterServer {/*** 当前连接数*/private static AtomicInteger count = new AtomicInteger(0);private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();public static SseEmitter connect(String userId){//设置超时时间,0表示不过期,默认是30秒,超过时间未完成会抛出异常SseEmitter sseemitter = new SseEmitter(0L);//注册回调sseemitter.onCompletion(completionCallBack(userId));//这个onError在springbooot低版本没有这个方法,公司springboot1.4.2版本,没有这个方法,可以进行注释。sseemitter.onError(errorCallBack(userId));sseemitter.onTimeout(timeoutCallBack(userId));sseEmitterMap.put(userId,sseemitter);//数量+1count.getAndIncrement();log.info("create new sse connect ,current user:{}",userId);return sseemitter;}/*** 给指定用户发消息*/public static void sendMessage(String userId, String message){if(sseEmitterMap.containsKey(userId)){try{sseEmitterMap.get(userId).send(message);}catch (IOException e){log.error("user id:{}, send message error:{}",userId,e.getMessage());e.printStackTrace();}}}/*** 想多人发送消息,组播*/public static void groupSendMessage(String groupId, String message){if(sseEmitterMap!=null&&!sseEmitterMap.isEmpty()){sseEmitterMap.forEach((k,v) -> {try{if(k.startsWith(groupId)){v.send(message, MediaType.APPLICATION_JSON);}}catch (IOException e){log.error("user id:{}, send message error:{}",groupId,message);removeUser(k);}});}}public static void batchSendMessage(String message) {sseEmitterMap.forEach((k,v)->{try{v.send(message,MediaType.APPLICATION_JSON);}catch (IOException e){log.error("user id:{}, send message error:{}",k,e.getMessage());removeUser(k);}});}/*** 群发消息*/public static void batchSendMessage(String message, Set<String> userIds){userIds.forEach(userid->sendMessage(userid,message));}//移除用户public static void removeUser(String userid){sseEmitterMap.remove(userid);//数量-1count.getAndDecrement();log.info("remove user id:{}",userid);}public static List<String> getIds(){return new ArrayList<>(sseEmitterMap.keySet());}public static int getUserCount(){return count.intValue();}private static Runnable completionCallBack(String userId) {return () -> {log.info("结束连接,{}",userId);removeUser(userId);};}private static Runnable timeoutCallBack(String userId){return ()->{log.info("连接超时,{}",userId);removeUser(userId);};}private static Consumer<Throwable> errorCallBack(String userId){return throwable -> {log.error("连接异常,{}",userId);removeUser(userId);};}
}

Controller测试代码

package com.project.test.controller;import com.hjl.test.util.SseEmitterServer;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping(value = "/test")
public class TestController {//sse连接接口@GetMapping (value = "/sse/connect/{id}")public SseEmitter connect(@PathVariable String id){return SseEmitterServer.connect(id);}//sse向指定用户发送消息接口@GetMapping (value = "/sse/send/{id}")public Map<String,Object> send(@PathVariable String id,@RequestParam(value = "message", required = false) String message){Map<String,Object> returnMap = new HashMap<>();//向指定用户发送信息SseEmitterServer.sendMessage(id,message);returnMap.put("message","向id为"+id+"的用户发送:"+message+"成功!");returnMap.put("status","200");returnMap.put("result",null);return returnMap;}//sse向所有已连接用户发送消息接口@GetMapping (value = "/sse/batchSend")public Map<String,Object> batchSend(@RequestParam(value = "message", required = false) String message){Map<String,Object> returnMap = new HashMap<>();//向指定用户发送信息SseEmitterServer.batchSendMessage(message);returnMap.put("message",message+"消息发送成功!");returnMap.put("status","200");returnMap.put("result",null);return returnMap;}//sse关闭接口@GetMapping (value = "/sse/close/{id}")public Map<String,Object> close(@PathVariable String id){Map<String,Object> returnMap = new HashMap<>();//移除idSseEmitterServer.removeUser(id);System.out.println("当前连接用户id:"+SseEmitterServer.getIds());returnMap.put("message","连接关闭成功!");returnMap.put("status","200");returnMap.put("result",null);return returnMap;}}

测试结果如下:

这里测试SSE连接,就像正常接口那样请求就行。
本地调用接口/sse/connect/1如下:
这里我连接2个用户,用来模拟向指定用户id发送信息和批量向已连接的用户发送消。
在这里插入图片描述
在这里插入图片描述
后端服务打印如下:
在这里插入图片描述

本地调用接口/sse/send/1如下:
在这里插入图片描述
用户1的结果如下,发现它收到了消息:
在这里插入图片描述
用户2没有收到结果,如下:
在这里插入图片描述

本地调用接口/sse/batchSend如下:
批量向所有已经连接的用户发送消息。
在这里插入图片描述
用户1结果如下,发现接收到了消息:
在这里插入图片描述
用户2结果如下,发现也接收到了消息:
在这里插入图片描述
测试结果都符合预期。
点击postman的close按钮,关闭连接:
在这里插入图片描述
在这里插入图片描述
发现前端连接虽然关闭了,但是后端实际还在连接中,根本没有移除用户的提示:
在这里插入图片描述
所以这里还需要自己手动写关闭接口测试。
本地调用接口/sse/close/1如下:
在这里插入图片描述
可以看到把用户id为1的给移除了,只剩用户2还在连接中。
在这里插入图片描述
这里所有测试完成,结果符合预期。

注意

将超时时间由原来的0改为默认的30秒,会报错。

在这里插入图片描述
测试结果如下:
在这里插入图片描述
在这里插入图片描述
这里直接出现了一个异常:org.springframework.web.context.request.async.AsyncRequestTimeoutException
甚至连接都断开了。

将springboot降为低版本如1.4.2.RELEASE。

使用postman进行测试的时候,发现它不是一直在请求中:如下:
将Springboot降为1.4.2.RELEASE
在这里插入图片描述

springboot的1.4.2.RELEASE版本没有onError方法,需要注释掉。在这里插入图片描述
postman测试如下:
低版本测试的时候发现它有一个这个连接可以直接看到,而使用springboot版本2.x版本就发现它一直处于发送请求的状态,什么时候后端向前端发送了消息,它就显示这个。
springboot的1.4.2.RELEASE版本结果:
在这里插入图片描述
springboot的2.7.3版本结果:
在这里插入图片描述

这里先将这种情况先记录下来先,等后面有时间再研究。怎么高版本就不能向低版本那样返回这个连接信息呢?所以SpringBoot高版本使用SSE连接的时候一直处于Sending request这种情况,这种情况是正常的吗?有没有大佬告知下,谢谢。


http://www.ppmy.cn/news/169579.html

相关文章

i5 13400 和 i5-12400 差距 i513400对比12400选哪个好

i5-13400采用10纳米工艺&#xff0c;拥有6个大核和4个小核&#xff0c;共16线程&#xff0c;标准运行频率为2.5 GHz&#xff0c;最高运行频率为 4.6 GHz&#xff0c;L2级缓存为9.5 MB&#xff0c;L3级缓存为20MB&#xff0c;功耗为65W 组装电脑选 i513400还是12400怎么搭配更合…

rtx4000显卡什么级别 rtx4000显卡属于哪个级别

RTX™ 4000 采用 NVIDIA Turing™ 架构 和 NVIDIA RTX 平台&#xff0c;可在单插槽 PCI-e 外形中提供卓越的性能和功能。rtx4000怎么样这些点很重要 http://www.adiannao.cn/dq 它是基于TU106核心的&#xff0c;整体规格跟RTX 2070显卡差不多&#xff0c;2304个CUDA核心&#…

七彩虹将星x15xs 2022款 怎么样

七彩虹新款的将星X15 XS游戏笔记本正式上市&#xff0c;搭载了英特尔刚刚推出的第十二代酷睿移动版处理器以及NVIDIA RTX 30系显卡&#xff0c;性能有了新的提升。 将星X15-XS依然采用延续了雷震子IP&#xff0c;机器的外观设计非常有特点&#xff0c;苍劲有力的书法“将”字位…

七彩虹计算机主板怎么样,七彩虹的主板怎么样?

七彩虹主板性价比的确是很高 不过 非要拿它和华硕的主板比较的话 首先要走出只要是华硕主板就都比其他品牌好的误区&#xff01; 不管什么牌子的主板都有高端/中端/低端 当然他们用料是不一样的 就拿华硕来说 一般黑颜色的主板 大多是高端的主板 用料也相当讲究什么八相供电 全…

q4000相当于什么显卡

此显卡相当于GTX650 。 q4000&#xff1a;专业图形显卡&#xff0c;主要用在商业领域&#xff0c;用来制作复杂的3D图形、视频渲染和视频加速等专业工作。它不适合玩游戏&#xff0c;一块专业图像显卡&#xff0c;其游戏性能可能还不如一块千元左右的游戏显卡的性能来得强&…

rx6600xt显卡相当于什么显卡

rx6600xt显卡相当于什么n卡&#xff1f;相当于RTX 3060显卡 rx 6600xt采用Navi23核心&#xff0c;基础核心频率1968、游戏频率2359、加速频率2589、CUDA单元2048个&#xff0c;显存规格GDDR6&#xff0c;显存频率16000MHz,显存容量8GB&#xff0c;显存位宽128bit,TDP功耗160W。…

电脑游戏测试cpugpu软件,【七彩虹GTX760评测】七彩虹iGame760烈焰战神规格介绍-中关村在线...

七彩虹iGame760烈焰战神规格介绍 我们先来看一下测试中用到的显卡。七彩虹iGame760烈焰战神U-2GD5采用基于28nm制程工艺&#xff0c;Kepler架构设计的GK104图形核心。该显卡拥有1152个流处理器&#xff0c;32个ROPs单元和96个纹理单元&#xff0c;完美支持DirectX 11游戏特效、…

同样是2080ti,为什么华硕卖一万二,七彩虹卖八千?

一块显卡最重要的就是看显示芯片&#xff0c;也就是GPU&#xff0c;因为GPU的规格和档次是决定着显卡性能的关键&#xff0c;但是我们可以看到很多品牌搭载同一颗GPU芯片的显卡&#xff0c;比如华硕的2080ti和七彩虹的2080ti其实GPU核心都是一样的&#xff0c;显存几乎也是成品…