在当今实时交互体验至上的时代,像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则更为轻量级。无论选择哪种方案,掌握长连接和流式数据处理都是构建现代化实时交互应用的关键。
希望本文能帮助你更好地入门去理解和应用实时流式响应技术,为用户打造更加流畅、自然的交互体验!
后续还会有更加深入去理解和实现技术!