Skip to content

实时通信

vue3-element-admin 内置了 WebSocket 支持,基于 STOMP 协议实现实时通信能力。

STOMP 协议

STOMP (Simple Text Oriented Messaging Protocol) 是一个简单的文本消息传递协议,提供了可互操作的连接格式,允许客户端与任意 STOMP 消息代理进行交互。

核心概念

  • 连接(Connect):建立 WebSocket 连接
  • 订阅(Subscribe):订阅消息主题
  • 发送(Send):向服务器发送消息
  • 断开(Disconnect):断开连接

useStomp

useStomp 是封装的 STOMP 客户端组合函数:

typescript
import { useStomp } from '@/composables'

const { 
  connect,      // 连接
  disconnect,   // 断开
  subscribe,    // 订阅
  unsubscribe,  // 取消订阅
  send,         // 发送消息
  isConnected   // 连接状态
} = useStomp()

基本用法

typescript
import { useStomp } from '@/composables'

const { connect, subscribe, send, disconnect } = useStomp()

// 1. 建立连接
onMounted(() => {
  connect()
})

// 2. 订阅主题
const subscription = subscribe('/topic/messages', (message) => {
  console.log('收到消息:', message.body)
})

// 3. 发送消息
send('/app/message', { text: 'Hello World' })

// 4. 断开连接
onUnmounted(() => {
  disconnect()
})

配置选项

typescript
const { connect } = useStomp({
  // WebSocket 端点
  brokerURL: import.meta.env.VITE_APP_WS_ENDPOINT,
  
  // 连接成功回调
  onConnect: () => {
    console.log('WebSocket 连接成功')
  },
  
  // 连接断开回调
  onDisconnect: () => {
    console.log('WebSocket 连接断开')
  },
  
  // 错误回调
  onStompError: (frame) => {
    console.error('STOMP 错误:', frame)
  },
  
  // 心跳配置
  heartbeatIncoming: 10000,
  heartbeatOutgoing: 10000,
  
  // 自动重连
  reconnectDelay: 5000
})

应用场景

1. 字典同步

使用 useDictSync 实现字典数据实时同步:

typescript
import { useDictSync } from '@/composables'

const { startSync, stopSync, onDictChange } = useDictSync()

// 开始同步
startSync()

// 监听字典变更
onDictChange((code, action) => {
  console.log(`字典 ${code} 发生 ${action} 操作`)
})

// 停止同步
onUnmounted(() => {
  stopSync()
})

2. 在线人数统计

使用 useOnlineCount 实现在线人数实时统计:

typescript
import { useOnlineCount } from '@/composables'

const { onlineCount, startCount, stopCount } = useOnlineCount()

// 开始统计
startCount()

// 显示在线人数
console.log(`当前在线人数:${onlineCount.value}`)

// 停止统计
onUnmounted(() => {
  stopCount()
})

3. 实时通知

typescript
import { useStomp } from '@/composables'

const { connect, subscribe } = useStomp()

// 连接并订阅通知
connect()

// 订阅用户私有通知
subscribe('/user/queue/notifications', (message) => {
  const notification = JSON.parse(message.body)
  
  ElNotification({
    title: notification.title,
    message: notification.content,
    type: notification.type
  })
})

// 订阅广播通知
subscribe('/topic/broadcast', (message) => {
  const broadcast = JSON.parse(message.body)
  
  ElMessage.info(broadcast.content)
})

4. 聊天功能

typescript
import { useStomp } from '@/composables'

const { connect, subscribe, send } = useStomp()
const messages = ref<Message[]>([])

// 连接
connect()

// 订阅聊天消息
subscribe('/topic/chat', (message) => {
  const chatMessage = JSON.parse(message.body)
  messages.value.push(chatMessage)
})

// 发送消息
function sendMessage(content: string) {
  send('/app/chat', {
    content,
    sender: currentUser.value.username,
    timestamp: Date.now()
  })
}

环境配置

开发环境

bash
# .env.development
VITE_APP_WS_ENDPOINT=ws://localhost:8080/ws

生产环境

bash
# .env.production
VITE_APP_WS_ENDPOINT=wss://api.example.com/ws

Vite 代理配置

typescript
// vite.config.ts
export default defineConfig({
  server: {
    proxy: {
      '/ws': {
        target: 'ws://localhost:8080',
        ws: true,
        changeOrigin: true
      }
    }
  }
})

Nginx 配置

nginx
# WebSocket 代理
location /ws {
    proxy_pass http://backend:8080/ws;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_read_timeout 3600s;
}

后端配置

Spring Boot 配置

java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 启用简单消息代理
        config.enableSimpleBroker("/topic", "/queue");
        // 应用程序目标前缀
        config.setApplicationDestinationPrefixes("/app");
        // 用户目标前缀
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*")
                .withSockJS();
    }
}

发送消息示例

java
@Service
public class NotificationService {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    // 发送广播消息
    public void broadcast(String message) {
        messagingTemplate.convertAndSend("/topic/broadcast", message);
    }

    // 发送给指定用户
    public void sendToUser(String username, String message) {
        messagingTemplate.convertAndSendToUser(
            username, 
            "/queue/notifications", 
            message
        );
    }
}

最佳实践

1. 连接管理

在应用级别管理 WebSocket 连接,避免重复连接:

typescript
// App.vue 或 Layout 组件
import { setupWebSocket, cleanupWebSocket } from '@/composables'

onMounted(() => {
  setupWebSocket()
})

onUnmounted(() => {
  cleanupWebSocket()
})

2. 错误处理

typescript
const { connect } = useStomp({
  onStompError: (frame) => {
    console.error('STOMP 错误:', frame.headers.message)
    ElMessage.error('WebSocket 连接异常')
  },
  
  onWebSocketError: (event) => {
    console.error('WebSocket 错误:', event)
  }
})

3. 重连机制

typescript
const { connect } = useStomp({
  reconnectDelay: 5000,  // 5秒后重连
  
  onDisconnect: () => {
    console.log('连接断开,将在5秒后重连...')
  }
})

4. 心跳检测

typescript
const { connect } = useStomp({
  heartbeatIncoming: 10000,  // 接收心跳间隔
  heartbeatOutgoing: 10000   // 发送心跳间隔
})

相关链接

基于 MIT 许可发布