Spring Boot整合WebSocket案例

wen java案例 1

本文目录导读:

Spring Boot整合WebSocket案例

  1. 项目结构
  2. Maven依赖配置 (pom.xml)
  3. 主启动类
  4. WebSocket配置类
  5. 握手拦截器
  6. WebSocket处理器
  7. WebSocketSession管理器
  8. 消息DTO(可选,如果使用JSON消息)
  9. 控制器
  10. 页面文件 (static/chat.html)
  11. application.yml配置文件
  12. 前端测试页面
  13. Maven和运行
  14. 测试指南

我来为您提供一个完整的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

测试指南

  1. 基础测试

    • 访问 http://localhost:8080/
    • 输入昵称登录
    • 发送消息到所有人或指定用户
  2. 多用户测试

    • 打开多个浏览器标签页
    • 使用不同的昵称登录
    • 测试群聊和私聊功能
  3. 连接测试

    • 查看Spring Boot控制台日志
    • 模拟断线重连

这个案例实现了:

  1. ✅ 完整的WebSocket连接管理
  2. ✅ 群聊和私聊功能
  3. ✅ 在线用户列表
  4. ✅ 用户身份认证
  5. ✅ 消息广播和定向发送
  6. ✅ 连接状态监控
  7. ✅ 简洁美观的前端界面

关键特性:

  • 使用Spring Boot的WebSocket支持
  • TextWebSocketHandler处理消息
  • 线程安全的ConcurrentHashMap管理会话
  • 支持JSON消息格式
  • 包含握手拦截器进行用户认证
  • 提供API接口发送系统消息

这个案例可以直接作为聊天室、实时通知、在线客服等应用的基础框架。

抱歉,评论功能暂时关闭!