本文目录导读:

- 项目结构
- 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接口发送系统消息
这个案例可以直接作为聊天室、实时通知、在线客服等应用的基础框架。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播