# springboot-websocket **Repository Path**: DYdayu/springboot-websocket ## Basic Information - **Project Name**: springboot-websocket - **Description**: springboot整合websocket实现聊天室功能 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 12 - **Forks**: 0 - **Created**: 2023-03-16 - **Last Updated**: 2026-08-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring Boot WebSocket 在线聊天室 这是一个用于学习 WebSocket 的 Spring Boot 示例项目。项目通过浏览器建立 WebSocket 长连接,实现在线用户列表广播和用户之间的点对点聊天。 ## 一期范围 ### 一期已实现 - 用户通过用户名和固定密码登录 - 登录态保存到 `HttpSession` - 浏览器与服务端建立 WebSocket 长连接 - 用户上线、下线时广播最新在线用户列表 - 在线用户之间发送点对点文本消息 - 浏览器使用 `sessionStorage` 临时保存聊天记录 ### 一期不包含 - 用户注册和数据库存储 - 完整身份认证与权限控制 - 离线消息、消息已读状态和消息撤回 - 聊天记录服务端持久化 - 群聊、文件、图片或语音消息 - WebSocket 心跳、断线自动重连和多节点部署 这些能力不影响当前“登录—建立连接—发送消息—断开连接”的学习闭环,因此不纳入当前实现。 ## 技术栈 - Java 8 - Spring Boot 2.7.10-SNAPSHOT - Spring Web - Spring WebSocket - Java WebSocket API:`javax.websocket` - Thymeleaf 风格 HTML 页面(当前主要作为静态模板使用) - jQuery - Lombok - Maven ## 项目结构 ```text src/main/java/com/dayu/websocket ├── SpringbootWebsocketApplication.java # Spring Boot 启动类 ├── Message.java # 浏览器发给服务端的私聊消息 ├── ResultMessage.java # 服务端推给浏览器的统一消息 ├── Result.java # 普通 HTTP 接口响应 ├── config │ └── WebsocketConfiguration.java # 注册 @ServerEndpoint 端点 ├── controller │ ├── IndexController.java # 登录页、聊天室页面跳转 │ └── UserController.java # 登录和当前用户名接口 ├── entity │ └── User.java # 登录表单对象 ├── utils │ └── MessageUtils.java # WebSocket 消息 JSON 序列化 └── ws ├── GetHttpSessionConfiguration.java # 在握手时传递 HttpSession └── WebsocketApi.java # WebSocket 生命周期与消息处理 src/main/resources ├── application.yml # 服务端口和静态资源配置 ├── templates │ ├── login.html # 登录页面 │ └── main.html # 聊天页面及浏览器端 WebSocket 代码 └── static # CSS、JavaScript 和字体资源 ``` ## 快速启动 ### 1. 环境要求 - JDK 8 - Maven 3.6 或更高版本 确认环境: ```bash java -version mvn -version ``` ### 2. 启动项目 ```bash mvn spring-boot:run ``` 服务默认监听 `7070` 端口。启动完成后访问: ```text http://localhost:7070/getLogin ``` ### 3. 登录 - 用户名:任意非空名称,例如 `zhangsan` - 密码:`123` 密码目前直接写在 `UserController` 中,仅用于演示,不适用于生产环境。 ### 4. 测试聊天 1. 普通浏览器窗口登录用户 `zhangsan`。 2. 使用无痕窗口或另一种浏览器登录用户 `lisi`,避免两个用户共享同一个 Cookie 和 `HttpSession`。 3. 在好友列表中点击对方用户名。 4. 输入消息并点击“发送”。 5. 关闭其中一个页面,观察另一个页面的在线用户列表变化。 ## WebSocket 主流程 ```mermaid sequenceDiagram participant B as 浏览器 participant C as UserController participant H as 握手配置器 participant W as WebsocketApi participant M as 在线连接 Map B->>C: POST /login C->>C: 用户名写入 HttpSession C-->>B: 登录成功 B->>H: WebSocket 握手 /chat/{userName} H->>H: HttpSession 放入 EndpointConfig H->>W: 握手成功,触发 @OnOpen W->>M: 保存 用户名 -> WebsocketApi W-->>B: 广播在线用户列表 B->>W: 发送私聊 JSON W->>M: 根据 toName 查找接收方 W-->>B: 向接收方推送聊天 JSON B-xW: 页面关闭或连接断开 W->>M: @OnClose 移除用户 W-->>B: 广播新的在线用户列表 ``` ### 为什么需要传递 HttpSession 登录接口是普通 HTTP 请求,登录用户名存储在 `HttpSession` 中。WebSocket 建立以后,`@OnMessage` 不会收到 `HttpServletRequest`,因此不能直接从请求中读取登录用户。 `GetHttpSessionConfiguration` 在 WebSocket 的 HTTP Upgrade 握手阶段取得 `HttpSession`,把它放进 `EndpointConfig.userProperties`。随后 `WebsocketApi.onOpen` 取出并保存该会话,消息到达时便能识别真实发送人。 ## 核心模块 ### HTTP 登录模块 - 输入:`userName`、`passwd` - 输出:登录结果和 `HttpSession` 登录态 - 数据归属:当前登录用户名归 `HttpSession` 管理 - 状态变更责任:只有 `UserController.login` 写入当前登录用户 - 一期归属:是 相关接口: | 方法 | 路径 | 用途 | | --- | --- | --- | | `GET` | `/getLogin` | 打开登录页面 | | `POST` | `/login` | 校验固定密码并写入登录态 | | `GET` | `/getMain` | 打开聊天室页面 | | `GET` | `/getUsername` | 获取当前 Session 中的用户名 | ### WebSocket 连接模块 - 输入:握手请求、客户端文本帧、连接关闭事件 - 输出:在线名单消息、点对点聊天消息 - 依赖:HTTP `HttpSession`、Jackson JSON 序列化 - 数据归属:在线连接由 `WebsocketApi.webSocketMap` 管理 - 状态变更责任:`onOpen` 增加连接,`onClose` 删除连接 - 一期归属:是 WebSocket 地址: ```text ws://localhost:7070/chat/{userName} ``` `WebsocketApi` 的生命周期回调: | 回调 | 触发时机 | 主要职责 | | --- | --- | --- | | `@OnOpen` | WebSocket 握手成功 | 保存 Session、登记在线用户、广播在线名单 | | `@OnMessage` | 收到浏览器文本帧 | 解析 JSON、查找接收方、发送私聊消息 | | `@OnClose` | 连接正常关闭 | 删除在线用户、广播最新在线名单 | | `@OnError` | 连接或收发出现异常 | 输出异常信息 | ## 消息协议 WebSocket 传输的是文本帧,本项目在文本帧中使用 JSON 表达业务消息。 ### 浏览器发送私聊消息 ```json { "toName": "lisi", "message": "你好" } ``` 字段说明: | 字段 | 类型 | 必填 | 含义 | | --- | --- | --- | --- | | `toName` | string | 是 | 接收方用户名,必须是当前在线用户 | | `message` | string | 是 | 聊天文本 | ### 服务端推送在线名单 ```json { "isSystem": true, "fromName": null, "message": ["zhangsan", "lisi"] } ``` ### 服务端推送聊天消息 ```json { "isSystem": false, "fromName": "zhangsan", "message": "你好" } ``` 字段说明: | 字段 | 类型 | 含义 | | --- | --- | --- | | `isSystem` | boolean | `true` 为系统消息,`false` 为聊天消息 | | `fromName` | string/null | 聊天消息发送人;系统消息为空 | | `message` | string/array | 聊天正文或在线用户名集合 | ## 浏览器端关键 API 创建连接: ```javascript const ws = new WebSocket("ws://" + window.location.host + "/chat/" + username); ``` 监听生命周期: ```javascript ws.onopen = function () { // 握手成功,可以开始发送消息 }; ws.onmessage = function (event) { // event.data 是服务端推送的文本帧 }; ws.onclose = function () { // 连接已经关闭 }; ``` 发送消息: ```javascript ws.send(JSON.stringify({ toName: "lisi", message: "你好" })); ``` ## 调试方法 在浏览器开发者工具中打开“网络(Network)”,选择 `WS` 类型,再点击 `/chat/{userName}` 连接,可以查看: - Headers:WebSocket 握手请求和 `101 Switching Protocols` 响应 - Messages:浏览器与服务端之间收发的文本帧 - Console:页面代码输出的消息日志 服务端日志中的“新的连接”“单点消息”等内容可以辅助确认生命周期回调是否执行。 ## 当前实现的已知限制 - 相同用户名再次登录会覆盖 `webSocketMap` 中的旧连接。 - 接收方不在线时,当前发送逻辑可能出现空指针异常。 - 页面固定使用 `ws://`;如果页面通过 HTTPS 部署,需要改为 `wss://`。 - 没有校验空消息、接收方和消息长度。 - 没有心跳检测和断线自动重连。 - 在线数据只保存在当前 JVM 内存中,不支持多实例共享。 - `sessionStorage` 只保存在当前浏览器标签页,不是服务端聊天记录。 这些限制属于学习示例的非一期能力,当前 README 只明确记录,不将其描述为已经支持。 ## 测试 运行测试: ```bash mvn test ``` 当前自动化测试只验证 Spring 上下文能否启动。WebSocket 主流程需要按照“测试聊天”章节使用两个独立浏览器会话进行验证。 ## 推荐学习顺序 1. 阅读 `WebsocketConfiguration`,理解端点如何注册。 2. 阅读 `UserController`,理解用户名如何进入 `HttpSession`。 3. 阅读 `GetHttpSessionConfiguration`,理解 HTTP 握手与 WebSocket 连接的衔接。 4. 阅读 `WebsocketApi.onOpen`,理解连接建立和在线用户登记。 5. 阅读 `WebsocketApi.onMessage`,理解文本帧解析与点对点推送。 6. 阅读 `WebsocketApi.onClose`,理解连接资源清理。 7. 阅读 `main.html` 中的脚本,对照浏览器端 `onopen`、`onmessage`、`onclose` 和 `send`。