springboot整合SSE技术开发经验总结及心得

news2025/3/10 15:13:21

springboot整合SSE技术开发经验总结及心得

  • 一、开发背景
  • 二、快速了解SSE
    • 1、概念
    • 2、特性
  • 三、开发思路
  • 四、代码演示
    • 1、引入依赖
    • 2、服务端代码
    • 3、后端定时任务代码
  • 4、解决乱码的实体类
    • 4、前端代码
  • 五、核心代码分析

一、开发背景

公司需要开发一个大屏界面,大屏页面的数据是实时更新的,由后端主动实时推送数据给大屏页面。此时会立刻联想到:websocket 技术。当然使用websocket,确实可以解决这个场景。但是今天本文的主角是 :SSE,他和websocket略有不同,SSE只能由服务端主动发消息,而websocket前后端都可以推送消息。

二、快速了解SSE

1、概念

SSE全称 Server Sent Event,顾名思义,就是服务器发送事件,所以也就注定了他 只能由服务端发送信息。

2、特性

  • 主动从服务端推送消息的技术
  • 本质是一个HTTP的长连接
  • 发送的是一个stream流,格式为text/event-stream

三、开发思路

要实现后端的实时推送消息,前台实时更新数据,思路如下:

  • 1、前后端需要建立连接
  • 2、后端如何做到实时推送信息呢?可以采用定时调度

四、代码演示

1、引入依赖

原则上是不需要引入的,因为springboot底层已经整合了SSE

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2、服务端代码

controller层


@RestController
@CrossOrigin
@RequestMapping("/sse")
public class SseEmitterController extends BaseController {

    @Autowired
    private SseEmitterService sseEmitterService;

    /**
     * 创建SSE连接
     *
     * @return
     */
    @GetMapping("/connect/{type}")
    public SseEmitter connect(@PathVariable("type") String type) {
        return sseEmitterService.connect(type);
    }
}

service层

public interface SseEmitterService {

    SseEmitter connect(String type);

    void volumeOverview();

    void sysOperation();

    void monitor();
    ........
}

service实现层


@Service
public class SseEmitterServiceImpl implements SseEmitterService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static Map<String, SseEmitterUTF8> sseCache = new ConcurrentHashMap<>();


    /**
     * 创建连接sse
     * @param type
     * @return
     */
    @Override
    public SseEmitter connect(String type) {

        if (sseCache.containsKey(type)){
            return sseCache.get(type);
        }
        SseEmitterUTF8 sseEmitter = new SseEmitterUTF8(0L);
        try {
            sseEmitter.send(SseEmitter.event().comment("创建连接成功 !!!"));
        } catch (IOException e) {
            logger.error("创建连接失败 , {} " , e.getMessage());
        }
        sseEmitter.onCompletion(() -> {
            logger.info("connect onCompletion , {} 结束连接 ..." , type);
            removeUser(type);
        });
        sseEmitter.onTimeout(() -> {
            logger.info("connect onTimeout , {} 连接超时 ..." , type);
            removeUser(type);
        });
        sseEmitter.onError((throwable) -> {
            logger.error("connect onError , {} 连接异常 ..." , type);
            removeUser(type);
        });
        sseCache.put(type, sseEmitter);

        //立即推送
        volumeOverview();
        dealResp();
        monitor();
        if (type.equals(SseEmitterConstant.OVER_VIEW)){
            sysOperation();
            mileStone();
        }
        logger.info("当前用户总连接数 : {} " , sseCache.size());
        return sseEmitter;
    }

    /**
     * 交易量概览
     */
    @Override
    public void volumeOverview() {

        Map<String,Object> map = new HashMap<>();
        map.put("latest_tps",440.3);
        map.put("total_cics_trans",341656001);
        map.put("total_zjcx_trans",391656001);
        map.put("zjcx_tps",23657);
        map.put("day10",48388352);
        map.put("history",105013985);

        SseEmitter.SseEventBuilder data = SseEmitter.event().name(SseEmitterConstant.VOLUME_OVERVIEW).data(map, MediaType.APPLICATION_JSON);

        for (Map.Entry<String, SseEmitterUTF8> entry : sseCache.entrySet()) {
            SseEmitterUTF8 sseEmitter = entry.getValue();
            if (sseEmitter == null) {
                continue;
            }
            try {
                sseEmitter.send(data);
            } catch (IOException e) {
                String body = "SseEmitterServiceImpl[volumeOverview  ]";
                logger.error(body + ": 向客户端 {} 推送消息失败 , 尝试进行重推 : {}", entry.getKey() ,e.getMessage());
                messageRepush(entry.getKey(),data,body);
            }

        }
    }
		private void messageRepush(String type, SseEmitter.SseEventBuilder data,String body){
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(2000);
                SseEmitterUTF8 sseEmitter = sseCache.get(type);
                if (sseEmitter == null) {
                    logger.error(body + " :向客户端{} 第{}次消息重推失败,未创建长链接", type, i + 1);
                    continue;
                }
                sseEmitter.send(data);
            } catch (Exception ex) {
                logger.error(body + " :向客户端{} 第{}次消息重推失败", type, i + 1, ex);
                continue;
            }
            logger.info(body + " :向客户端{} 第{}次消息重推成功", type, i + 1);
            return;
        }
    }

常量类

public class SseEmitterConstant {

    /**
     * 创建连接的客户端类型
     */
    public static final String OVER_VIEW = "overview";


    /**
     * even 数据类型
     */
    public static final String VOLUME_OVERVIEW = "vw";



    public SseEmitterConstant(){}
}

3、后端定时任务代码

采用注解的方式实现:@Scheduled,使用该注解时,需要增加这个注解@EnableScheduling,相当于来开启定时调度功能,如果不加@EnableScheduling注解,那么定时调度会不生效的。

启动类增加注解@EnableScheduling

package com.hidata;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableScheduling
public class HidataApplication {

    public static void main(String[] args)
    {
        SpringApplication.run(HidataApplication.class, args);
        System.out.println("[HiUrlShorter platform startup!]");
    }
}

创建 定时任务调度类,在该类上加上@Scheduled注解,


@Configuration
public class SendMessageTask{

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SseEmitterService sseEmitterService;

    @Scheduled(cron = "0/40 * * * * ?}")
    public void volumeOverviewTask() {

        try {
            sseEmitterService.volumeOverview();
        } catch (Exception e) {
            logger.error("SendMessageTask [volumeOverviewTask]: {} ",e.getMessage());
        }
    }
.......
}


4、解决乱码的实体类

如果发送中文数据的时候,会出现乱码的现象。此时需要做对应的处理

package com.hidata.devops.lagrescreen.domain;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.nio.charset.StandardCharsets;

public class SseEmitterUTF8 extends SseEmitter {

    public SseEmitterUTF8(Long timeout) {
        super(timeout);
    }

    @Override
    protected void extendResponse(ServerHttpResponse outputMessage) {
        super.extendResponse(outputMessage);

        HttpHeaders headers = outputMessage.getHeaders();
        headers.setContentType(new MediaType(MediaType.TEXT_EVENT_STREAM, StandardCharsets.UTF_8));
    }
}


4、前端代码

    // 连接服务器
    var sseSource = new EventSource("http://localhost:8080/sse/connect");
    // 连接打开
    sseSource.onopen = function () {
        console.log("连接打开");
    }

    // 连接错误
    sseSource.onerror = function (err) {
        console.log("连接错误:", err);
    }
    
	//接收信息
    eventSource.addEventListener("vw", function (event) {
    console.log(event.data);
    .....
  });

五、核心代码分析

先看代码片段

SseEmitter.event().name("vw").data(map, MediaType.APPLICATION_JSON);

分析:
后端不会把所有数据一起发送给前端,而是会把页面分成多个模块,然后发给前端,此时前端需要区分哪一块数据对应哪一块页面。所以我们可以给各个模块的数据起个名字。也就是上述的代码

SseEmitter.event().name("vw")

这样,前端就知道怎么渲染页面了,类似于这样
在这里插入图片描述
关于even()的属性,可以查看源码,

public interface SseEventBuilder {
        SseEmitter.SseEventBuilder id(String var1);

        SseEmitter.SseEventBuilder name(String var1);

        SseEmitter.SseEventBuilder reconnectTime(long var1);

        SseEmitter.SseEventBuilder comment(String var1);

        SseEmitter.SseEventBuilder data(Object var1);

        SseEmitter.SseEventBuilder data(Object var1, @Nullable MediaType var2);

        Set<DataWithMediaType> build();
    }

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1203361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年武汉市中级工程师职称申报流程

很多人评审职称不知道职称申报需要走哪些步骤&#xff0c;不是很了解职称申报的一个过程&#xff0c;今天秋禾火告诉大家武汉职称申报流程是哪些&#xff0c;带你快速了解 首先是职称申报方式 职称评审目前是系统网上申报和线下送审纸质材料的双结合方式申报个人通过“湖北省…

相机以及其它传感器传感器

深度相机点云质量对比 比较点云质量时需要注意的点&#xff1a; 1.对特殊材质、颜色的检测效果&#xff1a;透明塑料、金属、毛玻璃、高反光物体&#xff08;镜子、水坑&#xff09;、吸光物体&#xff08;黑色物体&#xff09;。 2.特殊环境&#xff1a;雨、雪、雾、明暗交替位…

mNetAssist网络调试助手编译

1.现在的问题 很多都是用百度网盘下载&#xff0c;访问github才能下载源码。 2.码云下载地址 这个已经是7年前编辑的了&#xff0c;是从github上复制到码云的。 cheni/mNetAssist 3.下载后用QT编译 图示中的两行要注释掉&#xff0c;用//&#xff0c;作者也有备注QT4.8要用…

Docker 和 Kubernetes:技术相同和不同之处

Docker和Kubernetes是当今最流行的容器化技术解决方案。本文将探讨Docker和Kubernetes的技术相似之处和不同之处&#xff0c;以帮助读者更好地理解这两种技术。 Docker和Kubernetes&#xff1a;当今最流行的容器化技术解决方案 在当今的IT领域&#xff0c;Docker和Kubernetes无…

【Linux】进程概念IV 进程地址空间

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法…感兴趣就关注我吧&#xff01;你定不会失望。 本篇导航 0. 数据在内存中的分布1. 虚拟地址与真实物理地址2. 进程地址空间2.1 进程地址空间概念2.2 进程->页表->内存 0. 数据在内…

Prometheus+Ansible+Consul实现服务发现

一、简介 1、Consul简介 Consul 是基于 GO 语言开发的开源工具&#xff0c;主要面向分布式&#xff0c;服务化的系统提供服务注册、服务发现和配置管理的功能。Consul 提供服务注册/发现、健康检查、Key/Value存储、多数据中心和分布式一致性保证等功能。 在没有使用 consul 服…

解析浏览器的事件循环机制:理解JavaScript运行时的执行顺序

解析浏览器的事件循环机制&#xff1a;理解JavaScript运行时的执行顺序 前言定义执行顺序异步任务概念&#xff1a;微任务、宏任务宏任务有哪些&#xff1f;微任务有哪些 实例代码与图解 前言 因为防止在多个用户同时在浏览器中操作一个DOM节点所带来的复杂性&#xff0c;故Ja…

ADFS 高可用配置 + NLB配置(Windows网络负载均衡)

ADFS 高可用配置 NLB配置&#xff08;Windows网络负载均衡&#xff09; ADFS安装配置NLB配置节点 TEST-ADFS-01 网络负载平衡配置节点 TEST-ADFS-02 网络负载平衡修改CRM配置 ADFS实现高可用负载均衡有两种&#xff0c;主要是在数据库的选择方式&#xff1a; windows自带的内…

AP/PF PLASMA电源维修等离子变频电源PF23V-A1-138

维修包括&#xff1a;PECVD、MOCVD、IONIMP,PLASMA的设备电源,包括直流、高压、脉冲、射频、微波、匹配器、RPSC、CHILLER等。电源维修的时候&#xff0c;需要检测一下各功率器件是否存在击穿短路&#xff0c;例如电源整流桥堆、开关管、高频大功率整流管、浪涌电流的大功率电阻…

# Spring事务与分布式事务

一、事务的具体定义 事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元&#xff0c;组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交&#xff0c;只要其中任一操作执行失败&#xff08;出现异常&#xff09;&#xff0c;都将导致整个事务…

抖音自动发评论之论人工智能AI的应用和发展趋势

人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;是目前国际上热门的科技话题之一。它是计算机科学中的一个分支&#xff0c;旨在创造能够智能地工作、学习、感知、理解和决策的机器。人工智能的应用范围十分广泛&#xff0c;包括语音识别、自然语言处理…

国际阿里云:提高CDN缓存命中率教程!!!

CDN缓存命中率低会导致源站压力大&#xff0c;静态资源访问效率低。您可以根据导致CDN缓存命中率低的具体原因&#xff0c;选择对应的优化策略来提高CDN的缓存命中率。 背景信息 CDN通过将静态资源缓存在CDN节点上实现资源访问加速。当客户端访问某资源时&#xff0c;如果CDN节…

群晖Docker(Container Manager)中安装Home Assistant Container

群晖Docker&#xff08;Container Manager&#xff09;中安装Home Assistant Container 不要使用 套件里面的 Home Assistant&#xff0c;不利于后期拓展 方式一&#xff1a; docker run -d --name"home-assistant-1" -v /volume1/docker/homeassistant/config:/c…

基于JAX-WS实现RESTful形式的web服务端点(endpoint)

RESTful形式的web服务使用XML/HTTP绑定&#xff0c;实现jakarta.xml.ws.Provider。 服务端的实现类用jakarta.xml.ws.WebServiceProvider和BindingType(valueHTTPBinding.HTTP_BINDING)注释。其中HTTPBinding.HTTP_BINDING表示使用XML/HTTP绑定。 例如&#xff1a; WebServic…

常用网络命令ping、arp、tracert、route的详细用法,弄懂立马成大神

你们好&#xff0c;我的网工朋友。 做网工&#xff0c;你遇到最多的问题是啥&#xff1f;大部分人的回答都是网络故障吧。 所以有很多朋友多次问到&#xff0c;如何检测网络故障和对网络进行基础的操作。比如&#xff1a;网络经常掉线、网络时好时坏&#xff0c;ip地址冲突、…

BUUCTF刷题十一道【缺】(10)

文章目录 EasyBypass[SCTF2019]Flag Shop[BSidesCF 2019]SVGMagic[极客大挑战 2020]Greatphp[GYCTF2020]Easyphp【留坑-反序列化】[HarekazeCTF2019]Avatar Uploader 1[FireshellCTF2020]Caas[ISITDTU 2019]EasyPHP[N1CTF 2018]eating_cms[GYCTF2020]Ez_Express【留坑-nodejs原…

塑料质量检测是确保产品制造和装配过程的关键环节

激光塑料透光率检测是一种有效的塑料材料特性检测方法。在激光束通过上层透明材料后&#xff0c;被下层材料吸收。上层材料可以是透明的或者是有颜色的&#xff0c;但是必须能够保证有足够的激光通过。 塑料质量检测是确保产品制造和装配过程的关键环节。通过激光塑料透光率检测…

C语言—字符串连接函数

strcpy_s&#xff1a;拷贝整个字符串 #include <stdio.h> #include <string.h>int main() {char str1[] "first stringiiii";char str2[] "second string";char str3[100];strcpy_s(str1, sizeof(str1) / sizeof(str1[0]), str2);strcpy_s(…

datax 搭建使用

文章目录 datax 环境搭建使用一、解压文件二、配置 json 文件三、执行命令 datax 环境搭建使用 用于全量同步 一、解压文件 将包上传至服务器 输入命令&#xff1a; tar -zxvf datax.tar.gz -C /opt/module/ 将包 解压到 /opt/module 目录 解压完之后&#xff0c;不需要任何…

双通道、8位、MCP47CMD02T-E/MG、MCP47CMD02T-E/MF、MCP47CMD02T-E/UN数模转换器(DAC)适合消费和工业应用

典型应用&#xff1a; •设定点或偏移微调 •传感器校准 •低功耗便携式仪器仪表 •个人电脑外围设备 •数据采集系统 特性 8位分辨率 轨到轨输出 快速建立时间&#xff1a;4s&#xff08;典型值&#xff09; I2C接口 非易失性存储器&#xff08;MTP&#xff09;&#xff1a…