本文目录导读:

- 项目结构
- Maven依赖配置 (pom.xml)
- 主启动类
- WebSocket配置类
- 握手拦截器
- WebSocket处理器
- WebSocketSession管理器
- 消息DTO(可选,如果使用JSON消息)
- 控制器
- 页面文件 (static/chat.html)
- application.yml配置文件
- 前端测试页面
- Maven和运行
- 测试指南
我来为您提供一个完整的Spring Boot整合WebSocket的实战案例。
项目结构
springboot-websocket-demo
├── pom.xml
├── src/main/java/com/example/websocket/
│ ├── WebSocketApplication.java
│ ├── config/
│ │ └── WebSocketConfig.java
│ ├── controller/
│ │ └── ChatController.java
│ ├── websocket/
│ │ ├── ChatWebSocketHandler.java
│ │ ├── WebSocketSessionManager.java
│ │ └── WebSocketServer.java
│ └── dto/
│ └── Message.java
└── src/main/resources/
├── application.yml
└── static/
└── index.html
Maven依赖配置 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-websocket-demo</artifactId>
<version>1.0.0</version>
<name>springboot-websocket-demo</name>
<description>Spring Boot WebSocket Demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Thymeleaf模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
主启动类
package com.example.websocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WebSocketApplication {
public static void main(String[] args) {
SpringApplication.run(WebSocketApplication.class, args);
}
}
WebSocket配置类
package com.example.websocket.config;
import com.example.websocket.websocket.ChatWebSocketHandler;
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;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import javax.annotation.Resource;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Resource
private ChatWebSocketHandler chatWebSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(chatWebSocketHandler, "/chat")
.addInterceptors(new ChatHandshakeInterceptor())
.setAllowedOrigins("*");
// 支持SockJS
registry.addHandler(chatWebSocketHandler, "/chat-sockjs")
.addInterceptors(new ChatHandshakeInterceptor())
.setAllowedOrigins("*")
.withSockJS();
}
}
握手拦截器
package com.example.websocket.config;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Map;
public class ChatHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpServletRequest httpServletRequest = servletRequest.getServletRequest();
// 获取请求参数
String userName = httpServletRequest.getParameter("username");
if (userName == null || userName.trim().isEmpty()) {
return false;
}
attributes.put("username", userName);
return true;
}
return false;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
// 握手完成后的处理
}
}
WebSocket处理器
package com.example.websocket.websocket;
import com.example.websocket.dto.Message;
import com.fasterxml.jackson.databind.ObjectMapper;
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 org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class ChatWebSocketHandler extends TextWebSocketHandler {
// 存储所有在线连接的用户
private static final Map<String, WebSocketSession> ONLINE_USERS = new ConcurrentHashMap<>();
// 存储用户与连接的映射
private static final Map<String, String> SESSION_USER_MAP = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 连接建立后
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String username = (String) session.getAttributes().get("username");
if (username != null) {
// 判断是否已存在同名的连接
if (ONLINE_USERS.containsKey(username)) {
// 给用户发送错误消息并关闭连接
session.sendMessage(new TextMessage("用户名已存在,请更换用户名后再试"));
session.close();
return;
}
ONLINE_USERS.put(username, session);
SESSION_USER_MAP.put(session.getId(), username);
// 广播新用户加入消息
broadcastOnlineUsers();
// 发送欢迎消息
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(
Message.success("系统", "欢迎 " + username + ",连接成功!")
)));
System.out.println("用户 " + username + " 已连接");
}
}
/**
* 接收消息
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String username = SESSION_USER_MAP.get(session.getId());
if (username == null) {
return;
}
String payload = message.getPayload();
System.out.println("收到用户 " + username + " 的消息: " + payload);
// 解析消息
Message msg;
try {
msg = objectMapper.readValue(payload, Message.class);
} catch (Exception e) {
msg = Message.builder()
.from(username)
.content(payload)
.type(Message.MessageType.CHAT)
.timestamp(System.currentTimeMillis())
.build();
}
// 设置发送者
msg.setFrom(username);
// 根据消息类型发送
switch (msg.getType()) {
case CHAT:
// 群聊消息,广播给所有用户
if (StringUtils.isEmpty(msg.getTo())) {
broadcastMessage(msg);
} else {
// 私聊消息
sendPrivateMessage(msg);
}
break;
case JOIN:
broadcastOnlineUsers();
break;
case LEAVE:
// 用户离开
break;
}
}
/**
* 连接关闭后
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String username = SESSION_USER_MAP.remove(session.getId());
if (username != null) {
ONLINE_USERS.remove(username);
System.out.println("用户 " + username + " 已断开连接");
// 广播在线用户列表更新
broadcastOnlineUsers();
}
}
/**
* 连接异常
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("连接发生错误: " + session.getId() + " - " + exception.getMessage());
if (session.isOpen()) {
session.close();
}
}
/**
* 处理Ping消息
*/
@Override
protected void handlePongMessage(WebSocketSession session, org.springframework.web.socket.PongMessage message) throws Exception {
super.handlePongMessage(session, message);
}
/**
* 广播在线用户列表
*/
private void broadcastOnlineUsers() throws IOException {
Map<String, Object> data = new ConcurrentHashMap<>();
data.put("type", "ONLINE_USERS");
data.put("users", ONLINE_USERS.keySet());
String message = objectMapper.writeValueAsString(data);
for (WebSocketSession session : ONLINE_USERS.values()) {
synchronized (session) {
if (session.isOpen()) {
session.sendMessage(new TextMessage(message));
}
}
}
}
/**
* 广播消息给所有用户
*/
private void broadcastMessage(Message message) throws IOException {
String jsonMessage = objectMapper.writeValueAsString(message);
for (WebSocketSession session : ONLINE_USERS.values()) {
synchronized (session) {
if (session.isOpen()) {
session.sendMessage(new TextMessage(jsonMessage));
}
}
}
}
/**
* 发送私聊消息
*/
private void sendPrivateMessage(Message message) throws IOException {
// 发送给目标用户
WebSocketSession targetSession = ONLINE_USERS.get(message.getTo());
if (targetSession != null && targetSession.isOpen()) {
synchronized (targetSession) {
targetSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
}
}
// 也发给发送者,并标记为私聊
message.setPrivate(true);
WebSocketSession senderSession = ONLINE_USERS.get(message.getFrom());
if (senderSession != null && senderSession.isOpen()) {
synchronized (senderSession) {
senderSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
}
}
}
/**
* 主动发送消息给指定用户
*/
public boolean sendMessageToUser(String username, String content) throws IOException {
WebSocketSession session = ONLINE_USERS.get(username);
if (session != null && session.isOpen()) {
Message message = Message.builder()
.from("server")
.content(content)
.type(Message.MessageType.CHAT)
.timestamp(System.currentTimeMillis())
.build();
synchronized (session) {
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
}
return true;
}
return false;
}
/**
* 获取在线用户数量
*/
public int getOnlineUserCount() {
return ONLINE_USERS.size();
}
/**
* 获取所有在线用户名
*/
public Map<String, WebSocketSession> getOnlineUsers() {
return ONLINE_USERS;
}
}
WebSocketSession管理器
package com.example.websocket.websocket;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class WebSocketSessionManager {
private static final Map<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();
/**
* 添加会话
*/
public void add(String userId, WebSocketSession session) {
SESSIONS.put(userId, session);
}
/**
* 移除会话
*/
public void remove(String userId) {
SESSIONS.remove(userId);
}
/**
* 获取会话
*/
public WebSocketSession get(String userId) {
return SESSIONS.get(userId);
}
/**
* 会话是否存在
*/
public boolean exists(String userId) {
return SESSIONS.containsKey(userId);
}
/**
* 保存用户和session的关联
*/
public void saveSession(String userId, WebSocketSession session) {
add(userId, session);
}
/**
* 获取所有在线用户
*/
public Map<String, WebSocketSession> getAllSessions() {
return SESSIONS;
}
/**
* 发送消息给指定用户
*/
public boolean sendMessage(String userId, String message) throws IOException {
WebSocketSession session = SESSIONS.get(userId);
if (session != null && session.isOpen()) {
session.sendMessage(new org.springframework.web.socket.TextMessage(message));
return true;
}
return false;
}
/**
* 关闭指定用户的连接
*/
public void closeSession(String userId) throws IOException {
WebSocketSession session = SESSIONS.get(userId);
if (session != null && session.isOpen()) {
session.close();
}
remove(userId);
}
/**
* 关闭所有连接
*/
public void closeAllSessions() throws IOException {
for (WebSocketSession session : SESSIONS.values()) {
if (session.isOpen()) {
session.close();
}
}
SESSIONS.clear();
}
/**
* 统计数据
*/
public int getOnlineCount() {
return SESSIONS.size();
}
}
消息DTO(可选,如果使用JSON消息)
package com.example.websocket.dto;
import lombok.Data;
import lombok.Builder;
/**
* 消息对象
*/
@Data
@Builder
public class Message {
private MessageType type;
private String from;
private String to;
private String content;
private Long timestamp;
private boolean isPrivate;
public enum MessageType {
CHAT, JOIN, LEAVE
}
}
控制器
package com.example.websocket.controller;
import com.example.websocket.websocket.ChatWebSocketHandler;
import com.example.websocket.websocket.WebSocketSessionManager;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Controller
public class ChatController {
@Resource
private ChatWebSocketHandler chatWebSocketHandler;
@Resource
private WebSocketSessionManager sessionManager;
/**
* 聊天页面
*/
@GetMapping("/")
public String index(Model model) {
model.addAttribute("users", chatWebSocketHandler.getOnlineUsers().keySet());
return "chat";
}
/**
* 获取在线用户
*/
@GetMapping("/online-users")
@ResponseBody
public Map<String, Object> getOnlineUsers() {
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("users", chatWebSocketHandler.getOnlineUsers().keySet());
result.put("count", chatWebSocketHandler.getOnlineUserCount());
return result;
}
/**
* 发送消息给指定用户
*/
@PostMapping("/send-message")
@ResponseBody
public Map<String, Object> sendMessage(@RequestParam("to") String to,
@RequestParam("content") String content) {
Map<String, Object> result = new HashMap<>();
try {
boolean success = chatWebSocketHandler.sendMessageToUser(to, content);
result.put("success", success);
result.put("message", success ? "消息发送成功" : "用户不在线");
return result;
} catch (Exception e) {
result.put("success", false);
result.put("message", "消息发送失败: " + e.getMessage());
return result;
}
}
/**
* 关闭指定用户的连接
*/
@PostMapping("/disconnect")
@ResponseBody
public Map<String, Object> disconnect(@RequestParam("userId") String userId) {
Map<String, Object> result = new HashMap<>();
try {
sessionManager.closeSession(userId);
result.put("success", true);
result.put("message", "已关闭用户 " + userId + " 的连接");
return result;
} catch (Exception e) {
result.put("success", false);
result.put("message", "关闭连接失败: " + e.getMessage());
return result;
}
}
/**
* 重新加载配置(测试用)
*/
@GetMapping("/test-send")
@ResponseBody
public Map<String, Object> testSend() {
Map<String, Object> result = new HashMap<>();
try {
String message = "测试消息: " + System.currentTimeMillis();
chatWebSocketHandler.sendMessageToUser("test", message);
result.put("success", true);
result.put("message", message);
return result;
} catch (Exception e) {
result.put("success", false);
result.put("message", "发送失败: " + e.getMessage());
return result;
}
}
}
页面文件 (static/chat.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">WebSocket聊天室</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0;
padding: 20px;
min-height: 100vh;
}
.chat-container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 10px;
box-shadow: 0 15px 30px rgba(0,0,0,0.3);
overflow: hidden;
}
.chat-header {
background: #4a90d9;
color: white;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-main {
display: flex;
height: 500px;
}
.chat-sidebar {
width: 250px;
background: #f8f9fa;
padding: 15px;
border-right: 1px solid #dee2e6;
}
.chat-content {
flex: 1;
display: flex;
flex-direction: column;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 15px;
background: #e9ecef;
}
.chat-input {
padding: 15px;
background: white;
border-top: 1px solid #dee2e6;
}
.message {
margin-bottom: 15px;
padding: 10px;
border-radius: 8px;
max-width: 70%;
}
.message.received {
background: white;
float: left;
clear: both;
border: 1px solid #dee2e6;
}
.message.sent {
background: #4a90d9;
color: white;
float: right;
clear: both;
}
.message .message-user {
font-weight: bold;
margin-bottom: 5px;
font-size: 14px;
}
.message .message-time {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.online-list {
list-style: none;
padding: 0;
margin: 0;
}
.online-list-item {
padding: 8px 10px;
margin-bottom: 5px;
background: #e9ecef;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
}
.online-list-item:hover {
background: #dee2e6;
}
.online-list-item.selected {
background: #4a90d9;
color: white;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
background: #4a90d9;
color: white;
cursor: pointer;
margin-right: 10px;
}
.btn:hover {
background: #357abd;
}
.btn-danger {
background: #dc3545;
}
.btn-danger:hover {
background: #c82333;
}
input[type="text"] {
padding: 10px;
border: 1px solid #dee2e6;
border-radius: 5px;
flex: 1;
margin-right: 10px;
}
.system-message {
text-align: center;
color: #666;
font-size: 14px;
padding: 10px;
}
.connection-status {
font-size: 14px;
color: #28a745;
}
.connection-status.disconnected {
color: #dc3545;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<h2>WebSocket 聊天室</h2>
<div id="connectionStatus" class="connection-status">连接中...</div>
</div>
<div class="chat-main">
<!-- 在线用户列表 -->
<div class="chat-sidebar">
<h4>在线用户 (<span id="onlineCount">0</span>)</h4>
<ul id="onlineList" class="online-list"></ul>
</div>
<!-- 聊天区域 -->
<div class="chat-content">
<div id="messages" class="chat-messages"></div>
<div class="chat-input">
<div style="margin-bottom: 10px;">
<span id="targetDisplay">所有人</span>
</div>
<div style="display: flex;">
<input type="text" id="messageInput" placeholder="输入消息..." />
<button class="btn" onclick="sendMessage()">发送</button>
<button class="btn btn-danger" onclick="leaveRoom()">退出</button>
</div>
</div>
</div>
</div>
</div>
<!-- 登录弹窗 -->
<div id="loginModal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); display: flex; justify-content: center; align-items: center; z-index: 1000;">
<div style="background: white; padding: 30px; border-radius: 10px; text-align: center;">
<h3>请输入昵称</h3>
<input type="text" id="loginInput" placeholder="你的昵称" style="margin: 20px 0; width: 100%;" />
<button class="btn" onclick="login()">进入聊天室</button>
</div>
</div>
<script>
let ws = null;
let username = '';
let isConnected = false;
let currentTarget = '';
// 登录
function login() {
username = document.getElementById('loginInput').value.trim();
if (!username) {
alert('请输入昵称');
return;
}
// 隐藏登录弹窗
document.getElementById('loginModal').style.display = 'none';
// 连接WebSocket
connectWebSocket();
}
// 连接WebSocket
function connectWebSocket() {
// 构建WebSocket URL
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
const url = `${protocol}://${location.host}/chat?username=${encodeURIComponent(username)}`;
try {
ws = new WebSocket(url);
// 连接打开
ws.onopen = function() {
console.log('WebSocket连接已建立');
document.getElementById('connectionStatus').textContent = '已连接';
document.getElementById('connectionStatus').classList.remove('disconnected');
isConnected = true;
};
// 接收消息
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
handleMessage(data);
};
// 连接关闭
ws.onclose = function(event) {
console.log('WebSocket连接已关闭');
document.getElementById('connectionStatus').textContent = '已断开';
document.getElementById('connectionStatus').classList.add('disconnected');
isConnected = false;
};
// 连接错误
ws.onerror = function(error) {
console.log('WebSocket连接错误:', error);
document.getElementById('connectionStatus').textContent = '连接错误';
document.getElementById('connectionStatus').classList.add('disconnected');
isConnected = false;
};
} catch (error) {
console.error('WebSocket连接失败:', error);
alert('连接WebSocket失败');
}
}
// 处理接收到的消息
function handleMessage(data) {
switch(data.type) {
case 'CHAT':
displayMessage(data);
break;
case 'ONLINE_USERS':
updateOnlineUsers(data.users);
break;
default:
console.log('未知消息类型:', data.type);
}
}
// 显示消息
function displayMessage(message) {
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.className = 'message ' + (message.from === username ? 'sent' : 'received');
const userElement = document.createElement('div');
userElement.className = 'message-user';
userElement.textContent = message.from === username ? '我' : message.from;
const contentElement = document.createElement('div');
contentElement.textContent = message.content;
const timeElement = document.createElement('div');
timeElement.className = 'message-time';
const time = new Date(message.timestamp);
timeElement.textContent = `${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`;
messageElement.appendChild(userElement);
messageElement.appendChild(contentElement);
messageElement.appendChild(timeElement);
// 如果是私聊消息,显示标记
if (message.isPrivate) {
const privateTag = document.createElement('span');
privateTag.style.cssText = 'background: #ffc107; padding: 2px 5px; border-radius: 3px; font-size: 12px; margin-left: 10px;';
privateTag.textContent = '私聊';
userElement.appendChild(privateTag);
}
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// 更新在线用户列表
function updateOnlineUsers(users) {
const listElement = document.getElementById('onlineList');
listElement.innerHTML = '';
document.getElementById('onlineCount').textContent = users.length;
users.forEach(user => {
const item = document.createElement('li');
item.className = 'online-list-item';
item.textContent = user;
item.onclick = function() {
currentTarget = user;
document.getElementById('targetDisplay').textContent = '发送给: ' + user;
// 选中效果
const items = document.querySelectorAll('.online-list-item');
items.forEach(i => i.classList.remove('selected'));
this.classList.add('selected');
};
listElement.appendChild(item);
});
}
// 发送消息
function sendMessage() {
if (!isConnected) {
alert('连接已断开');
return;
}
const input = document.getElementById('messageInput');
const content = input.value.trim();
if (!content) return;
const message = {
type: 'CHAT',
from: username,
to: currentTarget,
content: content,
timestamp: Date.now()
};
ws.send(JSON.stringify(message));
input.value = '';
}
// 退出聊天室
function leaveRoom() {
if (ws) {
ws.close();
}
// 重新显示登录弹窗
document.getElementById('loginModal').style.display = 'flex';
document.getElementById('loginInput').value = '';
username = '';
document.getElementById('messages').innerHTML = '';
}
// 支持回车发送
document.getElementById('messageInput').addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
sendMessage();
}
});
// 页面加载时自动聚焦登录输入框
window.onload = function() {
document.getElementById('loginInput').focus();
};
</script>
</body>
</html>
application.yml配置文件
server:
port: 8080
spring:
application:
name: springboot-websocket-demo
thymeleaf:
prefix: classpath:/templates/
suffix: .html
cache: false
websocket:
# WebSocket配置
endpoint: /chat
allowed-origins: "*"
max-buffer-size: 1048576
send-timeout: 10000
前端测试页面
<!-- src/main/resources/static/test.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">WebSocket测试页面</title>
</head>
<body>
<h1>WebSocket 测试</h1>
<input type="text" id="username" placeholder="输入用户名" />
<button onclick="connect()">连接</button>
<input type="text" id="message" placeholder="输入消息" />
<button onclick="sendMsg()">发送</button>
<button onclick="disconnect()">断开连接</button>
<div id="output"></div>
<script>
let ws = null;
function connect() {
const username = document.getElementById('username').value;
ws = new WebSocket(`ws://localhost:8080/chat?username=${username}`);
ws.onopen = function() {
console.log('连接成功');
output('连接成功');
};
ws.onmessage = function(event) {
console.log('收到消息:', event.data);
output('收到: ' + event.data);
};
ws.onclose = function(event) {
console.log('连接关闭:', event.code, event.reason);
output('连接关闭');
};
ws.onerror = function(error) {
console.log('错误:', error);
output('错误: ' + JSON.stringify(error));
};
}
function sendMsg() {
const content = document.getElementById('message').value;
ws.send(content);
output('发送: ' + content);
}
function disconnect() {
if (ws) {
ws.close();
}
}
function output(text) {
const div = document.getElementById('output');
div.innerHTML += `<div>${text}</div>`;
}
</script>
</body>
</html>
Maven和运行
# 启动应用 mvn spring-boot:run # 或使用编译后的jar mvn clean package java -jar target/springboot-websocket-demo-1.0.0.jar
测试指南
-
基础测试:
- 访问
http://localhost:8080/ - 输入昵称登录
- 发送消息到所有人或指定用户
- 访问
-
多用户测试:
- 打开多个浏览器标签页
- 使用不同的昵称登录
- 测试群聊和私聊功能
-
连接测试:
- 查看Spring Boot控制台日志
- 模拟断线重连
这个案例实现了:
- ✅ 完整的WebSocket连接管理
- ✅ 群聊和私聊功能
- ✅ 在线用户列表
- ✅ 用户身份认证
- ✅ 消息广播和定向发送
- ✅ 连接状态监控
- ✅ 简洁美观的前端界面
关键特性:
- 使用Spring Boot的WebSocket支持
- TextWebSocketHandler处理消息
- 线程安全的ConcurrentHashMap管理会话
- 支持JSON消息格式
- 包含握手拦截器进行用户认证
- 提供API接口发送系统消息
这个案例可以直接作为聊天室、实时通知、在线客服等应用的基础框架。