springboot+sse 发送信息

该文章展示了如何在JavaSpring框架中利用SseEmitter进行服务器发送事件(Server-SentEvents,SSE)的实现,包括创建连接、处理超时和错误、以及在客户端通过EventSource接收和处理来自服务器的数据流。示例代码中包含了创建连接、关闭连接的方法以及对应的JSP页面交互逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package com.webstockdemo;


import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

@Slf4j
public class SseEmitterUtil {



    /**
     * 使用 map 对象缓存userId
     */
    private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();

    /**
     * 
     *  创建连接
     * @param userId 用户ID
     * @return SseEmitter
     */
    public static SseEmitter connect(String userId) {
        // 设置超时时间,0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutException
        SseEmitter sseEmitter = new SseEmitter(1L);
        // 注册回调
        sseEmitter.onCompletion(completionCallBack(userId));
        sseEmitter.onError(errorCallBack(userId));
        sseEmitter.onTimeout(timeoutCallBack(userId));

        // 缓存
        sseEmitterMap.put(userId, sseEmitter);


        log.info("创建新的sse连接,当前用户:{}", userId);
        return sseEmitter;
    }
    /**
     * 移除用户连接
     */
    public static void removeUser(String userId) {
        sseEmitterMap.remove(userId);
        log.info("移除用户:{}", userId);
    }
    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.info("连接异常:{}", userId);
            removeUser(userId);
        };
    }
}

package com.webstockdemo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Random;

@RestController
@Slf4j
public class WebStockController {

    /**
     * 用于创建连接
     */
    @CrossOrigin
    @GetMapping("/sse/connect/{userId}")
    public SseEmitter connect(@PathVariable String userId) throws IOException {
        SseEmitter connect = SseEmitterUtil.connect(userId);
        connect.send(new Random().nextInt());
        return connect;
    }


    /**
     * 关闭连接
     */
    @CrossOrigin
    @GetMapping("/sse/close/{userid}")
    public String close(@PathVariable("userid") String userid) {
        SseEmitterUtil.removeUser(userid);
        return "连接关闭";
    }

}

jsp 页面代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>消息推送</title>
</head>
<body>
    <div>
        <button onclick="closeSse()">关闭连接</button>
        <div id="message"></div>
    </div>
</body>

<script>
    let source = null;

    const userId = new Date().getTime();

    if (window.EventSource) {

        // 建立连接
        source = new EventSource('http://localhost:8080/sse/connect/' + userId);

        /**
         * 触发open事件
         * 
         */
        source.addEventListener('open', function (e) {
            setMessageInnerHTML("建立连接。。。");
        }, false);

        /**
         * 客户端收到服务器发来的数据
         * 
         */
 // 监听消息并打印
    source.onmessage = function (evt) {
        console.log("message", evt.data, evt)
    }
       source.addEventListener("me", function (evt) {
        console.log("event", evt.data)
        // 事件流如果不关闭会自动刷新请求,所以我们需要根据条件手动关闭
       // if (evt.data == 3) {
           // source.close();   
       // }

        setMessageInnerHTML(evt.data);
    })



        /**
         * 触发error事件
         * 
         * 
         */
        source.addEventListener('error', function (e) {
            if (e.readyState === EventSource.CLOSED) {
                setMessageInnerHTML("连接关闭");
            } else {
                console.log("服务器异常:"+JSON.stringify(e));
            }
        }, false);

    } else {
        setMessageInnerHTML("你的浏览器不支持SSE");
    }

    // 监听窗口关闭事件,主动去关闭sse连接,如果服务端设置永不过期,浏览器关闭后手动清理服务端数据
    window.onbeforeunload = function () {
       // closeSse();
    };

    // 关闭Sse连接
    function closeSse() {
        source.close();
        const httpRequest = new XMLHttpRequest();
        httpRequest.open('GET', 'http://localhost:8080/sse/close/' + userId, true);
        httpRequest.send();
        console.log("close");
    }

    // 将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
</script>
</html>

效果

### 实现 Spring Boot 和 Vue 之间流式输出 (SSE) #### 后端:Spring Boot 配置 为了实现在 Spring Boot 中发送 Server-Sent Events(SSE),可以创建一个控制器来广播消息给前端客户端。下面是一个简单的例子: ```java import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SseController { @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> stream() { return Flux.interval(Duration.ofSeconds(1)) .map(sequence -> "data: Message at time " + LocalDateTime.now()); } } ``` 这段代码定义了一个 REST 控制器,它通过 `/stream` 路径暴露了一个 SSE 终结点[^1]。 对于更复杂的场景,可能需要管理多个连接或向特定会话推送数据,在这种情况下应该考虑使用 `SseEmitter` 或者其他高级特性。 #### 前端:Vue.js 使用 EventSource API 接收更新 在 Vue 应用程序里可以通过 JavaScript 的内置对象 `EventSource` 来接收来自服务器的消息。这里展示怎样在一个组件内部初始化并监听这些事件: ```javascript export default { name: 'App', mounted () { const eventSource = new EventSource('/stream'); eventSource.onmessage = function(event) { console.log(`New message from server: ${event.data}`); }; eventSource.onerror = function(error) { console.error('Error occurred:', error); eventSource.close(); }; }, unmounted () { this.eventSource?.close(); // 清理资源 } }; ``` 此片段展示了如何建立到后端的持久化连接以及当接收到新信息时执行的操作。 由于原生 `EventSource` 存在局限性,如果项目需求超出其能力范围,则建议寻找第三方库作为替代方案,比如 [EventSourcePolyfill](https://github.com/Yaffle/EventSource)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java斗罗

请作者健身

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值