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

这篇具有很好参考价值的文章主要介绍了SpringBoot使用SSE进行实时通知前端。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

说明

项目有个需求是要实时通知前端,告诉前端这个任务加载好了。然后想了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);
        //数量+1
        count.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);
        //数量-1
        count.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<>();
        //移除id
        SseEmitterServer.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发送信息和批量向已连接的用户发送消。
SpringBoot使用SSE进行实时通知前端
SpringBoot使用SSE进行实时通知前端
后端服务打印如下:
SpringBoot使用SSE进行实时通知前端

本地调用接口/sse/send/1如下:
SpringBoot使用SSE进行实时通知前端
用户1的结果如下,发现它收到了消息:
SpringBoot使用SSE进行实时通知前端
用户2没有收到结果,如下:
SpringBoot使用SSE进行实时通知前端

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

注意

将超时时间由原来的0改为30毫秒(L在整数后面表示Long占8字节),会报错。

SpringBoot使用SSE进行实时通知前端
测试结果如下:
SpringBoot使用SSE进行实时通知前端
SpringBoot使用SSE进行实时通知前端
这里直接出现了一个异常:org.springframework.web.context.request.async.AsyncRequestTimeoutException
甚至连接都断开了。

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

使用postman进行测试的时候,发现它不是一直在请求中:如下:
将Springboot降为1.4.2.RELEASE
SpringBoot使用SSE进行实时通知前端

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

这里先将这种情况先记录下来先,等后面有时间再研究。怎么高版本就不能向低版本那样返回这个连接信息呢?所以SpringBoot高版本使用SSE连接的时候一直处于Sending request这种情况,这种情况是正常的吗?有没有大佬告知下,谢谢。文章来源地址https://www.toymoban.com/news/detail-472831.html

到了这里,关于SpringBoot使用SSE进行实时通知前端的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • WebSocket实现后端数据变化,通知前端实时更新数据

    背景 ​ 项目中需要做一个消息提示功能,当有用户处理相关待办信息后,别的用户需要实时更新处理后的待办信息。 解决方案: ​ 1、使用最原始的方法,写个定时器去查询待办信息。但这种方式在大多数情况是不被允许的,它会浪费系统中的许多资源,同时也并不是完全

    2024年04月15日
    浏览(40)
  • SSE:后端向前端发送消息(springboot SseEmitter)

    有一个项目,前端vue,后端springboot。现在需要做一个功能:用户在使用系统的时候,管理员发布公告,则使用系统的用户可以看到该公告。 基于此,一个简单的方案: 前端使用JS方法setInterval ,重复调用后端公告获取接口。此方法有几点缺陷: 循环调用的时间间隔不好确定

    2024年02月02日
    浏览(36)
  • 用 SpringBoot 和 SSE 打造动态前端更新的终极指南

    你知道什么是开发者的梦魇吗?慢!慢!慢!在一个需要实时数据更新的应用中,如果数据像乌龟一样慢吞吞地爬行,那用户体验就会像坐过山车一样直线下降。所以今天,我们要化身为数据传输的超级英雄,用 SpringBoot 和 SSE(服务器发送事件)打造一个超酷、超快、而且超

    2024年02月02日
    浏览(32)
  • 详细说明使用husky规范前端项目搭建

    什么是husky: 操作git hook的工具,主要实现代码提交前 eslint 校验和 commit 信息的规范校验,也可以避免多人合作时代码格式化不统一造成的冲突 什么是git hook: 通常分为客户端钩子和服务端钩子,这里主要介绍客户端钩子。 pre-commit: 该钩子在键入提交信息前运行。 它用于检

    2024年02月11日
    浏览(39)
  • WEB通讯技术。前端实现SSE长连接,nodejs+express搭建简单服务器,进行接口调试,通过curl请求数据

    长连接(Keep-Alive)是一种HTTP/1.1的持久连接技术,它允许客户端和服务器在一次TCP连接上进行多个HTTP请求和响应,而不必为每个请求/响应建立和断开一个新的连接。长连接有助于减少服务器的负载和提高性能。 长连接的HTTP请求方法与普通HTTP请求方法相同,可以使用GET、P

    2024年02月09日
    浏览(39)
  • Java:SpringBoot整合SSE(Server-Sent Events)实现后端主动向前端推送数据

    SpringBoot整合SSE(Server-Sent Events)可以实现后端主动向前端推送数据 依赖 后端接收sse连接 前端浏览器代码 项目目录 完整依赖 pom.xml 前端代码 index.html 定义一个返回数据 Message.java 定义sse接口 SseService.java 实现sse接口 SseServiceImpl.java 定时任务 SendMessageTask.java 前端路由 IndexCont

    2024年02月10日
    浏览(32)
  • Spring Boot中使用Server-Sent Events (SSE) 实现实时数据推送教程

    Server-Sent Events (SSE) 是HTML5引入的一种轻量级的服务器向浏览器客户端单向推送实时数据的技术。在Spring Boot框架中,我们可以很容易地集成并利用SSE来实现实时通信。          在Spring Boot项目中,无需额外引入特定的依赖,因为Spring Web MVC模块已经内置了对SSE的支持。 辅助

    2024年03月18日
    浏览(41)
  • Java使用websocket实现消息实时通知

    博客仅做学习记录使用。 做项目中遇到这样一个实时通知需求,因为第一次接触这个,期间查了很多资料,看了很多博客,最后实现功能,查询的博客太多,就不一一放出来了,感谢各位大佬。 websocket方式主要代码来源于这个大佬的博客: https://blog.csdn.net/moshowgame/article/d

    2024年02月08日
    浏览(47)
  • 项目实战之RabbitMQ重试机制进行消息补偿通知

    🧑‍💻作者名称:DaenCode 🎤作者简介:啥技术都喜欢捣鼓捣鼓,喜欢分享技术、经验、生活。 😎人生感悟:尝尽人生百味,方知世间冷暖。 业务MQ消费者代码逻辑记得往外抛异常,进行try-catch了也要往外抛。 消息消费重试,达到重试次数进入到异常交换机、队列。消息确

    2024年02月05日
    浏览(32)
  • 进度变动实时通知-使用SocketIO实现前后端的通信(基于WebSocket的实时通信库)

    最近在接触的一个项目,将PDF上传到项目里,通过调用OCR云服务把PDF里的表格数据识别出来。在此过程中,前后端需要实时通信,对识别数据进行“进度跟踪”。因此我们采用SocketIO的通讯方式,识别中前端和后端服务建立SocketIO连接,根据事件进行数据的实时更新百分比进度

    2024年02月06日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包