在这系列文章中,我们将一起探索如何搭建一个支持大模型集成项目 NexLM 的开发过程,从 架构设计 到 代码实战,逐步搭建一个支持 多种大模型(GPT-4、DeepSeek 等) 的 一站式大模型集成与管理平台,并集成 认证中心、微服务、流式对话 等核心功能。
系列目录规划:
在上一章中,我们使用 SSE (Server-Sent Events) 实现了 AI 生成式流式对话,但是 SSE 只能 单向推送数据,无法支持 用户在对话过程中发送控制指令(如 停止响应、调整模型、动态修改参数)。
对比项 | SSE (Server-Sent Events) | WebSocket |
---|---|---|
通信方向 | 单向 (Server → Client) | 双向 (Server ↔ Client) |
协议 | HTTP/1.1 + EventSource | WebSocket 协议 |
连接方式 | 基于 HTTP 长连接 | 需要单独 升级协议 |
消息格式 | 纯文本(也支持 JSON) | 二进制 / JSON / 文本 |
断线重连 | 浏览器自动重连 | 需要手动实现重连逻辑 |
服务器推送 | 天然支持(适合 AI 生成式内容) | 需要额外实现 |
服务器压力 | 轻量级(基于 HTTP/1.1) | 服务器需要维护更多连接 |
浏览器支持 | 所有现代浏览器支持 | 需要 支持 WebSocket |
适用场景 | AI 流式输出、日志推送、股票行情 | 实时聊天、双向交互、多人协作 |
核心区别:
本章,我们将升级方案,使用 WebSocket 实现 双向通信,使 AI 对话更智能、更可交互。(上一节回顾)
与 SSE 相比,WebSocket 具有以下优点:
后端:使用 Spring Boot 搭建 WebSocket 服务器,实现 AI 对话流式返回。
前端:用 WebSocket
对接服务器,实现 用户消息发送 & AI 流式回复。
增强功能:
在 pom.xml
添加 WebSocket 依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
在 WebSocketChatHandler.java
实现 双向通信逻辑:
@Component
public class WebSocketChatHandler extends TextWebSocketHandler {
private final DeepSeekClient deepSeekClient;
public WebSocketChatHandler(DeepSeekClient deepSeekClient) {
this.deepSeekClient = deepSeekClient;
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("WebSocket 连接成功:" + session.getId());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String userMessage = message.getPayload();
System.out.println("用户发送消息:" + userMessage);
try {
// 调用 AI 大模型接口,并流式返回消息
deepSeekClient.streamChatWs(userMessage, session);
} catch (Exception e) {
session.sendMessage(new TextMessage("AI 服务异常,请稍后重试。"));
e.printStackTrace();
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
System.out.println("WebSocket 连接关闭:" + session.getId());
}
}
在 WebSocketConfig.java
里开启 WebSocket 并配置路由:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private DeepSeekClient deepSeekClient;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketChatHandler(deepSeekClient), "/api/ai/chat/websocket").setAllowedOrigins("*");
}
}
路由说明:
ws://localhost:8080/api/ai/chat/websocket
setAllowedOrigins("*")
)<script>
let ws;
let isStreaming = false; // 是否正在接收 AI 回复
function initWebSocket() {
ws = new WebSocket("ws://localhost:8080/web/api/ai/chat/websocket");
ws.onopen = function() {
console.log("WebSocket 连接成功!");
document.getElementById("stopButton").disabled = false; // 允许停止
};
ws.onmessage = function(event) {
if (!isStreaming) return; // 如果停止了,就不处理新消息
appendBotMessage(event.data);
};
ws.onerror = function(error) {
console.error("WebSocket 错误: ", error);
};
ws.onclose = function() {
console.log("WebSocket 连接关闭");
document.getElementById("stopButton").disabled = true; // 禁用停止按钮
};
}
function sendMessage() {
let input = document.getElementById("userInput");
let message = input.value.trim();
if (message === "") return;
let modelType = document.getElementById("modelType").value;
appendUserMessage(message);
// 如果 WebSocket 关闭了,重新连接
if (!ws || ws.readyState === WebSocket.CLOSED) {
initWebSocket();
setTimeout(() => ws.send(JSON.stringify({ modelType: modelType, message: message })), 500);
} else {
ws.send(JSON.stringify({ modelType: modelType, message: message }));
}
isStreaming = true; // 允许接收 AI 回复
input.value = "";
document.getElementById("stopButton").disabled = false; // 启用“停止”按钮
}
function stopStreaming() {
isStreaming = false; // 停止接收数据
if (ws) {
ws.close(); // 关闭 WebSocket 连接
}
document.getElementById("stopButton").disabled = true; // 禁用“停止”按钮
}
function appendUserMessage(text) {
let chatBox = document.getElementById("chatBox");
let userMessage = document.createElement("div");
userMessage.className = "message user-message";
userMessage.innerText = text;
chatBox.appendChild(userMessage);
chatBox.scrollTop = chatBox.scrollHeight;
}
function appendBotMessage(text) {
let chatBox = document.getElementById("chatBox");
let lastMessage = chatBox.lastElementChild;
if (lastMessage && lastMessage.classList.contains("bot-message")) {
lastMessage.innerText += text;
} else {
let botMessage = document.createElement("div");
botMessage.className = "message bot-message";
botMessage.innerText = text;
chatBox.appendChild(botMessage);
}
chatBox.scrollTop = chatBox.scrollHeight;
}
// 初始化 WebSocket 连接
initWebSocket();
</script>
新增一个"停止回答"按钮可随时停止。
<div class="chat-container">
<h1>AI 双向流式聊天h1>
<div class="chat-box" id="chatBox">div>
<div class="input-area">
<input type="text" id="userInput" placeholder="请输入问题...">
<select id="modelType">
<option value="chatgpt">ChatGPToption>
<option value="deepseek">DeepSeekoption>
<option value="local">本地模型option>
select>
<button onclick="sendMessage()">发送button>
<button id="stopButton" onclick="stopStreaming()" disabled>停止回答button>
div>
div>
启动 Spring Boot WebSocket 服务
访问 chat
页面,测试 WebSocket 交互
✅ 支持 AI 生成式内容
OpenAI API
或 大模型
生成 流式 AI 响应✅ 支持会话上下文
✅ 实现重连机制
方案 | SSE (Server-Sent Events) | WebSocket |
---|---|---|
通信方向 | 单向 (Server → Client) | 双向 (Server ↔ Client) |
适用场景 | AI 生成式流式返回、订阅推送 | 实时对话、交互式 AI |
断线重连 | 浏览器自动重连 | 需要手动实现 |
性能 | 适合中等并发 | 高并发更优 |
WebSocket 适用于:
下一步优化:
你已经掌握 SSE + WebSocket 让你的 AI 聊天更智能!后续我们将探索 AI 生成内容的优化技巧!
你对这个项目感兴趣吗?欢迎 Star & 关注! GitHub 项目地址 你的支持是我持续创作的动力,欢迎点赞、收藏、分享!