第七章:SpringBoot实现高效WebSocket通信

牛子 2025-11-07 13:16:22 12 0 0 0

#一、创建服务,如图所示:
7479181b93304ed390860ddbe35d8b37.png

#二、添加依赖,示例如下:

<!-- Spring WebSocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

#三、创建websocket服务配置类,示例如下:


import com.ruoyi.websocket.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("*"); // 允许跨域(生产环境需指定具体域名) } }

#四、创建consts,示例如下:

public class MessageConst {
    //心跳信息
    public static final Integer UP_NEART_BEAT =0;
    //上传司机信息
    public static final Integer UP_DRIVER_INFO=1;
    //上传车辆实时位置信息
    public static final Integer UP_CAR_POSITION=2;
    //上传车辆信息
    public static final Integer UP_CAR_INFO=3;
    //上传车辆状态信息
    public static final Integer UP_CAR_STATUS=4;
    //上传车辆历史轨迹信息
    public static final Integer UP_HISTORY_TTRACK=5;
    //上传车辆实时轨迹信息
    public static final Integer UP_REAL_TRACK=6;
    //上传车辆实时位置信息
    public static final Integer UP_REAL_POSITION=7;
    //上传围栏报警信息
    public static final Integer UP_FENCE_WARN=8;
}

#五、创建Vo,示例如下:

import lombok.Data;
import org.springframework.web.socket.WebSocketSession;
 
@Data
public class DriverInfoVo {
    private Integer driverId;
    private String driverName;
    private WebSocketSession session;
}

#六、回调函数,示例如下:

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));
            }
        }
    }
}

#七、创建前端Test.vue文件,示例如下:
7cbf52dc7c81443ca182919629e08c47.png
注意:这个端口是你后端的端口:
8edbc638d08a47baa16ee85fd25ded99.png
前端页面:

<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:9305/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>

#八、测试,示例如下:
运行前端服务并访问测试页面(显示“已连接”即表示成功):
e387341e7bcb491bb812c69d0cd8cc7f.png
发送消息后,若后端控制台有显示,则表明发送成功:
303e207cef16480c99407118803ed9b7.png