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

相关推荐
期待のcode几秒前
Java虚拟机的运行模式
java·开发语言·jvm
程序员老徐3 分钟前
Tomcat源码分析三(Tomcat请求源码分析)
java·tomcat
a程序小傲12 分钟前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
仙俊红14 分钟前
spring的IoC(控制反转)面试题
java·后端·spring
阿湯哥15 分钟前
AgentScope Java 集成 Spring AI Alibaba Workflow 完整指南
java·人工智能·spring
小楼v25 分钟前
说说常见的限流算法及如何使用Redisson实现多机限流
java·后端·redisson·限流算法
与遨游于天地38 分钟前
NIO的三个组件解决三个问题
java·后端·nio
czlczl200209251 小时前
Guava Cache 原理与实战
java·后端·spring
yangminlei1 小时前
Spring 事务探秘:核心机制与应用场景解析
java·spring boot
记得开心一点嘛2 小时前
Redis封装类
java·redis