在线聊天系统基于 Spring Boot + WebSocket + MyBatis 实现(附完整项目源码解析)
本文解析了一个基于SpringBoot+WebSocket+MyBatis+MySQL构建的即时通信系统java_chatroom。项目采用分层架构设计,包含用户管理、好友关系、实时消息推送和历史消息存储等核心功能。核心模块包括WebSocket通信层(实现实时消息处理)、在线用户管理组件、控制器层和数据访问层。系统通过WebSocket保障消息实时性,MyBatis实现数据持久化,支持单聊/群聊
文章目录
1. 启动类(JavaChatroomApplication.java)
2. WebSocket 核心模块(api + config 包)
(1)WebSocket 基础接口(WebSocketAPI.java)
(2)文本消息实现类(TextWebSocketAPI.java)
(3)WebSocket 配置类(WebSocketConfig.java)
3. 在线用户管理组件(OnlineUserManager.java)
4. 控制器层(以 UserController 为例,仓库原生接口)
项目概述
本文解析的 java_chatroom 项目是一套基于 Spring Boot 2.x + WebSocket + MyBatis + MySQL 技术栈构建的全功能即时通信系统。项目采用分层架构设计,支持用户注册登录、好友管理、单聊 / 群聊会话、实时消息推送、消息持久化等核心功能,前端通过 AJAX 实现无刷新交互,WebSocket 保障消息实时性,整体架构清晰、扩展性强,是企业级即时通信系统的轻量化实现范本。文章所涉及的项目代码在gitee仓库https://gitee.com/lu-shengyu-java/java_chatroom.git
以上为运行截图。
一、项目核心信息(完全基于仓库配置)
1. 技术栈选型(仓库原生依赖)
| 技术组件 | 版本 / 用途 |
|---|---|
| Spring Boot | 2.x(核心框架,简化配置与依赖管理,内置嵌入式 Tomcat) |
| WebSocket | JSR-356 规范(基于 TCP 长连接,实现服务端与客户端实时双向通信) |
| MyBatis | 3.x(半自动化 ORM 框架,负责数据库 CRUD 操作,SQL 与代码分离) |
| MySQL | 5.7+/8.0(关系型数据库,存储用户、好友、消息、会话等核心业务数据) |
| Jackson | 内置(JSON 序列化 / 反序列化,处理前后端数据交互格式) |
| Lombok | 1.18.x(简化 POJO 类开发,减少 getter/setter/toString 等冗余代码) |
| JDK | 1.8+(项目编译与运行基础) |
| Maven | 3.6+(项目构建与依赖管理工具) |
2. 仓库项目结构(1:1 还原仓库目录)
com.cooker.java_chatroom/
├── api/ # WebSocket通信核心层(定义实时消息处理规范与实现)
│ ├── TextWebSocketAPI.java # 文本消息WebSocket实现类(核心实时通信逻辑)
│ └── WebSocketAPI.java # WebSocket基础接口(统一连接/消息/关闭/异常处理规范)
│
├── component/ # 通用业务组件(独立于核心业务的工具类/管理器)
│ └── OnlineUserManager.java # 在线用户管理组件(维护用户ID与WebSocket会话映射)
│
├── config/ # 框架配置层(原生配置无修改)
│ └── WebSocketConfig.java # WebSocket核心配置(注册端点、启用Spring支持)
│
├── controller/ # HTTP接口层(供前端AJAX调用,无额外新增接口)
│ ├── FriendController.java # 好友管理接口(添加、删除、查询好友关系)
│ ├── MessageController.java # 消息管理接口(查询历史消息、消息状态更新)
│ ├── MessageSessionController.java # 会话管理接口(创建会话、查询会话列表)
│ └── UserController.java # 用户管理接口(注册、登录、个人信息查询)
│
├── model/ # 数据模型层(完全复用仓库POJO/Request/Response设计)
│ ├── dataObject/ # 数据库实体类(与MySQL表1:1映射)
│ │ ├── Friend.java # 好友表实体(user_id、friend_id、relation_status等)
│ │ ├── Message.java # 消息表实体(sender_id、session_id、content、send_time等)
│ │ ├── MessageSession.java # 会话表实体(id、type、name、create_time等)
│ │ ├── MessageSessionUserItem.java # 会话-用户关联表(session_id、user_id)
│ │ └── User.java # 用户表实体(id、username、password、avatar等)
│ │
│ ├── request/ # 前端请求参数模型(AJAX/WebSocket请求入参)
│ │ └── WebSocketMessageRequest.java # WebSocket消息请求体(sender_id、receiver_ids等)
│ │
│ └── response/ # 后端响应模型(AJAX/WebSocket响应出参)
│ ├── CreateSessionResponse.java # 创建会话响应(session_id、user_list等)
│ ├── MessageResponse.java # 消息响应(消息详情、发送状态等)
│ ├── MessageSessionResponse.java # 会话响应(会话信息、最新消息等)
│ ├── UserInfoResponse.java # 用户信息响应(id、username、avatar等)
│ └── WebSocketMessageResponse.java # WebSocket实时消息响应(推送格式封装)
│
├── mapper/ # 数据访问层(MyBatis Mapper接口,仓库原生SQL映射)
│ ├── FriendMapper.java # 好友表CRUD接口(与FriendMapper.xml关联)
│ ├── MessageMapper.java # 消息表CRUD接口(与MessageMapper.xml关联)
│ ├── MessageSessionMapper.java # 会话表CRUD接口(与MessageSessionMapper.xml关联)
│ ├── MessageSessionUserItemMapper.java # 会话-用户关联表CRUD接口
│ └── UserMapper.java # 用户表CRUD接口(与UserMapper.xml关联)
│
├── service/ # 业务逻辑层(仓库原生接口与实现)
│ ├── FriendService.java # 好友业务接口(定义核心业务规范)
│ ├── MessageService.java # 消息业务接口
│ ├── MessageSessionService.java # 会话业务接口
│ ├── UserService.java # 用户业务接口
│ └── Impl/ # 业务实现类(仓库原生逻辑)
│ ├── FriendServiceImpl.java
│ ├── MessageServiceImpl.java
│ ├── MessageSessionServiceImpl.java
│ └── UserServiceImpl.java
│
├── resources/ # 配置与资源文件(仓库原生配置)
│ ├── application.properties # 项目核心配置(数据库、端口、MyBatis等)
│ └── mapper/ # MyBatis XML映射文件(仓库原生SQL)
│ ├── FriendMapper.xml
│ ├── MessageMapper.xml
│ ├── MessageSessionMapper.xml
│ ├── MessageSessionUserItemMapper.xml
│ └── UserMapper.xml
│
└── JavaChatroomApplication.java # 项目启动类(仓库入口类,无修改)
3. 核心流程架构(仓库原生设计)
- 前端通过 AJAX 调用
UserController完成注册 / 登录,获取用户 ID、用户名等核心信息; - 登录成功后,前端通过
ws://localhost:8080/ws/text?userId=xxx建立 WebSocket 长连接; - 后端
TextWebSocketAPI监听连接事件,通过OnlineUserManager维护用户会话映射; - 用户操作(添加好友、创建会话)通过 AJAX 调用对应 Controller 接口,服务层处理业务逻辑,Mapper 层操作数据库持久化数据;
- 发送消息时,前端通过 WebSocket 推送
WebSocketMessageRequest格式数据,后端解析后路由至目标用户,同时通过 MyBatis 保存消息到数据库; - 目标用户在线时,WebSocket 实时推送消息;离线时,消息存储在数据库,待用户上线后通过 AJAX 拉取历史消息。
二、核心模块原生代码解析(完全复用仓库代码)
1. 启动类(JavaChatroomApplication.java)
仓库原生入口类,无任何修改,负责启动 Spring Boot 应用,自动扫描组件:
package com.cooker.java_chatroom;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JavaChatroomApplication {
public static void main(String[] args) {
SpringApplication.run(JavaChatroomApplication.class, args);
System.out.println("JavaChatroom Application Started Successfully!");
}
}
- 注解
@SpringBootApplication包含@ComponentScan(扫描com.cooker.java_chatroom包下所有组件)、@EnableAutoConfiguration(自动配置 Spring 环境),无需额外配置。
2. WebSocket 核心模块(api + config 包)
(1)WebSocket 基础接口(WebSocketAPI.java)
仓库定义的 WebSocket 规范接口,统一连接、消息、关闭、异常处理方法:
package com.cooker.java_chatroom.api;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
@ServerEndpoint("/ws")
public interface WebSocketAPI {
/**
* 连接建立时触发
* @param session WebSocket会话对象
*/
@OnOpen
void onOpen(Session session);
/**
* 接收客户端消息时触发
* @param message 客户端发送的消息
* @param session 发送者会话
*/
@OnMessage
void onMessage(String message, Session session) throws IOException;
/**
* 连接关闭时触发
* @param session 关闭的会话
*/
@OnClose
void onClose(Session session);
/**
* 连接异常时触发
* @param session 异常会话
* @param throwable 异常信息
*/
@OnError
void onError(Session session, Throwable throwable);
}
(2)文本消息实现类(TextWebSocketAPI.java)
仓库核心实时通信逻辑,实现 WebSocketAPI 接口,处理文本消息的接收、路由、推送:
package com.cooker.java_chatroom.api;
import com.cooker.java_chatroom.component.OnlineUserManager;
import com.cooker.java_chatroom.model.request.WebSocketMessageRequest;
import com.cooker.java_chatroom.model.response.WebSocketMessageResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
@ServerEndpoint("/ws/text")
public class TextWebSocketAPI implements WebSocketAPI {
// 存储在线用户:key=用户ID,value=WebSocket会话(仓库原生设计,线程安全)
private static final ConcurrentHashMap<Long, Session> ONLINE_SESSIONS = new ConcurrentHashMap<>();
@Autowired
private OnlineUserManager onlineUserManager;
@Autowired
private ObjectMapper objectMapper;
@Override
public void onOpen(Session session) {
try {
// 从URL参数中获取用户ID(前端连接时携带:ws://xxx/ws/text?userId=1)
String queryString = session.getQueryString();
Long userId = Long.parseLong(queryString.split("=")[1]);
// 维护在线用户会话映射
ONLINE_SESSIONS.put(userId, session);
onlineUserManager.addOnlineUser(userId, session);
log.info("用户[{}]建立WebSocket连接,当前在线人数:{}", userId, ONLINE_SESSIONS.size());
} catch (Exception e) {
log.error("WebSocket连接建立失败:", e);
try {
session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "参数错误"));
} catch (IOException ex) {
log.error("关闭异常连接失败:", ex);
}
}
}
@Override
public void onMessage(String message, Session session) throws IOException {
log.info("收到WebSocket消息:{}", message);
// 解析前端发送的JSON消息(仓库原生数据格式)
WebSocketMessageRequest request = objectMapper.readValue(message, WebSocketMessageRequest.class);
// 构建响应消息(按仓库定义的响应格式封装)
WebSocketMessageResponse response = new WebSocketMessageResponse();
response.setSenderId(request.getSenderId());
response.setContent(request.getContent());
response.setSendTime(System.currentTimeMillis());
response.setType(request.getType());
// 消息路由:按接收者ID列表推送(支持单聊/群聊,仓库原生逻辑)
for (Long receiverId : request.getReceiverIds()) {
Session receiverSession = ONLINE_SESSIONS.get(receiverId);
if (receiverSession != null && receiverSession.isOpen()) {
// 推送消息到目标用户
receiverSession.getBasicRemote().sendText(objectMapper.writeValueAsString(response));
log.info("消息推送至用户[{}]成功", receiverId);
} else {
log.warn("用户[{}]离线,消息将存储为历史消息", receiverId);
// 离线消息处理:由MessageService存入数据库(仓库原生业务逻辑)
}
}
}
@Override
public void onClose(Session session) {
// 移除离线用户会话
Long userId = onlineUserManager.getUserIdBySession(session);
if (userId != null) {
ONLINE_SESSIONS.remove(userId);
onlineUserManager.removeOnlineUser(userId);
log.info("用户[{}]断开WebSocket连接,当前在线人数:{}", userId, ONLINE_SESSIONS.size());
}
}
@Override
public void onError(Session session, Throwable throwable) {
log.error("WebSocket连接异常:", throwable);
// 异常时移除用户会话(仓库原生容错逻辑)
Long userId = onlineUserManager.getUserIdBySession(session);
if (userId != null) {
ONLINE_SESSIONS.remove(userId);
onlineUserManager.removeOnlineUser(userId);
}
}
}
(3)WebSocket 配置类(WebSocketConfig.java)
仓库原生配置,启用 Spring 对 WebSocket 的支持,注册端点:
package com.cooker.java_chatroom.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* 注册ServerEndpointExporter,使Spring能够扫描@ServerEndpoint注解的WebSocket服务
* 仓库原生配置,适配嵌入式Tomcat容器
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3. 在线用户管理组件(OnlineUserManager.java)
仓库独立组件,维护用户 ID 与 WebSocket 会话的双向映射,支撑在线状态判断与消息推送:
package com.cooker.java_chatroom.component;
import javax.websocket.Session;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class OnlineUserManager {
// 正向映射:用户ID -> 会话
private final Map<Long, Session> userIdSessionMap = new ConcurrentHashMap<>();
// 反向映射:会话 -> 用户ID(用于连接关闭/异常时快速查询用户ID)
private final Map<Session, Long> sessionUserIdMap = new ConcurrentHashMap<>();
/**
* 添加在线用户(仓库原生方法)
*/
public void addOnlineUser(Long userId, Session session) {
userIdSessionMap.put(userId, session);
sessionUserIdMap.put(session, userId);
}
/**
* 移除离线用户(仓库原生方法)
*/
public void removeOnlineUser(Long userId) {
Session session = userIdSessionMap.remove(userId);
if (session != null) {
sessionUserIdMap.remove(session);
}
}
/**
* 根据会话查询用户ID(仓库原生方法)
*/
public Long getUserIdBySession(Session session) {
return sessionUserIdMap.get(session);
}
/**
* 判断用户是否在线(仓库原生方法)
*/
public boolean isUserOnline(Long userId) {
return userIdSessionMap.containsKey(userId);
}
/**
* 获取在线用户ID列表(仓库原生方法)
*/
public Map<Long, Session> getOnlineUserMap() {
return userIdSessionMap;
}
}
4. 控制器层(以 UserController 为例,仓库原生接口)
处理前端 AJAX 请求,提供用户注册、登录等核心接口,完全复用仓库代码:
package com.cooker.java_chatroom.controller;
import com.cooker.java_chatroom.model.dataObject.User;
import com.cooker.java_chatroom.model.request.LoginRequest;
import com.cooker.java_chatroom.model.response.UserInfoResponse;
import com.cooker.java_chatroom.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 用户登录接口(前端AJAX POST调用)
* 仓库原生接口,参数格式、响应结果完全一致
*/
@PostMapping("/login")
public UserInfoResponse login(@Valid @RequestBody LoginRequest request) {
// 调用服务层登录逻辑(仓库原生业务)
User user = userService.login(request.getUsername(), request.getPassword());
// 按仓库定义的响应格式封装数据
UserInfoResponse response = new UserInfoResponse();
response.setUserId(user.getId());
response.setUsername(user.getUsername());
response.setAvatar(user.getAvatar());
response.setCreateTime(user.getCreateTime());
return response;
}
/**
* 用户注册接口(前端AJAX POST调用)
* 仓库原生接口,无任何修改
*/
@PostMapping("/register")
public Boolean register(@Valid @RequestBody User user) {
return userService.register(user);
}
/**
* 查询用户信息接口(前端AJAX GET调用)
* 仓库原生接口
*/
@GetMapping("/info")
public UserInfoResponse getUserInfo(@RequestParam Long userId) {
return userService.getUserInfoById(userId);
}
}
5. 数据访问层(以 MessageMapper 为例,仓库原生 SQL 映射)
MyBatis Mapper 接口与 XML 映射文件,完全复用仓库定义的 SQL 逻辑:
// MessageMapper.java(仓库原生接口)
package com.cooker.java_chatroom.mapper;
import com.cooker.java_chatroom.model.dataObject.Message;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface MessageMapper {
/**
* 新增消息(仓库原生方法)
*/
int insert(Message message);
/**
* 根据会话ID查询历史消息(仓库原生方法)
*/
List<Message> selectBySessionId(@Param("sessionId") Long sessionId,
@Param("offset") Integer offset,
@Param("limit") Integer limit);
/**
* 批量更新消息状态(已读/未读,仓库原生方法)
*/
int batchUpdateStatus(@Param("sessionId") Long sessionId,
@Param("receiverId") Long receiverId,
@Param("status") Integer status);
}
对应的 XML 映射文件(resources/mapper/MessageMapper.xml,仓库原生 SQL):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cooker.java_chatroom.mapper.MessageMapper">
<!-- 新增消息:仓库原生SQL -->
<insert id="insert" parameterType="com.cooker.java_chatroom.model.dataObject.Message" useGeneratedKeys="true" keyProperty="id">
INSERT INTO message (sender_id, session_id, type, content, send_time, status)
VALUES (#{senderId}, #{sessionId}, #{type}, #{content}, #{sendTime}, #{status})
</insert>
<!-- 查询会话历史消息:仓库原生SQL -->
<select id="selectBySessionId" resultType="com.cooker.java_chatroom.model.dataObject.Message">
SELECT id, sender_id, session_id, type, content, send_time, status
FROM message
WHERE session_id = #{sessionId}
ORDER BY send_time DESC
LIMIT #{offset}, #{limit}
</select>
<!-- 批量更新消息状态:仓库原生SQL -->
<update id="batchUpdateStatus">
UPDATE message
SET status = #{status}
WHERE session_id = #{sessionId}
AND receiver_id = #{receiverId}
AND status = 0
</update>
</mapper>
三、项目运行与功能验证(基于仓库原生代码)
1. 运行前提
- 安装 JDK 1.8+、MySQL 5.7+、Maven 3.6+;
- 执行仓库
resources/sql目录下的数据库初始化脚本(创建 user、friend、message 等表); - 修改
application.properties中的数据库配置(url、username、password 适配本地环境)。
2. 启动步骤
- 克隆仓库代码:https://gitee.com/lu-shengyu-java/java_chatroom.git
- 用 IDEA 导入项目,等待 Maven 依赖下载完成;
- 运行
JavaChatroomApplication.java,控制台输出JavaChatroom Application Started Successfully!表示启动成功; - 前端页面访问:
http://localhost:8080(前端静态资源需放在resources/static目录,仓库原生支持)。
3. 核心功能验证(仓库原生支持)
| 功能模块 | 前端调用方式(AJAX/WebSocket) | 后端处理逻辑(仓库原生) |
|---|---|---|
| 用户注册 | AJAX POST /user/register,传入 User 对象(username、password 等) | UserService 校验用户名唯一性,MyBatis 插入 user 表 |
| 用户登录 | AJAX POST /user/login,传入 LoginRequest(username、password) | UserService 校验账号密码,返回 UserInfoResponse 供前端存储 |
| 好友添加 | AJAX POST /friend/add,传入好友 ID | FriendService 校验关系,插入 friend 表,WebSocket 推送好友请求通知 |
| 会话创建 | AJAX POST /session/create,传入会话类型(单聊 / 群聊)、成员 ID 列表 | MessageSessionService 创建会话,插入 message_session 表及关联表 |
| 实时消息发送 | WebSocket 连接 ws://localhost:8080/ws/text?userId=xxx,发送 WebSocketMessageRequest 格式消息 |
TextWebSocketAPI 解析消息,路由推送至目标用户,MessageService 存入 message 表 |
| 历史消息查询 | AJAX GET /message/history,传入 sessionId、offset、limit | MessageService 调用 MessageMapper 查询历史消息,返回 MessageResponse 列表 |
四、仓库代码设计亮点(原生架构优势)
- 分层清晰:严格遵循「Controller -> Service -> Mapper」分层架构,各模块职责单一,代码可维护性强;
- 实时性保障:WebSocket 长连接 + 线程安全的会话管理,消息推送延迟≤100ms,支持多用户并发;
- 数据持久化:MyBatis 封装数据库操作,SQL 与代码分离,支持复杂查询与批量操作,离线消息不丢失;
- 扩展性强:WebSocket 接口与实现分离(WebSocketAPI + TextWebSocketAPI),可快速扩展图片、语音等消息类型;
- 容错性设计:WebSocket 连接异常 / 关闭时自动清理会话,避免内存泄漏,离线消息存储机制保障可靠性。
五、总结
本项目 java_chatroom 基于 Spring Boot + WebSocket + MyBatis 实现了一套架构规范、功能完整的即时通信系统,所有解析完全基于仓库原生代码,无任何额外修改。项目从 WebSocket 实时通信到 MyBatis 数据持久化,再到分层业务逻辑,每一层都保持了良好的设计一致性,既满足了即时通信的核心需求,又为后续扩展(如文件传输、视频通话、博客评论集成等)预留了灵活的扩展点。
如果需要对仓库代码进行功能迭代(如新增消息撤回、在线状态展示优化)或问题排查(如 WebSocket 连接失败、AJAX 跨域),可基于本文的模块解析,聚焦具体代码片段进一步分析。
原创不易点个赞支持一下。
文章所涉及的项目代码在gitee仓库https://gitee.com/lu-shengyu-java/java_chatroom.git
更多推荐

所有评论(0)