在为Vue 3 + WebSocket项目添加实时通知功能时,遇到了与原有聊天功能的冲突问题。本文记录了问题的分析过程和最终的解决方案。
我们的项目是一个基于 Vue 3 + Vuetify + Pinia 构建的采购系统,原本已经实现了完善的聊天功能,使用 WebSocket + STOMP 协议进行实时通信。
// 原有聊天功能架构
ChatManagement.vue → chatStore.connectWebSocket() → initWebSocket() → 订阅聊天频道
在为系统添加实时通知功能后,出现了以下问题:
WebSocket连接成功,响应帧: _FrameImpl {...}
TypeError: There is no underlying STOMP connection
at CompatClient._checkConnection
at CompatClient.subscribe
at WebSocketService.subscribe
通过深入分析,发现问题的根本原因是WebSocket连接冲突:
// ❌ 问题代码:两处都在初始化WebSocket
// main.js
const setupNotificationWebSocket = async () => {
await webSocketService.connect() // 第1次连接
webSocketService.subscribe('/user/queue/notifications', ...)
}
// chatStore.js
async connectWebSocket() {
await initWebSocket() // 第2次连接
// 订阅聊天相关频道
}
webSocketService.connected
状态混乱WebSocket 单例服务的假象:
// 看似是单例,实际上存在状态竞争
class WebSocketService {
connect() {
if (this.connected) return Promise.resolve() // ❌ 状态可能不准确
// ...
}
}
问题的关键:
graph TD
A[用户操作] --> B[进入聊天页面]
B --> C[ChatManagement.vue]
C --> D[chatStore.connectWebSocket]
D --> E[initWebSocket - 统一入口]
E --> F[webSocketService.connect - 唯一连接]
F --> G[订阅所有频道]
G --> H[/user/queue/messages - 聊天]
G --> I[/user/queue/status - 状态]
G --> J[/user/queue/unread - 未读]
G --> K[/user/queue/notifications - 通知]
H --> L[handleWebSocketMessage]
I --> L
J --> L
K --> L
L --> M[智能消息路由]
M --> N[聊天消息处理]
M --> O[通知消息处理]
style F fill:#ccffcc
style G fill:#ccffcc
// ✅ 修复后:main.js 只负责应用启动
const preloadData = async () => {
// 只预加载数据,不初始化WebSocket
const categoryStore = useCategoryStore()
await categoryStore.fetchRequirementCategoryTree()
}
// ❌ 删除的冲突代码
// const setupNotificationWebSocket = async () => { ... }
// ✅ websocket.js:统一的初始化函数
export async function initWebSocket() {
try {
await webSocketService.connect();
if (!webSocketService.connected) return;
const { handleWebSocketMessage } = await import('./messageHandler');
// 关键:一次性订阅所有频道
webSocketService.subscribe('/user/queue/messages', handleWebSocketMessage);
webSocketService.subscribe('/user/queue/status', handleWebSocketMessage);
webSocketService.subscribe('/user/queue/unread', handleWebSocketMessage);
webSocketService.subscribe('/user/queue/notifications', handleWebSocketMessage); // 新增
console.log('WebSocket初始化完成,已订阅所有必要通道(包括通知)');
// 初始化通知数据
await initNotificationData();
} catch (error) {
console.error('WebSocket初始化失败:', error);
}
}
// ✅ messageHandler.js:根据消息来源智能路由
export function handleWebSocketMessage(message) {
try {
// 关键:根据destination判断消息类型
const destination = message.headers?.destination || '';
if (destination.includes('/user/queue/notifications')) {
// 专门处理通知消息
handleNotificationMessage(message);
return;
}
// 原有聊天消息处理逻辑保持不变
const data = JSON.parse(message.body);
const { type, payload } = data;
switch (type) {
case 'chat_message':
handleChatMessage(payload);
break;
case 'user_status':
handleUserStatus(payload);
break;
// ...
}
} catch (error) {
console.error('处理WebSocket消息失败:', error);
}
}
// 通知消息专用处理函数
function handleNotificationMessage(message) {
try {
const notification = JSON.parse(message.body);
const notificationStore = useNotificationStore();
// 添加到通知列表
notificationStore.addNotification(notification);
// 显示UI通知
showNotificationUI(notification);
} catch (error) {
console.error('处理通知消息失败:', error);
}
}
// ✅ NotificationBell.vue:避免重复初始化
onMounted(async () => {
// 只获取数据,不初始化WebSocket连接
try {
if (notificationStore.notifications.length === 0) {
await notificationStore.fetchNotifications({ page: 1, size: 10 })
}
if (notificationStore.unreadCount === 0) {
await notificationStore.fetchUnreadCount()
}
} catch (error) {
console.error('初始化通知数据失败:', error)
}
})
功能 | 修复前 | 修复后 |
---|---|---|
买家→买家消息 | ❌ 需要刷新 | ✅ 实时收到 |
卖家→买家消息 | ✅ 正常 | ✅ 正常 |
实时通知 | ❌ 不稳定 | ✅ 稳定推送 |
WebSocket连接 | ❌ 冲突/重复 | ✅ 单一稳定 |
错误日志 | ❌ 大量错误 | ✅ 无错误 |
// 修复前:多个连接
连接1: main.js → webSocketService (通知)
连接2: chatStore → webSocketService (聊天)
// 总连接数:2个,存在冲突
// 修复后:单一连接
连接1: chatStore → webSocketService (通知+聊天)
// 总连接数:1个,功能完整
// ✅ 推荐:单一入口管理
const WebSocketManager = {
initialized: false,
async init() {
if (this.initialized) return;
await webSocketService.connect();
this.subscribeAll();
this.initialized = true;
},
subscribeAll() {
// 统一订阅所有频道
}
}
// ✅ 推荐:基于destination的路由
function routeMessage(message) {
const destination = message.headers?.destination || '';
// 路由表
const routes = {
'/user/queue/notifications': handleNotification,
'/user/queue/messages': handleChatMessage,
'/user/queue/status': handleUserStatus
};
for (const [pattern, handler] of Object.entries(routes)) {
if (destination.includes(pattern)) {
handler(message);
return;
}
}
}
// ✅ 推荐:完善的错误处理
class WebSocketService {
connect() {
return new Promise((resolve, reject) => {
this.stompClient.connect(
headers,
frame => {
console.log('✅ WebSocket连接成功');
this.connected = true;
resolve(frame);
},
error => {
console.error('❌ WebSocket连接失败:', error);
this.connected = false;
this.attemptReconnect(); // 自动重连
reject(error);
}
);
});
}
}
// ✅ 推荐:关注点分离
// WebSocket管理 ≠ 数据获取
onMounted(() => {
// 只负责获取数据,不管理连接
fetchInitialData();
})
// 在需要实时功能的组件中初始化WebSocket
onMounted(() => {
// 聊天组件负责初始化实时连接
initRealtimeFeatures();
})
这个解决方案适用于所有需要在现有WebSocket应用中添加新实时功能的场景:
作者: williamdsy
日期: 2025年6月
标签: WebSocket, Vue.js, 冲突解决, 系统集成
提示: 如果你也遇到了类似的WebSocket集成问题,欢迎在评论区分享你的解决方案!