在使用 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);
};
🔒 注意事项
-
泛型统一问题
Flux<ServerSentEvent<?>>是最灵活的方式。如果你用具体类型(如String),所有data都必须是那个类型。 -
序列化
Spring 会自动用 Jackson 序列化
data字段。确保你的对象(如ChatResponse)可被 JSON 序列化。 -
不要混用
Flux<T>和Flux<ServerSentEvent<T>>一旦你要控制
event类型,就必须用ServerSentEvent包装。 -
性能 & 缓冲
SSE 是单向、文本、按行发送的,适合低频到中频流。高频场景需注意背压(backpressure)。
✅ 总结
| 需求 | 解决方案 |
|---|---|
| 发送不同类型的消息 | 使用 Flux<ServerSentEvent<?>> |
| 区分消息语义(如消息/错误/控制) | 设置 .event("xxx") |
| 客户端按类型处理 | 用 addEventListener('xxx', ...) |
| 插入非聊天数据(如状态、统计) | 构造额外的 ServerSentEvent 并用 Flux.concat 合并 |
这样你就可以在一个 SSE 连接中安全、清晰地穿插多种类型的数据了。