《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 连接中安全、清晰地穿插多种类型的数据了。

相关推荐
廋到被风吹走2 小时前
【Spring】常用注解分类整理
java·后端·spring
是一个Bug2 小时前
Java基础20道经典面试题(二)
java·开发语言
Z_Easen2 小时前
Spring 之元编程
java·开发语言
leoufung2 小时前
LeetCode 373. Find K Pairs with Smallest Sums:从暴力到堆优化的完整思路与踩坑
java·算法·leetcode
阿蒙Amon2 小时前
C#每日面试题-委托和事件的区别
java·开发语言·c#
宋情写2 小时前
java-IDEA
java·ide·intellij-idea
最贪吃的虎3 小时前
Git: rebase vs merge
java·运维·git·后端·mysql
资生算法程序员_畅想家_剑魔3 小时前
Java常见技术分享-12-多线程安全-锁机制
java·开发语言
一叶飘零_sweeeet3 小时前
吃透 Spring 体系结构
java·spring