本篇博客将带领你使用 Spring Boot、WebSocket 和 JavaScript 实现一个类似 ChatGPT 的流式回复效果。前端发送消息后,后端接收消息并请求 AI API,并将 AI 返回的流式响应实时推送到前端,最终在聊天界面呈现出逐字出现的打字效果。
技术原理
-
WebSocket 全双工通信: WebSocket 协议提供全双工通信通道,允许服务器和客户端之间进行双向实时数据传输,非常适合实现流式数据传输。
-
Spring WebFlux 响应式编程: Spring WebFlux 基于 Reactor 库,支持响应式流处理,可以处理 AI API 返回的流式数据。
-
SSE(Server-Sent Events): SSE 是一种基于 HTTP 的单向服务器推送技术,可以将 AI 返回的流式数据实时推送到前端。
实现步骤
一、后端实现 (Spring Boot)
- 添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
复制
- 创建 WebSocket 处理器
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Flux; import java.time.Duration; @Controller public class ChatController { @Autowired private SimpMessagingTemplate messagingTemplate; @Autowired private WebClient webClient; // 用于调用 AI API @MessageMapping("/chat.sendMessage") // 接收来自客户端的消息 public void sendMessage(@Payload Message message) throws Exception { // 构造 AI API 请求 ApiRequest apiRequest = new ApiRequest(message.getContent()); // 假设 AI API 接受 ApiRequest 对象 // 调用 AI API,获取流式响应 Flux<String> apiResponse = callAiApi(apiRequest); // 处理 AI API 响应,并逐条发送给前端 apiResponse.delayElements(Duration.ofMillis(50)) // 模拟打字延迟 .subscribe(chunk -> { Message responseMessage = new Message("AI", chunk); messagingTemplate.convertAndSend("/topic/chat", responseMessage); }); } // 调用 AI API private Flux<String> callAiApi(ApiRequest request) { // 将 ApiRequest 对象转换为 JSON 字符串 // ... // 发送 POST 请求,并指定返回数据类型为 Flux<String> return webClient.post() .uri("https://api.example.com/ai-api") // 替换为实际的 AI API 地址 .bodyValue(request) .retrieve() .bodyToFlux(String.class); } // 消息类 public static class Message { private String sender; private String content; // 构造函数,getter 和 setter } // AI API 请求类 public static class ApiRequest { private String prompt; // 构造函数,getter 和 setter } }
复制
- WebSocket 配置
import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); } }
复制
二、前端实现 (JavaScript)
<!DOCTYPE html> <html> <head> <title>Chat Application</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.1/sockjs.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script> </head> <body> <div id="chat-container"> <div id="chat-output"></div> <input type="text" id="message-input" placeholder="Enter message..."> <button onclick="sendMessage()">Send</button> </div> <script> // 连接 WebSocket var socket = new SockJS('/ws'); var stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { console.log('Connected: ' + frame); // 订阅聊天频道 stompClient.subscribe('/topic/chat', function(message) { showMessage(JSON.parse(message.body)); }); }); // 发送消息 function sendMessage() { var message = document.getElementById('message-input').value; stompClient.send("/app/chat.sendMessage", {}, JSON.stringify({ 'sender': 'user', // 可替换为实际用户名 'content': message })); } // 显示消息 function showMessage(message) { var chatOutput = document.getElementById('chat-output'); var messageElement = document.createElement('div'); if (message.sender === 'AI') { // AI 回复逐字显示 typeWriter(messageElement, message.content, 0); } else { messageElement.innerHTML = '<strong>' + message.sender + ':</strong> ' + message.content; chatOutput.appendChild(messageElement); } } // 模拟打字效果 function typeWriter(element, text, i) { if (i < text.length) { element.innerHTML += text.charAt(i); setTimeout(function() { typeWriter(element, text, i + 1); }, 50); // 调整打字速度 } else { document.getElementById('chat-output').appendChild(element); } } </script> </body> </html>
复制
总结
本文介绍了如何使用 Spring Boot 和 WebSocket 实现类似 ChatGPT 的流式回复效果,并详细讲解了每个步骤的技术原理和代码实现。通过这种方式,我们可以构建更加 engaging 和 interactive 的 Web 应用,为用户带来全新的体验。