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


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

    核心代码

    依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    后端接收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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    前端浏览器代码

    // 连接服务器
    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))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    完整代码

    项目目录

    $ 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    完整依赖 pom.xml

    
    <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.0modelVersion>
        <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.7.7version>
            <relativePath/> 
        parent>
    
        <groupId>com.examplegroupId>
        <artifactId>demoartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <name>demoname>
        <description>Demo project for Spring Bootdescription>
    
        <properties>
            <java.version>1.8java.version>
            <mybatis-plus.version>3.5.2mybatis-plus.version>
        properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-thymeleafartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
    
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
    
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
    
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombokgroupId>
                                <artifactId>lombokartifactId>
                            exclude>
                        excludes>
                    configuration>
                plugin>
            plugins>
        build>
    
    project>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    前端代码 index.html

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Demotitle>
    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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    定义一个返回数据 Message.java

    package com.example.demo.entity;
    
    import lombok.Data;
    
    @Data
    public class Message {
        private String data;
        private Integer total;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    定义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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    实现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();
                }
            });
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    定时任务 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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    前端路由 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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    启动类 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);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    完整代码:https://github.com/mouday/spring-boot-demo/SpringBoot-SSE

    参考文章

  • 相关阅读:
    项目经理VS产品经理,二者到底有何不同?
    react: zustand数据缓存
    论文复现--lightweight-human-pose-estimation-3d-demo.pytorch(单视角多人3D实时动作捕捉DEMO)
    交叉编译详解
    MySQL主从搭建,实现读写分离(基于docker)
    软件测试整理
    网课答题查题公众号助手搭建教程
    固体物理-复习重点
    0-1背包问题的一维数组优化解析
    数据治理-度量指标
  • 原文地址:https://blog.csdn.net/mouday/article/details/132612632