Spring Boot 使用 SSE 方式向前端推送数据详解

目录
  • 前言
  • 服务端
  • SSE工具类
  • 在Controller层创建 SSEController.java
  • 前端代码

前言

SSE简单的来说就是服务器主动向前端推送数据的一种技术,它是单向的,也就是说前端是不能向服务器发送数据的。SSE适用于消息推送,监控等只需要服务器推送数据的场景中,下面是使用Spring Boot 来实现一个简单的模拟向前端推动进度数据,前端页面接受后展示进度条。

服务端

在Spring Boot中使用时需要注意,最好使用Spring Web 提供的SseEmitter这个类来进行操作,我在刚开始时使用网上说的将Content-Type设置为text-stream这种方式发现每次前端每次都会重新创建接。最后参考该文实现了最终想要的效果:

SSE工具类

SSEServer.java

package vip.huhailong.catchat.sse;

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;

/**
 * @author Huhailong
 */
@Slf4j
public class SSEServer {

    /**
     * 当前连接数
     */
    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));
        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);
        };
    }
}

上面这个类可以把它当作一个SSE的工具类,下面我们使用一下它

在Controller层创建 SSEController.java

package vip.huhailong.catchat.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import vip.huhailong.catchat.sse.SSEServer;

/**
 * @author Huhailong
 */
@Slf4j
@RestController
@CrossOrigin
@RequestMapping("/sse")
public class SSEController {

    @GetMapping("/connect/{userId}")
    public SseEmitter connect(@PathVariable String userId){
        return SSEServer.connect(userId);
    }

    @GetMapping("/process")
    public void sendMessage() throws InterruptedException {
        for(int i=0; i<=100; i++){
            if(i>50&&i<70){
                Thread.sleep(500L);
            }else{
                Thread.sleep(100L);
            }
            SSEServer.batchSendMessage(String.valueOf(i));
        }
    }
}

上面的connect是用来连接sse的,它返回一个SseEmitter实例,这时候连接就已经创建了,然后下面的process接口是用来推送数据的,我这里是准备让前端实现一个进度条的效果,所以推送的是数字,为了效果明显,我在推送到50到70的时候速度放慢,其余都是100ms

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
    <script>
        let data = new EventSource("/cat-chat/sse/connect/huhailong")
        data.onmessage = function(event){
            console.log("test=>",event)
            document.getElementById("result").innerText = event.data+'%';
            document.getElementById("my-progress").value = event.data;
        }
    </script>
</head>
<body>
    <div id="result"></div>
    <progress style="width: 300px" id="my-progress" value="0" max="100"></progress>
</body>
</html>

最终效果:

到此这篇关于Spring Boot 使用 SSE 方式向前端推送数据详解的文章就介绍到这了,更多相关Spring Boot SSE内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Springboot如何基于assembly服务化实现打包

    (1)首先我们编辑 assembly.xml 配置文件,在前文的基础上新增第三方依赖设置(高亮部分),实现将第三方的 jar 包添加到压缩包里的 lib 目录: <?xml version="1.0" encoding="UTF-8"?> <assembly> <!-- 必须写,否则打包时会有 assembly ID must be present and non-empty 错误 这个名字最终会追加到打包的名字的末尾,如项目的名字为

  • Springboot基于assembly的服务化打包方案及spring boot部署方式

    在使用assembly来打包springboot微服务项目前,我想说一说,目前springboot项目的几种常见的部署方式. 使用docker容器去部署,将springboot的应用构建成一个docker image,然后通过容器去启动镜像 ,这种方式在需要部署大规模的应用和应用扩展时是非常方便的,属于目前工业级的部署方案,但是需要掌握docker的生态圈技术. 使用fatjar直接部署启动,这是很多初学者或者极小规模情况下的一个简单应用部署方式. 本文主要针对第二种部署方式提供一种更加友好的打

  • maven插件assembly使用及springboot启动脚本start.sh和停止脚本 stop.sh

    我们在项目中都会遇到项目打包,可以通过assembly对我们的项目进行打包. 1.首先看一下在打包前的项目文件结构. 2.在pom.xml中配置assembly插件 <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plug

  • Spring Boot 使用 SSE 方式向前端推送数据详解

    目录 前言 服务端 SSE工具类 在Controller层创建 SSEController.java 前端代码 前言 SSE简单的来说就是服务器主动向前端推送数据的一种技术,它是单向的,也就是说前端是不能向服务器发送数据的.SSE适用于消息推送,监控等只需要服务器推送数据的场景中,下面是使用Spring Boot 来实现一个简单的模拟向前端推动进度数据,前端页面接受后展示进度条. 服务端 在Spring Boot中使用时需要注意,最好使用Spring Web 提供的SseEmitter这个类来进

  • 利用spring boot+WebSocket实现后台主动消息推送功能

    目录 前言: 有个需求: WebSocket 主要能实现的场景: 总结 前言: 使用此webscoket务必确保生产环境能兼容/支持!使用此webscoket务必确保生产环境能兼容/支持!使用此webscoket务必确保生产环境能兼容/支持!主要是tomcat的兼容与支持. 有个需求: APP用户产生某个操作,需要让后台管理系统部分人员感知(表现为一个页面消息). 最早版本是后台管理系统轮训,每隔一段时间轮训一次,由于消息重要,每隔几秒就查一次.这样做明显很不雅!会消耗大量资源,并且大部分请求是

  • RabbitMQ延迟队列及消息延迟推送实现详解

    这篇文章主要介绍了RabbitMQ延迟队列及消息延迟推送实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 应用场景 目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如: 淘宝七天自动确认收货.在我们签收商品后,物流系统会在七天后延时发送一个消息给支付系统,通知支付系统将款打给商家,这个过程持续七天,就是使用了消息中间件的延迟推送功能. 12306 购票支付确认页面.我们在选好票点击确定跳转的页面中往往都会有倒计时,代表着 3

  • Spring Boot的filter(过滤器)简单使用实例详解

    过滤器(Filter)的注册方法和 Servlet 一样,有两种方式:代码注册或者注解注册 1.代码注册方式 通过代码方式注入过滤器 @Bean public FilterRegistrationBean indexFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(new IndexFilter()); registration.addUrlPatterns("/&quo

  • spring boot 项目利用Jenkins实现自动化部署的教程详解

    本文主要讲解利用Jenkins实现spring boot项目的自动化部署: •1.后台技术架构:Spring boot 1.4.2.RELEASE +Mybatis 3+ Redis+ Mysql 5.6+JDK8; •2.代码构建工具:maven 3.3; •3.源代码托管工具:Gitlab; •4.利用maven的docker插件打包成docker镜像在docker容器中部署项目: •5.服务器是阿里云的ECS; 一.安装Jenkins 1.阿里云服务器安装Jenkins方式见如下链接地址

  • Spring Boot中扩展XML请求与响应的支持详解

    前言 在之前的所有Spring Boot教程中,我们都只提到和用到了针对HTML和JSON格式的请求与响应处理.那么对于XML格式的请求要如何快速的在Controller中包装成对象,以及如何以XML的格式返回一个对象呢? 什么是xml文件格式 我们要给对方传输一段数据,数据内容是"too young,too simple,sometimes naive",要将这段话按照属性拆分为三个数据的话,就是,年龄too young,阅历too simple,结果sometimes naive.

  • Spring Boot 2 实战:自定义启动运行逻辑实例详解

    本文实例讲述了Spring Boot 2 实战:自定义启动运行逻辑.分享给大家供大家参考,具体如下: 1. 前言 不知道你有没有接到这种需求,项目启动后立马执行一些逻辑.比如缓存预热,或者上线后的广播之类等等.可能现在没有但是将来会有的.想想你可能的操作, 写个接口上线我调一次行吗?NO!NO!NO!这种初级菜鸟才干的事.今天告诉你个骚操作使得你的代码更加优雅,逼格更高. 2. CommandLineRunner 接口 package org.springframework.boot; impo

  • Spring Boot Actuator监控的简单使用方法示例代码详解

    Spring Boot Actuator帮助我们实现了许多中间件比如mysql.es.redis.mq等中间件的健康指示器. 通过 Spring Boot 的自动配置,这些指示器会自动生效.当这些组件有问题的时候,HealthIndicator 会返回 DOWN 或 OUT_OF_SERVICE 状态,health 端点 HTTP 响应状态码也会变为 503,我们可以以此来配置程序健康状态监控报警. 使用步骤也非常简单,这里演示的是线程池的监控.模拟线程池满了状态下将HealthInicator

  • Spring Boot如何实现定时任务的动态增删启停详解

    我以为动态停启定时任务一般用quartz,没想到还可以通过ScheduledTaskRegistrar来拓展.但是分布式场景,建议还是用quartz吧! 在 spring boot 项目中,可以通过 @EnableScheduling 注解和 @Scheduled 注解实现定时任务,也可以通过 SchedulingConfigurer 接口来实现定时任务.但是这两种方式不能动态添加.删除.启动.停止任务.要实现动态增删启停定时任务功能,比较广泛的做法是集成 Quartz 框架. 但是本人的开发原

  • spring boot+mybatis搭建一个后端restfull服务的实例详解

    1.创建一个maven项目. 2.在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="

随机推荐