第一节 Springboot实现websocekt通信

亮子 2025-09-02 10:25:00 151 0 0 0

1、添加依赖

<!-- websocket依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2、创建websocket服务器配置类

package com.bwie.config;

import com.bwie.websocket.DriverWebSocketHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket // 启用WebSocket支持
public class WebSocketConfig implements WebSocketConfigurer {

    private final DriverWebSocketHandler webSocketHandler;

    // 注入自定义的消息处理器
    public WebSocketConfig(DriverWebSocketHandler webSocketHandler) {
        this.webSocketHandler = webSocketHandler;
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册WebSocket端点,允许前端通过ws://localhost:8080/ws连接
        registry.addHandler(webSocketHandler, "/ws")
                .setAllowedOrigins("*"); // 允许跨域(生产环境需指定具体域名)
    }
}

image.png

3、回调函数

package com.bwie.websocket;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class DriverWebSocketHandler extends TextWebSocketHandler {

    // 存储所有上报信息的司机信息(线程安全)
    // key = driverId
    // value = DriverInfoVo
    private static final ConcurrentHashMap<String, WebSocketSession> sessionsMap = new ConcurrentHashMap<>();

    // 存储所有活跃的WebSocket会话(线程安全)
    private static final Set<WebSocketSession> sessions =
            Collections.synchronizedSet(new HashSet<>());

    // 客户端连接成功时触发
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
        System.out.println("新客户端连接,当前在线数:" + sessions.size());
        String id = session.getId();
        System.out.println("id:" + id);
        session.sendMessage(new TextMessage("连接成功!"));
    }

    // 接收客户端消息时触发
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String clientMessage = message.getPayload();
        System.out.println("收到消息:" + clientMessage);

        // 示例:向所有客户端广播消息
        broadcast("服务器收到:" + clientMessage);
    }

    // 客户端断开连接时触发
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessions.remove(session);
        System.out.println("客户端断开,当前在线数:" + sessions.size());
    }


    // 广播消息给所有在线客户端
    private void broadcast(String message) throws IOException {
        for (WebSocketSession session : sessions) {
            if (session.isOpen()) { // 确保连接未关闭
                session.sendMessage(new TextMessage(message));
            }
        }
    }
}

4、前端代码

<template>
  <div class="websocket-client">
    <h3>WebSocket 客户端</h3>
    
    <div class="connection-status">
      连接状态: 
      <span :class="isConnected ? 'connected' : 'disconnected'">
        {{ isConnected ? '已连接' : '未连接' }}
      </span>
    </div>

    <div class="message-controls">
      <input 
        type="text" 
        v-model="messageInput" 
        placeholder="输入消息..."
        :disabled="!isConnected"
      >
      <button 
        @click="sendMessage" 
        :disabled="!isConnected || !messageInput"
      >
        发送消息
      </button>
      <button 
        @click="isConnected ? closeConnection() : connect()"
      >
        {{ isConnected ? '断开连接' : '连接服务器' }}
      </button>
    </div>

    <div class="message-history">
      <h4>消息历史:</h4>
      <div v-for="(msg, index) in messageHistory" :key="index" class="message-item">
        <span :class="msg.isSent ? 'sent' : 'received'">
          {{ msg.isSent ? '我' : '服务器' }}: {{ msg.content }}
        </span>
        <span class="time">{{ msg.timestamp }}</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue';

// 定义消息类型接口
interface WebSocketMessage {
  content: string;
  isSent: boolean;
  timestamp: string;
}

// WebSocket配置
const WS_URL = ref('ws://localhost:9011/ws'); // 后端WebSocket地址
const isConnected = ref(false);
const webSocket = ref<WebSocket | null>(null);
const messageInput = ref('');
const messageHistory = ref<WebSocketMessage[]>([]);

// 格式化时间
const formatTime = () => {
  const date = new Date();
  return date.toLocaleTimeString();
};

// 连接WebSocket
const connect = () => {
  if (isConnected.value) return;

  try {
    webSocket.value = new WebSocket(WS_URL.value);
    
    // 连接成功
    webSocket.value.onopen = () => {
      console.log('WebSocket连接成功');
      isConnected.value = true;
      addMessage('连接服务器成功', false);
    };

    // 接收消息
    webSocket.value.onmessage = (event) => {
      console.log('收到消息:', event.data);
      addMessage(event.data, false);
    };

    // 连接关闭
    webSocket.value.onclose = (event) => {
      console.log(`WebSocket关闭: ${event.code} ${event.reason}`);
      isConnected.value = false;
      addMessage(`连接已关闭: ${event.reason}`, false);
      webSocket.value = null;
    };

    // 连接错误
    webSocket.value.onerror = (error) => {
      console.error('WebSocket错误:', error);
      addMessage('连接发生错误', false);
    };
  } catch (error) {
    console.error('连接失败:', error);
    addMessage('连接服务器失败', false);
  }
};

// 关闭连接
const closeConnection = () => {
  if (webSocket.value && isConnected.value) {
    webSocket.value.close(1000, '正常关闭');
  }
};

// 发送消息
const sendMessage = () => {
  if (!webSocket.value || !isConnected.value || !messageInput.value.trim()) return;

  const message = messageInput.value.trim();
  try {
    webSocket.value.send(message);
    addMessage(message, true);
    messageInput.value = ''; // 清空输入框
  } catch (error) {
    console.error('发送消息失败:', error);
    addMessage('发送消息失败', false);
  }
};

// 添加消息到历史记录
const addMessage = (content: string, isSent: boolean) => {
  messageHistory.value.push({
    content,
    isSent,
    timestamp: formatTime()
  });
  
  // 保持滚动到底部
  setTimeout(() => {
    const container = document.querySelector('.message-history');
    if (container) {
      container.scrollTop = container.scrollHeight;
    }
  }, 0);
};

// 组件挂载时连接
onMounted(() => {
  connect();
});

// 组件卸载时关闭连接
onUnmounted(() => {
  closeConnection();
});

// 监听窗口关闭事件
watch(isConnected, (newVal) => {
  if (newVal) {
    window.addEventListener('beforeunload', closeConnection);
  } else {
    window.removeEventListener('beforeunload', closeConnection);
  }
}, { immediate: true });
</script>

<style scoped>
.websocket-client {
  max-width: 800px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.connection-status {
  margin: 15px 0;
  padding: 10px;
  border-radius: 4px;
  background-color: #f5f5f5;
}

.connected {
  color: #4caf50;
  font-weight: bold;
}

.disconnected {
  color: #f44336;
  font-weight: bold;
}

.message-controls {
  display: flex;
  gap: 10px;
  margin: 20px 0;
}

.message-controls input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
}

.message-controls button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  background-color: #2196f3;
  color: white;
  cursor: pointer;
  transition: background-color 0.3s;
}

.message-controls button:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}

.message-controls button:not(:disabled):hover {
  background-color: #0b7dda;
}

.message-history {
  margin-top: 20px;
  padding: 15px;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  max-height: 400px;
  overflow-y: auto;
}

.message-item {
  margin: 8px 0;
  padding: 8px 12px;
  border-radius: 4px;
  max-width: 70%;
}

.sent {
  background-color: #e3f2fd;
  float: right;
}

.received {
  background-color: #f1f8e9;
  float: left;
}

.time {
  display: block;
  font-size: 12px;
  color: #757575;
  margin-top: 4px;
}

.message-item::after {
  content: "";
  clear: both;
  display: table;
}
</style>

5、测试效果

image.png