Spring Boot使用WebSocket

ops/2025/1/21 1:05:40/

        跟其他http的控制层类似,我们需要实现一个基本的 WebSocket 服务器端点。

PlatformAsyncWebSocket.java
package com.rmeservice.platform.websocket;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@Slf4j
@Component
@ServerEndpoint(value = "/ws/platformAsync/{userId}")
public class PlatformAsyncWebSocket {// 用来存储每一个客户端对象对应的 WsController 对象private static final Map<String, PlatformAsyncWebSocket> onlineUsers = new ConcurrentHashMap<>();// 声明 Session 对象,通过该对象可以给指定的用户发送请求private Session session;/*** 连接建立时被调用*/@OnOpenpublic void onOpen(Session session, EndpointConfig config) {this.session = session;Map<String, List<String>> requestParameterMap = this.session.getRequestParameterMap();List<String> userIds = requestParameterMap.get("userId");if (userIds!= null &&!userIds.isEmpty()) {String userId = userIds.get(0);onlineUsers.put(userId, this);log.info("用户 {} 建立 WebSocket 连接成功", userId);} else {log.warn("连接建立时未获取到有效的 userId");}}/*** 接收到客户端消息时被调用*/@OnMessagepublic void onMessage(String message, Session session) {try {Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();List<String> userIds = requestParameterMap.get("userId");if (userIds!= null &&!userIds.isEmpty()) {String userId = userIds.get(0);log.info("从用户 {} 接收到消息: {}", userId, message);// 处理接收到的消息,可根据具体业务需求添加逻辑} else {log.warn("接收消息时未获取到有效的 userId");}} catch (Exception e) {log.error("处理客户端消息时发生异常", e);}}/*** 连接被关闭时调用*/@OnClosepublic void onClose(Session session) {try {Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();List<String> userIds = requestParameterMap.get("userId");if (userIds!= null &&!userIds.isEmpty()) {String userId = userIds.get(0);onlineUsers.remove(userId);session.close();log.info("用户 {} 的 WebSocket 连接关闭", userId);} else {log.warn("连接关闭时未获取到有效的 userId");}} catch (Exception e) {log.error("关闭连接时发生异常", e);}}/*** 推送消息,将消息推送给某个指定的用户*/public void sendMsg(String userId, String message) {try {PlatformAsyncWebSocket wsController = onlineUsers.get(userId);if (wsController!= null && wsController.session.isOpen()) {wsController.session.getBasicRemote().sendText(message);log.info("向用户 {} 发送消息: {}", userId, message);} else {log.warn("无法向用户 {} 发送消息,可能是用户未连接或连接已关闭", userId);}} catch (IOException e) {log.error("向用户 {} 发送消息时发生异常: {}", userId, e.getMessage());// 可考虑添加重试机制或通知管理员等操作}}
}

代码解释

  1. 类定义和成员变量

    • @Slf4j:使用 Lombok 提供的注解,自动生成 log 日志对象。
    • @Component:将类标记为 Spring 组件,由 Spring 容器管理。
    • @ServerEndpoint(value = "/ws/platformAsync/{userId}"):将类声明为 WebSocket 服务器端点,客户端可通过 /ws/platformAsync/{userId} 连接。
    • private static final Map<String, PlatformAsyncWebSocket> onlineUsers = new ConcurrentHashMap<>();:存储已连接用户及其对应的 PlatformAsyncWebSocket 实例。
    • private Session session;:存储与客户端的 WebSocket 会话。
  2. onOpen 方法

    • 当 WebSocket 连接建立时,将 session 存储在成员变量中。
    • 从 session 的请求参数中获取 userId,若存在则存储到 onlineUsers 中,并记录连接成功日志;若不存在则记录警告日志。
  3. onMessage 方法

    • 接收到消息时,从 session 的请求参数中获取 userId,若存在则记录接收到的消息及用户信息;若不存在则记录警告日志。
    • 对处理消息的逻辑进行异常处理,将异常记录到日志中。
  4. onClose 方法

    • 连接关闭时,从 session 的请求参数中获取 userId,若存在则从 onlineUsers 中移除并关闭 session,记录关闭信息;若不存在则记录警告日志。
    • 对关闭连接的操作进行异常处理,将异常记录到日志中。
  5. sendMsg 方法

    • 根据 userId 查找 PlatformAsyncWebSocket 实例,若存在且会话打开,发送消息并记录日志;若不存在或会话关闭,记录警告日志。
    • 对发送消息的操作进行异常处理,记录异常信息,可考虑添加重试机制或通知管理员。

       在实际应用中,可能需要考虑更多的业务需求,如消息的协议格式、安全验证、消息队列、负载均衡等。

       对于高并发场景,可以考虑使用更高级的线程同步机制或分布式存储来存储 onlineUsers。 对于异常处理,可以根据具体需求添加更完善的错误处理逻辑,如重试、告警等。


http://www.ppmy.cn/ops/151797.html

相关文章

实战经验:使用 Python 的 PyPDF 进行 PDF 操作

文章目录 1. 为什么选择 PyPDF&#xff1f;2. 安装 PyPDF3. PDF 文件的合并与拆分3.1 合并 PDF 文件3.2 拆分 PDF 文件 4. 提取 PDF 文本5. 修改 PDF 元信息6. PDF 加密与解密6.1 加密 PDF6.2 解密 PDF 7. 页面旋转与裁剪7.1 旋转页面7.2 裁剪页面 8. 实战经验总结 PDF 是一种非…

使用libwebsocket技术总结

一、编译libwebsocket 1) 需要使用Cmake工具&#xff0c;将根目录下CMakeLists.txt打开后&#xff0c;需要配置openssl库的路径 2) 当前libwebsocket v3.2版本需要使用openssl v1.1.x以上版本&#xff0c;否则ssl安全协议支持只能选择内置ssl模块&#xff0c;一般都选择opens…

08、如何预防SQL注入

目录 1、分析及其存在哪些危险 2、预防SQL注入 1、分析及其存在哪些危险 原理: SQL 注入是一种常见的网络攻击手段,攻击者通过在用户输入中插入恶意的 SQL 语句,利用程序对用户输入处理不当的漏洞,使恶意 SQL 语句被数据库服务器执行。 通常发生在应用程序将用户输入直接拼…

Web安全|渗透测试|网络安全

基础入门(P1-P5) p1概念名词 1.1域名 什么是域名&#xff1f; 域名&#xff1a;是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称&#xff0c;用于在数据传输时对计算机的定位标识&#xff08;有时也指地理位置&#xff09;。 什么是二级域名多级域名…

【python_钉钉群发图片】

需求&#xff1a; **在钉钉群发图片&#xff0c;需要以图片的形式展示&#xff0c;如图所示&#xff1a;**但是目前影刀里面没有符合条件的指令 解决方法&#xff1a; 1、在钉钉开发者后台新建一个自建应用&#xff0c;发版&#xff0c;然后获取里面的appkey和appsecret&am…

【设计模式】 单例模式(单例模式哪几种实现,如何保证线程安全,反射破坏单例模式)

单例模式 作用&#xff1a;单例模式的核心是保证一个类只有一个实例&#xff0c;并且提供一个访问实例的全局访问点。 实现方式优缺点饿汉式线程安全&#xff0c;调用效率高 &#xff0c;但是不能延迟加载懒汉式线程安全&#xff0c;调用效率不高&#xff0c;能延迟加载双重检…

【Idea】编译Spring源码 read timeout 问题

Idea现在是大家工作中用的比较多的开发工具&#xff0c;尤其是做java开发的&#xff0c;那么做java开发&#xff0c;了解spring框架源码是提高自己技能水平的一个方式&#xff0c;所以会从spring 官网下载源码&#xff0c;导入到 Idea 工具并编译&#xff0c;但是发现build的时…

vue3+vite+ts+router4+Pinia+Axios+sass 从0到1搭建

1、使用vite构建项目 npm create vitelatest 填写项目名的时候不能大写 2、跑起来之后配置下 import { defineConfig } from vite import vue from vitejs/plugin-vue import { resolve } from path // https://vite.dev/config/ export default defineConfig({plugins: [vue…