在当今实时交互体验至上的时代,像ChatGPT那样流畅的流式回复体验越来越受欢迎。本文将深入浅出地讲解如何使用前端技术与Spring后端强强联手,打造令人惊艳的流式回复功能。
一、基本概念
在正式开始之前,我们先来了解一些基础概念:
- 长连接: 区别于传统的HTTP请求-响应模式,长连接允许服务器主动向客户端推送数据,而无需等待客户端发起请求。
- 流式传输: 数据并非一次性传输完毕,而是像流水一样持续地传输,让接收方可以实时处理。
- WebSocket: HTML5提供的全双工通信协议,能够实现浏览器与服务器之间的双向实时通信。
- SSE (Server-Sent Events): 服务端推送技术,浏览器通过HTTP连接接收服务器单向推送的数据流。
- Spring WebFlux: Spring生态系统中用于构建反应式应用程序的框架,支持WebSocket和SSE,能够处理流式数据。
二、为什么需要实时流式响应
传统的HTTP请求-响应模式下,客户端必须不断地发送请求才能获取最新的数据,这种方式效率低下且资源消耗大。而长连接允许服务器主动推送数据,实时性更高,也更加节省资源。
三、技术选型与原理
1. 前端:
- WebSocket: HTML5提供的全双工通信协议,完美契合长连接和流式数据传输的需求。
- SSE (Server-Sent Events): 服务端推送技术,浏览器通过HTTP连接接收服务器单向推送的数据流。
2. 后端 (Spring):
- Spring WebFlux: Spring生态系统中用于构建反应式应用程序的框架,支持WebSocket和SSE。
3. 原理:
- 建立连接: 前端通过WebSocket或SSE与后端建立持久连接。
- 发送请求: 前端发送消息请求到后端。
- 流式处理: 后端接收请求后,不等待完整结果,而是将处理过程中的中间结果(例如逐字生成的回复)实时推送给前端。
- 动态展示: 前端接收数据流,并动态更新页面内容,实现逐字显示的效果。
四、WebSocket方案实现
4.1 技术原理
- 建立连接:
- 前端使用
new WebSocket()
创建WebSocket对象,连接到后端指定的WebSocket地址。 - 后端使用Spring WebFlux提供的
WebSocketHandler
接口处理WebSocket连接请求。
- 前端使用
- 数据传输:
- 前端通过
WebSocket.send()
发送消息到后端。 - 后端通过
WebSocketSession.send()
方法向客户端推送消息。 - 前后端都可以监听
onmessage
事件来接收对方发送的消息。
- 前端通过
- 关闭连接:
- 前端或后端都可以调用
WebSocket.close()
方法关闭连接。
- 前端或后端都可以调用
4.2. 后端 (Spring WebFlux):
@Component
public class ChatWebSocketHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
// 接收消息
return session.receive()
.map(WebSocketMessage::getPayloadAsText)
.flatMap(message -> {
// 处理消息,例如调用ChatGPT API
return processMessage(message);
})
.flatMap(reply -> {
// 将回复逐字发送
return Flux.fromIterable(reply.split(""))
.delayElements(Duration.ofMillis(50)) // 模拟延迟
.map(session::textMessage)
.map(session::send);
})
.then();
}
// 模拟调用ChatGPT API并逐字返回回复
private Mono<String> processMessage(String message) {
return Mono.just("收到消息:" + message + ",正在思考中...").delayElement(Duration.ofSeconds(1))
.flatMap(initialReply -> {
return Flux.range(0, initialReply.length())
.map(i -> initialReply.substring(0, i + 1))
.delayElements(Duration.ofMillis(100)) // 模拟逐字生成
.reduce((a, b) -> b) // 只取最后一个结果
.cast(String.class);
});
}
}
4.3. 前端 (JavaScript):
const socket = new WebSocket('ws://localhost:8080/chat');
socket.onopen = () => {
console.log('WebSocket 连接已建立');
};
socket.onmessage = (event) => {
const message = event.data;
document.getElementById('chat-output').innerHTML += message;
};
function sendMessage() {
const message = document.getElementById('chat-input').value;
socket.send(message);
}
五、SSE方案实现
4.1 技术原理
- 建立连接: 前端使用
new EventSource()
创建EventSource对象,连接到后端指定的SSE接口。 - 数据传输:
- 后端通过
Flux<ServerSentEvent<T>>
返回数据流,Spring WebFlux会自动将数据流转换为SSE格式。 - 前端监听
onmessage
事件接收后端推送的数据。
- 后端通过
- 关闭连接: 前端调用
EventSource.close()
关闭连接,或者后端停止发送数据。
4.2 代码示例
后端 (Spring WebFlux):
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamEvents() {
return Flux.interval(Duration.ofMillis(100))
.map(sequence -> ServerSentEvent.<String>builder()
.data("数据流 - " + sequence)
.build());
}
前端 (JavaScript):
const eventSource = new EventSource('/stream');
eventSource.onmessage = (event) => {
const message = event.data;
document.getElementById('output').innerHTML += message + '<br>';
};
六、方案比较
特点 | WebSocket | SSE |
---|---|---|
连接类型 | 全双工 | 单向(服务器到客户端) |
数据传输 | 双向 | 单向 |
应用场景 | 需要双向实时通信,如聊天、游戏 | 服务器推送数据,如实时更新、通知 |
复杂度 | 较高 | 较低 |
七、总结
本文只是大概介绍了如何利用WebSocket和SSE两种技术实现前端与Spring后端的长连接,并以ChatGPT的流式回复为例,给出了具体的代码实现和技术原理分析。
在实际应用中,需要根据具体需求选择合适的方案。如果需要双向实时通信,WebSocket是更优选择;如果只需服务器单向推送数据,SSE则更为轻量级。无论选择哪种方案,掌握长连接和流式数据处理都是构建现代化实时交互应用的关键。
希望本文能帮助你更好地入门去理解和应用实时流式响应技术,为用户打造更加流畅、自然的交互体验!
后续还会有更加深入去理解和实现技术!