20240603每日通信--------springboot使用netty-socketio集成即时通信WebSocket

简单效果图

群聊,私聊,广播都可以支持。
在这里插入图片描述

基础概念:

  • springboot
  • netty-socketio
  • WebSocket

POM文件:

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot-demo-websocket-socketio</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-boot-demo-websocket-socketio</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.xkcoding</groupId>
        <artifactId>spring-boot-demo</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <netty-socketio.version>1.7.16</netty-socketio.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.corundumstudio.socketio</groupId>
            <artifactId>netty-socketio</artifactId>
            <version>${netty-socketio.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <finalName>spring-boot-demo-websocket-socketio</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

websocket服务器配置

/**
 * <p>
 * websocket服务器配置
 * </p>
 */
@Configuration
@EnableConfigurationProperties({WsConfig.class})
public class ServerConfig {

    @Bean
    public SocketIOServer server(WsConfig wsConfig) {
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
        config.setHostname(wsConfig.getHost());
        config.setPort(wsConfig.getPort());

        //这个listener可以用来进行身份验证
        config.setAuthorizationListener(data -> {
            // http://localhost:8081?token=xxxxxxx
            // 例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息,本文不做身份验证
            String token = data.getSingleUrlParam("token");
            // 校验token的合法性,实际业务需要校验token是否过期等等,参考 spring-boot-demo-rbac-security 里的 JwtUtil
            // 如果认证不通过会返回一个 Socket.EVENT_CONNECT_ERROR 事件
            return StrUtil.isNotBlank(token);
        });

        return new SocketIOServer(config);
    }

    /**
     * Spring 扫描自定义注解
     */
    @Bean
    public SpringAnnotationScanner springAnnotationScanner(SocketIOServer server) {
        return new SpringAnnotationScanner(server);
    }
}

核心事件处理类

/**
 * <p>
 * 消息事件处理
 * </p>
 */
@Component
@Slf4j
public class MessageEventHandler {
    @Autowired
    private SocketIOServer server;

    @Autowired
    private DbTemplate dbTemplate;

    /**
     * 添加connect事件,当客户端发起连接时调用
     *
     * @param client 客户端对象
     */
    @OnConnect
    public void onConnect(SocketIOClient client) {
        if (client != null) {
            String token = client.getHandshakeData().getSingleUrlParam("token");
            // 模拟用户id 和token一致
            String userId = client.getHandshakeData().getSingleUrlParam("token");
            UUID sessionId = client.getSessionId();

            dbTemplate.save(userId, sessionId);
            log.info("连接成功,【token】= {},【sessionId】= {}", token, sessionId);
        } else {
            log.error("客户端为空");
        }
    }

    /**
     * 添加disconnect事件,客户端断开连接时调用,刷新客户端信息
     *
     * @param client 客户端对象
     */
    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        if (client != null) {
            String token = client.getHandshakeData().getSingleUrlParam("token");
            // 模拟用户id 和token一致
            String userId = client.getHandshakeData().getSingleUrlParam("token");
            UUID sessionId = client.getSessionId();

            dbTemplate.deleteByUserId(userId);
            log.info("客户端断开连接,【token】= {},【sessionId】= {}", token, sessionId);
            client.disconnect();
        } else {
            log.error("客户端为空");
        }
    }

    /**
     * 加入群聊
     *
     * @param client  客户端
     * @param request 请求
     * @param data    群聊
     */
    @OnEvent(value = Event.JOIN)
    public void onJoinEvent(SocketIOClient client, AckRequest request, JoinRequest data) {
        log.info("用户:{} 已加入群聊:{}", data.getUserId(), data.getGroupId());
        client.joinRoom(data.getGroupId());

        server.getRoomOperations(data.getGroupId()).sendEvent(Event.JOIN, data);
    }


    @OnEvent(value = Event.CHAT)
    public void onChatEvent(SocketIOClient client, AckRequest request, SingleMessageRequest data) {
        Optional<UUID> toUser = dbTemplate.findByUserId(data.getToUid());
        if (toUser.isPresent()) {
            log.info("用户 {} 刚刚私信了用户 {}:{}", data.getFromUid(), data.getToUid(), data.getMessage());
            sendToSingle(toUser.get(), data);
            client.sendEvent(Event.CHAT_RECEIVED, "发送成功");
        } else {
            client.sendEvent(Event.CHAT_REFUSED, "发送失败,对方不想理你");
        }
    }

    @OnEvent(value = Event.GROUP)
    public void onGroupEvent(SocketIOClient client, AckRequest request, GroupMessageRequest data) {
        Collection<SocketIOClient> clients = server.getRoomOperations(data.getGroupId()).getClients();

        boolean inGroup = false;
        for (SocketIOClient socketIOClient : clients) {
            if (ObjectUtil.equal(socketIOClient.getSessionId(), client.getSessionId())) {
                inGroup = true;
                break;
            }
        }
        if (inGroup) {
            log.info("群号 {} 收到来自 {} 的群聊消息:{}", data.getGroupId(), data.getFromUid(), data.getMessage());
            sendToGroup(data);
        } else {
            request.sendAckData("请先加群!");
        }
    }

    /**
     * 单聊
     */
    public void sendToSingle(UUID sessionId, SingleMessageRequest message) {
        server.getClient(sessionId).sendEvent(Event.CHAT, message);
    }

    /**
     * 广播
     */
    public void sendToBroadcast(BroadcastMessageRequest message) {
        log.info("系统紧急广播一条通知:{}", message.getMessage());
        for (UUID clientId : dbTemplate.findAll()) {
            if (server.getClient(clientId) == null) {
                continue;
            }
            server.getClient(clientId).sendEvent(Event.BROADCAST, message);
        }
    }

    /**
     * 群聊
     */
    public void sendToGroup(GroupMessageRequest message) {
        server.getRoomOperations(message.getGroupId()).sendEvent(Event.GROUP, message);
    }
}

websocket 服务器启动类

/**
 * <p>
 * websocket服务器启动
 * </p>
 *
 * @author yangkai.shen
 * @date Created in 2018-12-18 17:07
 */
@Component
@Slf4j
public class ServerRunner implements CommandLineRunner {
    @Autowired
    private SocketIOServer server;

    @Override
    public void run(String... args) {
        server.start();
        log.info("websocket 服务器启动成功。。。");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/676629.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯物联网竞赛_STM32L071_19_输出方波信号(PWM)

国赛考了一个方波&#xff0c;第一次考这个&#xff0c;连个示波器都没有 CUBMX配置&#xff1a; 按上述配置刚好是32MHZ / 32 / 100 1KHZ 理论&#xff1a; 频率&#xff1a;就是一秒钟能产生多少个脉冲&#xff0c;如下图: 这算是一个脉冲&#xff0c;1KHZ说明一秒钟产生1…

MySQL -- 连接查询

MySQL使用连接查询&#xff08;JOIN&#xff09;是为了从多个相关表中获取数据。连接查询是一种强大且常用的操作&#xff0c;可以根据某些条件将两张或多张表中的数据组合在一起&#xff0c;返回一个联合结果集。 1.为什么使用连接查询 数据规范化&#xff1a; 数据库设计时通…

结账和反结账

结账与反结账功能在财务软件和会计系统中扮演着重要的角色&#xff0c;以下是关于这两个功能的详细解释&#xff1a; 一、结账功能 结账功能是计算和结转各个会计科目本期发生额和期末余额的过程&#xff0c;同时标志着一定时期内财务活动的结束和财务数据的固化。结账功能的…

Elo7下单购物教程:如何利用自养号测评提升产品曝光度?

Elo7 是巴西的一个独特电商平台&#xff0c;专注于手工艺品和定制商品。它被誉为“巴西的 Etsy”&#xff0c;为当地和国际手工艺者提供了一个销售其独特创作的市场。Elo7 成立于 2008 年&#xff0c;其主要目标是支持小型创业者和手工艺人&#xff0c;让他们能够将自己的作品推…

Hbase实战处理(一)关于hbase的表设计和集成

一、Hbase的原理讲解 1、hbase介绍 2、hbase集群架构&#xff08;具体配置见其他文章&#xff09; hbase集群的HA配置 &#xff08;假如有3台机器&#xff08;同时是regionserver角色&#xff09;&#xff0c;master、slaver1、slaver2&#xff09; stop-hbase.sh cd /home/…

javascript导入excel文件

导入文件用到一个 xlsx.core.js 的包。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><script type"tex…

vfrom二开给左边添加字段或者容器

例如&#xff0c;我在左侧加入一个 我的公司 字段 修改三个文件&#xff0c;这是文件目录 这个文件是当界面选择 简体中文 的时候&#xff0c;显示的 字段组件 或者 容器组件的中文名 这个文件是当界面选择 English 的时候&#xff0c;显示的 字段组件 或者 容器组件的英文名 把…

Java集合基础知识点系统性总结篇

目录 集合一、图解集合的继承体系&#xff1f;&#xff08;[图片来源](https://www.cnblogs.com/mrhgw/p/9728065.html)&#xff09;点击查看大图二、List,Set,Map三者的区别&#xff1f;三、List接口的实现3.1、Arraylist 、 LinkedList、Vector3.2、Arraylist 、 LinkedList、…

Java如何实现pdf转base64以及怎么反转?

问题需求 今天在做发送邮件功能的时候&#xff0c;发现邮件的附件部分&#xff0c;比如pdf文档&#xff0c;要求先把pdf转为base64&#xff0c;邮件才会发送。那接下来就先看看Java 如何把 pdf文档转为base64。 两种方式&#xff0c;一种是通过插件 jar 包的方式引入&#xf…

pyside6安装

目录 1. 安装2. 配置PyCharm环境3. 测试 1. 安装 打开Anaconda Prompt&#xff0c;执行以下命令创建虚拟环境并激活 # 创建名为 myEnv, python版本为3.9 的虚拟环境 conda create -n myEnv python3.9 # 激活创建的虚拟环境 conda avtivate myEnv使用pip安装Pyside6&#xff0…

git拉去代码报错“Failed to connect to 127.0.0.1 port 31181: Connection refused“

最近参与了一个新项目&#xff0c;在使用git clone 克隆代码时遇到了一个报错"fatal: unable to access ‘https://example.git/’: Failed to connect to 127.0.0.1 port 31181: Connection refused",今天就和大家分享下解决过程。 报错详情 在使用git clone 克隆…

回溯算法题模板与实战详解

文章目录 回溯算法模板实战详解全排列问题N皇后问题 组合总和问题子集问题括号生成问题单词搜索问题 回溯算法是一种通过试错的方式来寻找问题解决方案的算法&#xff0c;常用于解决约束满足问题、组合优化问题和排列组合问题。其核心思想是深度优先搜索(DFS)与剪枝策略的结合&…

Apipost IDEA 插件使用说明

Apipost Helper作为IDEA插件&#xff0c;可以快速生成和查询API文档&#xff0c;直观友好地在IDE中调试接口。它简化了开发流程并提升效率&#xff0c;即使新手也能够迅速掌握。Apipost Helper提供了诸多便捷功能&#xff0c;如通过代码查找接口或者通过接口查找代码等&#xf…

第十五届蓝桥杯物联网试题(国赛)

好&#xff0c;很好&#xff0c;国赛直接来个阅读理解&#xff0c;我猛做4个小时40分钟&#xff0c;cpu都干冒烟了&#xff0c;也算是勉强做完吧&#xff0c;做的很仓促&#xff0c;没多检查就交了&#xff0c;方波不会&#xff0c;A板有个指示灯没做&#xff0c;其他应该都还凑…

师彼长技以助己(3)逻辑思维

师彼长技以助己&#xff08;3&#xff09;逻辑思维 前言 上一篇文章进行了工程思维和产品思维的测试&#xff0c;并介绍了几个比较重要的产品思维模型。接下来本篇介绍工程思维。&#xff08;注意产品思维并不代表产品经理思维&#xff0c;工程思维也并不代表工程师思维&…

学习小心意——python的构造方法和析构方法

构造方法和析构方法分别用于初始化对象的属性和释放类占有的资源 构造方法_init_() 语法格式如下&#xff1a; class 类名:def __init__(self, 参数1, 参数2, ...):# 初始化代码self.属性1 参数1self.属性2 参数2# ... 示例代码如下 class Student:def __init__(self):s…

期权的权利金怎么算的

期权权利金的计算涉及多个因素&#xff0c;包括敲定价格、到期时间以及整个期权合约的具体情况。期权的权利金具体的计算公式和因素可能因不同的期权合约和市场条件而有所不同&#xff0c;下文为大家介绍期权的权利金怎么算的 &#xff1f;本文来自&#xff1a;期权酱 一、期权…

实战:Zig 编写高性能 Web 服务(1)

1.1 认识 std.http std.http 是 Zig 标准库中用于处理 HTTP 相关操作的类库。以我学习新的编程语言的经历来看&#xff0c;编写web程序是最常见的技术场景&#xff0c;所以熟练掌握 HTTP server/client 服务相关的编程知识是比较重要的。 std.http 主要包含以下API: Client…

19、matlab信号预处理中的中值滤波(medfilt1()函数)和萨维茨基-戈雷滤波滤(sgolayfilt()函数)

1、中值滤波&#xff1a;medfilt1()函数 说明&#xff1a;一维中值滤波 1&#xff09;语法 语法1&#xff1a;y medfilt1(x) 将输入向量x应用3阶一维中值滤波器。 语法2&#xff1a;y medfilt1(x,n) 将一个n阶一维中值滤波器应用于x。 语法3&#xff1a;y medfilt1(x,n…