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

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、测试效果
