首页 > 编程语言 >Java:SpringBoot整合SSE(Server-Sent Events)实现后端主动向前端推送数据

Java:SpringBoot整合SSE(Server-Sent Events)实现后端主动向前端推送数据

时间:2023-09-03 12:32:22浏览次数:46  
标签:Java SpringBoot demo boot springframework Server org import com


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
     */
    @Override
    public 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
     */
    @Override
    public 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 {
    @Autowired
    private 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 {

    @Autowired
    private 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")
    @ResponseBody
    public 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

参考文章


标签:Java,SpringBoot,demo,boot,springframework,Server,org,import,com
From: https://blog.51cto.com/mouday/7339467

相关文章

  • Java:SpringBoot使用AES对JSON数据加密和解密
    目录1、加密解密原理2、项目示例2.1、项目结构2.2、常规业务代码2.3、加密的实现2.4、接口测试2.5、总结1、加密解密原理客户端和服务端都可以加密和解密,使用base64进行网络传输加密方字符串->AES加密->base64解密方base64->AES解密->字符串2、项目示例2.1、项目结构$tr......
  • Java:使用javax.crypto.Cipher的AES算法实现数据加密解密
    AES算法加密Stringalgorithm="AES/ECB/PKCS5Padding";//定义加密算法Stringkey="1234567890123456";//这是待加密的信息Stringmessage="HelloWorld.";//这是待加密的信息Ciphercipher=Cipher.getInstance(algorithm);cipher.init(Cipher.ENCRYPT......
  • Java中为什么加了一个类,引用不到呢
    在Java中,如果你加入了一个新的类但无法引用到它,可能是由以下几个原因引起的:缺少导入语句:如果你新增的类位于另一个包中,或者是外部的第三方库类,你需要使用import语句将其导入到当前的Java文件中。例如:importcom.example.MyClass;。确保导入语句正确并且没有拼写错误。类不在类路径......
  • SpringBoot集成redis集群
    1、添加依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><!--过滤lettuce,使用jedis作为redis客户端--><exclusion&......
  • Java常用四大线程池用法以及ThreadPoolExecutor详解(转)
    为什么用线程池?1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处-理效率2.线程并发数量过多,抢占系统资源从而导致阻塞3.对线程进行一些简单的管理在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类,学习Java中的线程池,就可以......
  • 无涯教程-JavaScript - NEGBINOMDIST函数
    NEGBINOMDIST函数取代了Excel2010中的NEGBINOM.DIST函数。描述该函数返回负二项式分布。NEGBINOMDIST返回在第number_s次成功之前出现number_f次失败的概率,而成功的恒定概率是概率_s。该函数与二项式分布相似,不同之处在于成功次数是固定的,而试验次数是可变的。像二项式一......
  • 一键配置Java环境变量
    后缀改成bat运行@echooffsetJAVA_HOME=D:\ProgramFiles\Java\jdk-11setPATH=%PATH%;%%JAVA_HOME%%\bin;%%JAVA_HOME%%\jre\binsetCLASSPATH=.;%%JAVA_HOME%%\lib\dt.jar;%%JAVA_HOME%%\lib\tools.jarsetRegV=HKLM\SYSTEM\CurrentControlSet\Control\Ses......
  • java线程池七大参数(转)
    转:https://blog.csdn.net/ye17186/article/details/89467919从源码来看,线程池构造有七个参数,corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler一、corePoolSize核心线程池大小,线程池会维护一个最小线程数量,即使这些线程空闲,也不会被销......
  • JavaScript-console 对象
    console对象console对象是JavaScript的原生对象,它有点像Unix系统的标准输出stdout和标准错误stderr,可以输出各种信息到控制台,并且还提供了很多有用的辅助方法。console的常见用途有两个。调试程序,显示网页代码运行时的错误信息。提供了一个命令行接口,用来与网页代码互动。cons......
  • 无涯教程-JavaScript - MODE函数
    MODE函数取代了Excel2010中的MODE.SNGL函数。描述该函数返回数组或数据范围中最频繁出现或重复的值。语法MODE(number1,[number2],...)争论Argument描述Required/OptionalNumber1Thefirstnumberargumentforwhichyouwanttocalculatethemode.RequiredNu......