完美火龙篇 四月的友2026-07-28 10:26
SpringBoot 即时聊天 IM 完整实现(HTTP会话管理 + WebSocket实时推送 + 离线消息)
引言即时通讯(IM)系统是现代应用中不可或缺的组件,支持用户实时交流。SpringBoot 作为轻量级框架,结合 WebSocket 和 HTTP 会话管理,可以实现完整的 IM 功能。本文将深入剖析其原理,并提供一个可运行的完整示例。## 核心原理剖析### HTTP 会话管理HTTP 是无状态协议,但通过 Session 机制可以维护用户状态。SpringBoot 使用 HttpSession 存储用户登录信息,如用户ID、用户名等。会话通过 Cookie 或 URL 重写传递 Session ID,实现身份识别。### WebSocket 实时推送WebSocket 提供全双工通信通道,突破 HTTP 请求-响应模式。SpringBoot 通过 WebSocketHandler 和 WebSocketConfig 配置,实现连接建立、消息收发和断开事件。核心原理是将每个用户与 WebSocket Session 绑定,实现点对点或群组消息推送。### 离线消息当用户不在线时,消息被存储到数据库(如 MySQL)。用户上线后,系统自动拉取未读消息,确保消息不丢失。## 环境准备- JDK 11± Maven 3.6± SpringBoot 2.7.0- MySQL 8.0- 依赖:spring-boot-starter-web, spring-boot-starter-websocket, spring-boot-starter-data-jpa, mysql-connector-java, lombok## 实现代码### 1. 项目结构src/main/java/com/example/im├── config│ ├── WebSocketConfig.java│ └── HttpSessionConfig.java├── controller│ └── AuthController.java├── handler│ └── ChatWebSocketHandler.java├── model│ └── Message.java├── repository│ └── MessageRepository.java└── service └── ChatService.java### 2. 核心配置:WebSocket 和 HTTP 会话java// config/WebSocketConfig.java@Configuration@EnableWebSocketpublic class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // 注册WebSocket端点,允许所有来源 registry.addHandler(chatWebSocketHandler(), "/chat") .setAllowedOrigins("*"); } @Bean public WebSocketHandler chatWebSocketHandler() { return new ChatWebSocketHandler(); }}### 3. 用户认证控制器java// controller/AuthController.java@RestControllerpublic class AuthController { @PostMapping("/login") public String login(@RequestParam String username, HttpSession session) { // 模拟用户登录,存储到Session session.setAttribute("userId", username.hashCode()); session.setAttribute("username", username); return "登录成功,Session ID: " + session.getId(); } @PostMapping("/logout") public String logout(HttpSession session) { session.invalidate(); return "已退出"; }}### 4. WebSocket 消息处理器(核心)java// handler/ChatWebSocketHandler.java@Componentpublic class ChatWebSocketHandler extends TextWebSocketHandler { // 存储在线用户:userId -> WebSocketSession private static final Map<Integer, WebSocketSession> onlineUsers = new ConcurrentHashMap<>(); @Autowired private ChatService chatService; @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 从HTTP Session获取用户ID(需在握手时传入) Integer userId = (Integer) session.getAttributes().get("userId"); onlineUsers.put(userId, session); System.out.println("用户 " + userId + " 上线"); // 推送离线消息 List<Message> offlineMessages = chatService.getOfflineMessages(userId); for (Message msg : offlineMessages) { session.sendMessage(new TextMessage(msg.getContent())); chatService.markAsRead(msg.getId()); } } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 解析消息(JSON格式) String payload = message.getPayload(); JSONObject json = new JSONObject(payload); int toUserId = json.getInt("toUserId"); String content = json.getString("content"); // 查找目标用户是否在线 WebSocketSession targetSession = onlineUsers.get(toUserId); if (targetSession != null && targetSession.isOpen()) { // 在线:实时推送 targetSession.sendMessage(new TextMessage(content)); } else { // 离线:存储消息 chatService.saveOfflineMessage(toUserId, content); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { Integer userId = (Integer) session.getAttributes().get("userId"); onlineUsers.remove(userId); System.out.println("用户 " + userId + " 离线"); }}### 5. 离线消息服务java// service/ChatService.java@Servicepublic class ChatService { @Autowired private MessageRepository messageRepository; public void saveOfflineMessage(int userId, String content) { Message msg = new Message(); msg.setUserId(userId); msg.setContent(content); msg.setStatus(0); // 0: 未读 messageRepository.save(msg); } public List<Message> getOfflineMessages(int userId) { return messageRepository.findByUserIdAndStatus(userId, 0); } public void markAsRead(Long messageId) { messageRepository.updateStatus(messageId, 1); // 1: 已读 }}### 6. 消息实体和仓库java// model/Message.java@Entity@Data@NoArgsConstructor@AllArgsConstructorpublic class Message { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private int userId; private String content; private int status; // 0:未读, 1:已读}// repository/MessageRepository.javapublic interface MessageRepository extends JpaRepository<Message, Long> { List<Message> findByUserIdAndStatus(int userId, int status); @Modifying @Transactional @Query("UPDATE Message m SET m.status = :status WHERE m.id = :id") void updateStatus(@Param("id") Long id, @Param("status") int status);}### 7. 前端连接示例(JavaScript)javascript// 建立WebSocket连接let socket = new WebSocket("ws://localhost:8080/chat");// 连接建立后发送消息socket.onopen = function() { socket.send(JSON.stringify({ toUserId: 123, content: "你好!" }));};// 接收消息socket.onmessage = function(event) { console.log("收到消息:" + event.data);};## 运行与测试1. 启动应用:mvn spring-boot:run2. 用户A登录:POST /login?username=Alice,获取Session ID:sessionId=abc1233. 用户A建立WebSocket连接,传入Session ID:ws://localhost:8080/chat?session=abc1234. 用户B登录并发送消息给A(如果A在线,实时推送;否则存储为离线消息)5. 用户A重新上线后,自动拉取离线消息## 原理深入- HTTP Session 与 WebSocket 的绑定 :通过握手时传递Session ID,在HandshakeInterceptor中将HttpSession属性复制到WebSocket Session属性中。- 线程安全 :使用ConcurrentHashMap管理在线用户,避免并发问题。- 消息持久化 :JPA自动映射到MySQL表,通过状态字段区分已读/未读。## 总结本文完整实现了基于SpringBoot的IM系统,核心包括:1. HTTP会话管理 :通过Cookie/Session维护用户登录状态。2. WebSocket实时推送 :建立持久连接,实现低延迟消息传递。3. 离线消息存储:消息持久化,用户上线自动拉取。此架构可扩展支持群聊、消息加密、文件传输等高级功能。通过理解其原理,可以轻松构建企业级IM应用。