《Spring WebFlux 实战:基于 SSE 实现多类型事件流(支持聊天消息、元数据与控制指令混合传输)》

在使用 Server-Sent Events (SSE) 时,如果你需要在一个 Flux 流中穿插不同类型的数据 (比如:普通消息、错误提示、控制指令、元信息等),你可以通过 自定义事件类型(event type) 来区分它们。


✅ 核心机制:SSE 的 event: 字段

标准 SSE 消息格式支持以下字段:

复制代码

text

编辑

复制代码
event: <事件类型>
data: <数据内容>
id: <可选ID>
retry: <重连时间>

其中:

  • event: 字段用于指定事件类型 (默认是 message
  • 客户端可以通过 EventSource.addEventListener('xxx', ...) 监听特定类型的事件

🛠 在 Spring WebFlux 中如何发送不同类型的 SSE?

你需要返回 Flux<ServerSentEvent<T>>,而不是直接 Flux<T>。这样你才能控制 event 类型和 data 内容。

示例:混合多种事件类型

复制代码

java

编辑

复制代码
@GetMapping(value = "/generateStream1", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<?>> generateStream1(
        @RequestParam(value = "message", defaultValue = "给我讲个笑话") String message) {

    var prompt = new Prompt(new UserMessage(message));

    // 假设 chatModel.stream 返回的是 Flux<ChatResponse>
    Flux<ChatResponse> chatStream = chatModel.stream(prompt);

    // 转换为带 event type 的 ServerSentEvent
    Flux<ServerSentEvent<?>> responseEvents = chatStream.map(response ->
        ServerSentEvent.<ChatResponse>builder()
            .event("chat_message")     // 自定义事件类型
            .data(response)
            .build()
    );

    // 插入一个"开始"事件
    ServerSentEvent<Void> startEvent = ServerSentEvent.builder(Void.class)
        .event("status")
        .data(null)
        .comment("Stream started")
        .build();

    // 插入一个"系统提示"事件(比如 token 使用量)
    ServerSentEvent<Map<String, Object>> metaEvent = ServerSentEvent.builder(Map.class)
        .event("metadata")
        .data(Map.of("tokens_used", 42, "model", "gpt-4"))
        .build();

    // 合并多个事件流:先发 start,再发 meta,然后是聊天消息流
    return Flux.concat(
        Flux.just(startEvent),
        Flux.just(metaEvent),
        responseEvents,
        Flux.just(ServerSentEvent.<Void>builder().event("end").build()) // 结束事件
    );
}

🖥 客户端如何处理不同事件类型?

复制代码

javascript

编辑

复制代码
const es = new EventSource('/generateStream1?message=你好');

es.addEventListener('status', (event) => {
    console.log('状态事件:', event);
});

es.addEventListener('metadata', (event) => {
    const data = JSON.parse(event.data);
    console.log('元数据:', data);
});

es.addEventListener('chat_message', (event) => {
    const msg = JSON.parse(event.data);
    console.log('聊天消息:', msg);
});

es.addEventListener('end', () => {
    console.log('流结束');
    es.close();
});

// 默认的 message 事件(如果没指定 event)
es.onmessage = (event) => {
    console.log('默认消息:', event.data);
};

🔒 注意事项

  1. 泛型统一问题
    Flux<ServerSentEvent<?>> 是最灵活的方式。如果你用具体类型(如 String),所有 data 都必须是那个类型。

  2. 序列化

    Spring 会自动用 Jackson 序列化 data 字段。确保你的对象(如 ChatResponse)可被 JSON 序列化。

  3. 不要混用 Flux<T>Flux<ServerSentEvent<T>>

    一旦你要控制 event 类型,就必须用 ServerSentEvent 包装。

  4. 性能 & 缓冲

    SSE 是单向、文本、按行发送的,适合低频到中频流。高频场景需注意背压(backpressure)。


✅ 总结

需求 解决方案
发送不同类型的消息 使用 Flux<ServerSentEvent<?>>
区分消息语义(如消息/错误/控制) 设置 .event("xxx")
客户端按类型处理 addEventListener('xxx', ...)
插入非聊天数据(如状态、统计) 构造额外的 ServerSentEvent 并用 Flux.concat 合并

这样你就可以在一个 SSE 连接中安全、清晰地穿插多种类型的数据了。

相关推荐
virus59455 小时前
悟空CRM mybatis-3.5.3-mapper.dtd错误解决方案
java·开发语言·mybatis
没差c6 小时前
springboot集成flyway
java·spring boot·后端
时艰.7 小时前
Java 并发编程之 CAS 与 Atomic 原子操作类
java·开发语言
编程彩机7 小时前
互联网大厂Java面试:从Java SE到大数据场景的技术深度解析
java·大数据·spring boot·面试·spark·java se·互联网大厂
笨蛋不要掉眼泪7 小时前
Spring Boot集成LangChain4j:与大模型对话的极速入门
java·人工智能·后端·spring·langchain
Yvonne爱编码7 小时前
JAVA数据结构 DAY3-List接口
java·开发语言·windows·python
像少年啦飞驰点、8 小时前
零基础入门 Spring Boot:从“Hello World”到可上线微服务的完整学习指南
java·spring boot·微服务·编程入门·后端开发
眼眸流转9 小时前
Java代码变更影响分析(一)
java·开发语言
Yvonne爱编码9 小时前
JAVA数据结构 DAY4-ArrayList
java·开发语言·数据结构
阿猿收手吧!9 小时前
【C++】C++原子操作:compare_exchange_weak详解
java·jvm·c++