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

news2024/9/22 7:31:51

组件概述

        面对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中
     */
    @Override
    public 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;
    }

    /**
     * 握手后
     */
    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {

    }
}
   /**
     * 连接成功后:将session基于业务标识和用户ID存储到目标容器
     */
    @Override
    public 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对象并解析文本数据为两部分:具体信息和业务类型。接着检查数据是否有效,如果无效则记录错误并抛出异常。最后,将消息转发消息发布器对象。


    /**
     * 处理发送来的文本消息
     */
    @Override
    protected 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";

    @PostConstruct
    void 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.coloradmin.cn/o/2054434.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UE5-C++入门教程(二)---编写Editor类别的自定义模型实现小球规划路线的可视化

前言 本教程将以图文教程的形式讲述如何快速入门通过C使用UE5.4进行项目编写。UE5的教程系列 第一期&#xff1a;UE5-C入门教程(一)&#xff1a;使用代码创建一个指定目标的移动小球-CSDN博客 UE5与ROS2实战->基于UE5和ROS2的激光雷达深度RGBD相机小车的仿真指南(一)—Unre…

MoneyPrinterTurbo的部署,在服务器Ubuntu22.04系统下——点动科技

在服务器Ubuntu22.04系统下&#xff0c;MoneyPrinterTurbo的部署 一、ubuntu22.04基本环境配置1.1 更换清华Ubuntu镜像源1.2 更新包列表&#xff1a;2. 安装英伟达显卡驱动2.1 使用wget在命令行下载驱动包2.2 更新软件列表和安装必要软件、依赖2.2 卸载原有驱动2.3 安装驱动2.4…

Axure高端交互元件库:助力产品与设计

用户体验&#xff08;UX&#xff09;和用户界面&#xff08;UI&#xff09;设计对于任何产品的成功都至关重要。为了在这个竞争激烈的市场中脱颖而出&#xff0c;设计师和产品开发团队需要依赖强大的工具来创造引人注目且功能丰富的交互界面。下面介绍一款Axure精心制作的"…

物联网(IoT)详解

物联网&#xff08;IoT&#xff09;详解 1. IoT定义简介2. IoT工作原理3. IoT关键技术4. 物联网与互联网区别5. IoT使用场景6. 开源物联网平台7. 参考资料 1. IoT定义简介 首先第一个问题&#xff0c;什么是物联网&#xff08;IoT&#xff09;? 物联网&#xff08;英文&#…

Linux:CentOS配置

一&#xff0c;安装VMware 这个可以通过官网获取 vmware下载 也可以联系我&#xff0c;我发给你 二&#xff0c;安装CentOS Centos官网找要下载的版本&#xff1a; https://vault.centos.org/ 阿里云镜像&#xff1a;https://mirrors.aliyun.com/centos-vault/?spma2c6h.13…

如何搭建redis哨兵集群

1. 构建redis镜像 FROM redis:7.0.15-alpine3.20# install tools RUN apk add curl --no-cache &&\apk add bash --no-cache # COPY redis.conf /usr/local/etc/redis/redis.confCMD [ "redis-server", "/usr/local/etc/redis/redis.conf"] dock…

一起学习LeetCode热题100道(45/100)

45.二叉树的右视图(学习) 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] 示例 2: 输入: [1,null,3] 输出: [1,3] 示例 …

【微信小程序】自定义组件 - 数据、方法和属性

1. data 数据 2. methods 方法 在小程序组件中&#xff0c;事件处理函数和自定义方法需要定义到 methods 节点中&#xff0c;示例代码如下&#xff1a; 3. properties 属性 在小程序组件中&#xff0c;properties 是组件的对外属性&#xff0c;用来接收外界传递到组件中的数…

如何使用docker打包后端项目并部署到阿里云k8s集群上

如何使用docker打包后端项目并部署到阿里云k8s集群上 1. 引言 在现代软件开发中,容器化技术已经成为主流,而Kubernetes (K8s) 是管理容器的首选平台之一。本文将详细介绍如何将一个后端项目使用Docker打包,并将其部署到阿里云的Kubernetes集群上。 2. 前置条件 阿里云账号…

sentinel集成springcloud实现限流熔断

Sentinel 是由阿里巴巴开源的一款流量控制和熔断降级组件,旨在通过灵活的流量控制和熔断降级机制,帮助开发者保护微服务架构中的应用和服务。 官网&#xff1a;home | Sentinel (sentinelguard.io) 一、安装sentinel 1.方式一&#xff1a;用docker-compose 安装 docker-com…

回归预测|基于粒子群优化深度神经网络DNN的数据回归预测Python程序PSO-DNN 多特征输入单输出

回归预测|基于粒子群优化深度神经网络DNN的数据回归预测Python程序PSO-DNN 多特征输入单输出 文章目录 前言回归预测|基于粒子群优化深度神经网络DNN的数据回归预测Python程序PSO-DNN 多特征输入单输出 一、PSO-DNN模型1. 粒子群优化&#xff08;PSO&#xff09;简介2. 深度神经…

java版知识付费saas租户平台的核心功能设计:打造高效、个性化的学习体验

随着互联网技术的飞速发展&#xff0c;我国在线教育行业迎来了新的变革&#xff0c;知识付费平台应运而生。这种新兴的在线教育模式&#xff0c;以用户需求为导向&#xff0c;以优质内容为核心&#xff0c;通过互联网技术手段&#xff0c;为用户提供便捷、高效的学习渠道。知识…

基于PyTorch的MNIST手写数字GAN生成器

文章目录 前言小笔记关键特性技术栈使用场景贡献者&#xff1a; 完整代码代码解析1. 导入必要的库2. 设备配置3. 超参数设置4. 创建样本目录5. 图像处理6. 加载MNIST数据集7. 创建数据加载器8. 定义判别器&#xff08;Discriminator&#xff09;D9. 定义生成器&#xff08;Gene…

C语言(15)——顺序表的应用

目录 1.基于动态顺序表实现通讯录项⽬ 1.1功能要求 1.2代码实现 2. 顺序表经典算法 1.基于动态顺序表实现通讯录项⽬ 1.1功能要求 1&#xff09;⾄少能够存储100个⼈的通讯信息 2&#xff09;能够保存⽤⼾信息&#xff1a;名字、性别、年龄、电话、地址等 3&#xff09;…

生活垃圾填埋场污染监测:新标准下的技术革新与环境保护

随着城市化进程的加速&#xff0c;生活垃圾产生量急剧增加&#xff0c;如何有效处理并控制其带来的环境污染成为亟待解决的问题。近日&#xff0c;生态环境部发布了新修订的《生活垃圾填埋场污染控制标准》&#xff08;GB 16889-2024&#xff09;&#xff0c;将自2024年9月1日起…

【Redis】哈希类型详解及缓存方式对比:从命令操作到实际应用场景

目录 Hash 哈希命令命令⼩结内部编码使⽤场景缓存方式对比 Hash 哈希 ⼏乎所有的主流编程语⾔都提供了哈希&#xff08;hash&#xff09;类型&#xff0c;它们的叫法可能是哈希、字典、关联数组、映射。在 Redis 中&#xff0c;哈希类型是指值本身又是⼀个键值对结构&#xff…

万维网与HTTP协议:基础知识简明指南

引言 在当今的数字时代&#xff0c;了解万维网&#xff08;World Wide Web, WWW&#xff09;和HTTP协议&#xff08;Hyper Text Transfer Protocol&#xff09;是至关重要的。本文将为基础小白们简明扼要地介绍万维网及其核心协议HTTP&#xff0c;并通过简单的例子和清晰的段落…

三级_网络技术_34_网络管理技术

1.在某主机上用浏览器无法访问到域名为www.tipu.edu.cn的网站&#xff0c;并且在该主机上执行tracert命令时有如下信息 分析以上信息&#xff0c;会造成这种现象的原因是 相关路由器上进行了访问控制 服务器 wwww.tjipu.edu.cn工作不正常 该计算机设置的DNS服务器工作不正常…

知行科技半年报显示商业化进展提速,下一个亮点在出海?

中国智驾落地竞速比拼愈演愈烈&#xff0c;让智驾公司陷入颇为紧张的竞争氛围。然而烈火出真金&#xff0c;这场角逐也成为领先企业脱颖而出的机会。 8月16日晚&#xff0c;智驾解决方案提供商知行科技(HK:01274)发布2024年上半年财报。数据显示&#xff0c;知行科技维持了营收…

SPI驱动学习一(协议原理)

目录 一、SPI协议介绍1. SPI 协议概述2. SPI 总线的主要组成部分3. SPI 协议的工作原理3. SPI 通信模式 二、SPI 协议的优点与缺点三、应用实例与常见问题1. 常用外设设备2. 常见问题3. 同时上电问题详细分析可能的原因解决方案 一、SPI协议介绍 1. SPI 协议概述 SPI&#xff…