WebSocket可拓展业务组件搭建,无侵入实现WebSocket通信信息自定义业务消费

ops/2024/10/19 0:19:54/

组件概述

        面对C端产品,往往会携带有客户端和服务端的双端通信以实现实时交互的效果,但是目前HTTP1.1并不支持双端通信,因此,对于聊天室、多人实时游戏等场景,就需要用到一个新的通信协议:WebSocket。

更多WebSocket相关的信息请参考本文章icon-default.png?t=N7T8https://blog.csdn.net/weixin_73077810/article/details/136840600?spm=1001.2014.3001.5501        而对于一个系统而言,往往WebSocket的建立使用不仅仅只有一个,那如果对于每一个业务都搭建一套适配的WebSocket组件,项目中便会加持上许多冗余代码,无法达到一个复用的效果,也不利于后续的相关业务拓展。

        本文的目标是实现一个可以复用的WebSocket组件,通过设计模式解耦业务标识分组实现项目中对于WebSocket通讯的复用实现,并基于业务标识扩展消息的自定义消费逻辑。

组件实现流程图

 伪代码讲解

连接建立阶段

        在WebSocket连接建立阶段,PlusWebSocketInterceptor拦截器首先在握手前将用户信息和业务标识存储到Session中,然后在握手后将session基于业务标识和用户ID存储到目标容器中。

/*** WebSocket握手请求的拦截器*/
@Slf4j
public class PlusWebSocketInterceptor implements HandshakeInterceptor {/*** 握手前:将用户信息 + 业务标识在后续存储到Session中*/@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {LoginUser loginUser = LoginHelper.getLoginUser();attributes.put(LOGIN_USER_KEY, loginUser);String businessType = request.getHeaders().get(BUSINESS_TYPE_KEY).get(0);if (StrUtil.isBlank(businessType)){throw new ServiceException("WebSocket握手中Header需要携带业务标识businessType");}attributes.put(BUSINESS_TYPE_KEY, businessType);return true;}/*** 握手后*/@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {}
}
   /*** 连接成功后:将session基于业务标识和用户ID存储到目标容器*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) {LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);String businessType = (String) session.getAttributes().get(BUSINESS_TYPE_KEY);WebSocketSessionHolder.addSession(loginUser.getUserId(), businessType, session);log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());}
// WebSocketSession存储容器
Map<String, Map<Long, WebSocketSession>> BUSINESS_TYPE_SESSION_MAP = new ConcurrentHashMap<>();

消息处理阶段

        在消息处理阶段,首先从WebSocket会话中获取登录用户信息,然后创建WebSocket消息DTO对象并解析文本数据为两部分:具体信息和业务类型。接着检查数据是否有效,如果无效则记录错误并抛出异常。最后,将消息转发消息发布器对象。

/*** 处理发送来的文本消息*/@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 从WebSocket会话中获取登录用户信息LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);// 创建WebSocket消息DTO对象// 解析文本数据为两部分:具体信息 + 业务类型String payload = message.getPayload();  
// payload = {"data":{"businessType":"xxx","data":{"type":xx,"contentUrl":"xxx","createTime":"xxx","friendId":"xxx","chatSessionId":"xxx","ossId":"xxx"}}}WebSocketMessageDto data = new JSONObject(payload).get("data", WebSocketMessageDto.class);if (data == null || data.getData() == null){log.error("WebSocket接收消息失败");throw new ServiceException("WebSocket接收消息失败");}// 记录要转发的用户列表 这里是转发给自己 + 通讯对象data.setSessionKeys(List.of(loginUser.getUserId(), data.getData().getFriendId()));WebSocketUtils.publishMessage(data);}
@Data
public class WebSocketMessageDto implements Serializable {/*** 需要推送到的session key 列表*/private List<Long> sessionKeys;/*** 传输的真实信息*/private WebSocketMutualDto data;/*** 业务类型*/private String businessType;
}

消息解析消费阶段

        在消息解析消费阶段,首先根据业务类型获取对应的自定义消费器,然后执行该消费器的自定义消费逻辑。接着遍历需要发送消息的sessionKey列表,如果session存在则直接发送消息给目标对象。

public static void publishMessage(WebSocketMessageDto webSocketMessage) {// 执行业务的自定义消费逻辑String businessType = webSocketMessage.getBusinessType();// 获取该业务的指定消费器来执行自定义的消费逻辑MessageConsumer messageHandler = WebSocketConstants.CONSUMER_MAP.get(businessType);messageHandler.consumerMessage(webSocketMessage.getData());// 当前服务内session,直接发送消息for (Long sessionKey : webSocketMessage.getSessionKeys()) {if (WebSocketSessionHolder.existSession(sessionKey, businessType)) {WebSocketUtils.sendMessage(businessType, sessionKey, new JSONObject(webSocketMessage.getData()).toString());continue;}}}
public interface WebSocketConstants {/*** 基于业务标识调用业务消费器进行自定义逻辑*/Map<String, MessageConsumer> CONSUMER_MAP = new HashMap<>();
}
/*** 业务自定义消费器*/
public interface MessageConsumer {// 默认不进行自定义消费default void consumerMessage(WebSocketMutualDto message){};
}
/*** A项目WebSocket订阅消费者*/
@Component
@RequiredArgsConstructor
@Slf4j
public class MemDriftWebSocketConsumer implements MessageConsumer {private final String Drift_WebSocket_Type = "driftBottle";@PostConstructvoid init(){// 注册到WebSocket消费者组件池WebSocketConstants.CONSUMER_MAP.put(Drift_WebSocket_Type, this);}@Override@Transactional(rollbackFor = Exception.class)public void consumerMessage(WebSocketMutualDto message) {// 自定义消费逻辑}
}

后期业务扩展实现

        如果有新的业务需要复用该组件,只需要继承MessageConsumer接口实现自己的业务消费逻辑,再者在修改消息中的业务标识便可无侵入复用WebSocket组件消费信息。

总结

        文章首先指出了在多个业务中使用独立WebSocket组件会导致代码冗余和不利于维护拓展的问题。为此,提出了一个通用的WebSocket组件设计方案,允许不同业务共享相同的WebSocket基础架构,同时能够处理各自独特的业务逻辑。

        组件的设计分为三个主要阶段:连接建立、消息处理、和消息解析消费。在连接建立阶段,PlusWebSocketInterceptor 拦截器会在握手前将用户信息和业务标识存储到会话(Session)中,并在握手后将会话按业务类型和用户ID组织存储。这一阶段确保了会话管理的有序和业务之间的隔离。

        消息处理阶段涉及到接收和解析客户端发来的消息。服务器首先从会话中获取用户信息,然后解析文本消息内容,将其转换为WebSocketMessageDto对象,这个对象包含了消息内容、接收者信息和业务类型等。之后,有效的消息将被转发到相应的处理器进行处理。


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

相关文章

pikachu-ssrf_redis

目录 SSRF 1、SSRF漏洞介绍&#xff1a; 2、SSRF漏洞原理&#xff1a; 3、SSRF漏洞利用手段&#xff1a; 4、SSRF漏洞绕过方法&#xff1a; SSRF(curl)用法 1、通过网址访问链接 2、利用file协议查看本地文件 3、dict协议扫描内网主机开放端口 4.gopher&#xff1a;威…

发条朋友圈赚900,这钱太好赚了吧?

最近找我接广告的特别多&#xff0c;忍不住想来吐槽几句了。 说什么发条朋友圈&#xff0c;保留24小时就给900,1000块&#xff0c;有的还会让你两天发两次&#xff0c;价格1500-2000不等&#xff0c;不用做项目&#xff0c;不用带学员&#xff0c;试想&#xff0c;私域粉丝比较…

单片机学习笔记概述

单片机是一种集成了处理器核心、存储器、输入/输出接口等多种功能于一体的微型计算机系统。由于其体积小、成本低、功耗低、易于编程和扩展等优点&#xff0c;单片机被广泛应用于各种嵌入式系统中&#xff0c;如家用电器、工业控制、仪器仪表、汽车电子等领域。 一、单片机的详…

OpenCV几何图像变换(8)调整图像大小的函数resize()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 resize 函数调整图像 src 的大小&#xff0c;使其缩小或放大至指定的大小。需要注意的是&#xff0c;初始的 dst 类型或大小不被考虑。相反&…

SpringBootWeb 篇-深入了解 SpringBoot + Vue 的前后端分离项目部署上线与 Nginx 配置文件结构

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 云服务器的准备 2.0 Xshell 和 Xftp 软件 2.1 Xshell 介绍 2.2 Xftp 介绍 3.0 在云服务器进行环境配置 3.1 安装 JDK 3.2 安装 MySQL 3.3 安装 Nginx 4.0 SpringB…

数据结构——树的三种表示方法

目录 引言 树 1.树的定义 2.树的基本概念 3.树的表示方式 (1)双亲表示法 (2)孩子表示法 (3)左孩子右兄弟表示法 (4)树的应用 结束语 引言 在学习完栈和队列的之后后&#xff0c;我们接下来学习新的数据结构——树。 树 1.树的定义 树是一种非线性数据结构&#x…

Matlab simulink建模与仿真 第一章(simulink入门)

参考视频&#xff1a;simulink1.1simulink简介_哔哩哔哩_bilibili 一、simulink简介 1、simulink与MATLAB的关系 &#xff08;1&#xff09;MATLAB是一种以矩阵为来处理数据的的计算软件&#xff0c;其应用范围十分广泛&#xff0c;该产品由若干模块组成&#xff0c;simulin…

批量自动校正图片、PDF文档方向工具

功能介绍&#xff1a;批量自动识别图片和PDF文档页面方向&#xff0c;并校正为正确阅读方向&#xff08;非倾斜纠偏&#xff09; jpg校正演示 #修复exif信息导致的jpg图像显示和实际旋转不一致问题 #增加图像DPI选项,优先原图或者固定DPI&#xff0c;但这些只会对产生旋转的图…