前端订阅后端推送WebSocket定时任务

news2024/10/9 10:20:13

0.需求

        后端定时向前端看板推送数据,每10秒或者30秒推送一次。

1.前言知识

        HTTP协议是一个应用层协议,它的特点是无状态、无连接和单向的。在HTTP协议中,客户端发起请求,服务器则对请求进行响应。这种请求-响应的模式意味着服务器无法主动向客户端发送消息。

        这种单向通信的缺点在于,如果服务器有持续的状态变化,客户端要获取这些变化就很困难。为了解决这个问题,许多Web应用采用了一种叫做长轮询的技术,即频繁地通过AJAX和XML发起异步请求来检查服务器的状态。但这种方式效率较低,也很浪费资源,因为需要不断地建立连接或保持连接打开。

        而WebSocket则是一种不同的通信协议,它允许客户端和服务器之间进行全双工通信。这意味着无论是客户端还是服务器,都可以随时通过已经建立的连接向对方发送数据。而且,WebSocket只需要建立一次连接就可以保持通信状态,无需频繁地建立和断开连接,因此效率大大提高。

        总结一下,HTTP协议虽然广泛应用,但因其单向通信的局限性,在处理服务器状态持续变化的情况时显得力不从心。而WebSocket协议则通过全双工通信的方式,有效地解决了这个问题,提高了通信效率。

2.后端实现

2.1不带参数

2.1.1添加依赖:

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

2.1.2websocket配置:

/**
 * 通过EnableWebSocketMessageBroker
 * 开启使用STOMP协议来传输基于代理(message broker)的消息,
 * 此时浏览器支持使用@MessageMapping 就像支持@RequestMapping一样。
 */

//WebSocket的配置类
@Configuration
//开启对WebSocket的支持
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{

    /**
     * 注册stomp的端点
     * 注册一个STOMP协议的节点,并映射到指定的URL
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //endPoint 注册协议节点,并映射指定的URl点对点-用
        //注册一个名字为"/endpointSocket" 的endpoint,并指定 SockJS协议。
        //允许使用socketJs方式访问,访问点为webSocketServer,允许跨域
        //连接前缀
          //配置客户端尝试连接地址
        //广播
        registry.addEndpoint("/ws/public").setAllowedOriginPatterns("*").withSockJS();
        //点对点
        registry.addEndpoint("/ws/private").setAllowedOriginPatterns("*").withSockJS();
    }


   /**
     * 通过实现 WebSocketMessageBrokerConfigurer 接口和加上 @EnableWebSocketMessageBroker 来进行 stomp 的配置与注解扫描。
     * 其中覆盖 registerStompEndpoints 方法来设置暴露的 stomp 的路径,其它一些跨域、客户端之类的设置。
     * 覆盖 configureMessageBroker 方法来进行节点的配置。
     * 其中 enableSimpleBroker配置的广播节点,也就是服务端发送消息,客户端订阅就能接收消息的节点。
     * 覆盖setApplicationDestinationPrefixes方法,设置客户端向服务端发送消息的节点。
     * 覆盖 setUserDestinationPrefix 方法,设置一对一通信的节点。
     *
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //配置消息代理,即设置广播节点
        registry.enableSimpleBroker("/topic","/user");
        //后端接收的主题前缀,即客户端向服务端发送消息需要有/client前缀
//        registry.setApplicationDestinationPrefixes("/client");
        //指定用户发送(一对一)的前缀/user/
//        registry.setUserDestinationPrefix("/user/");
    }
}

2.1.3后端代码 

        一个是订阅请求接口,一个是关闭定时任务接口。这段代码实现了一个基于WebSocket的定时推送机制,允许通过发送WebSocket消息来启动和关闭定时任务,从而每30秒推送一次数据。

  /**
     * 看板接口-不带参数
     * 定时任务(每30秒推送一次)
     */
    @MessageMapping("/backend/produce/summary")
    public void pushProduceSummary() {
        log.info("服务端接收到消息: {}");
        if (scheduledTask.get("pushProduceSummary") == null) {
            ScheduledFuture<?> future = executorService.scheduleAtFixedRate(() -> {
                ProgressVO progressVO = progressSummaryService.summary();
                String destination = "/topic/backend/produce/summary";
                template.convertAndSend(destination, progressVO);
                log.info("已推送信息,每30秒推送一次:{}");
            }, 1, 30, TimeUnit.SECONDS);
            scheduledTask.put("pushProduceSummary", future);
        } else {
            log.info("定时任务已开始!");
        }

    }
/**
     * 关闭/backend/produce/summary接口的定时任务
     *
     * @author weiq
     */
    @MessageMapping("/close/backend/produce/summary")
    public void cancelPushProduceSummary() {
        scheduledTask.forEach((StringKey, future) -> {
            if (future != null && !future.isCancelled() && StringKey.equals("pushProduceSummary")) {
                // 清除定时任务的引用
                scheduledTask.remove("pushProduceSummary");
                boolean cancel = future.cancel(true);
                if (cancel) {
                    log.info("已关闭定时任务Key={}",StringKey);
                }else{
                    log.info("失败关闭定时任务Key={}",StringKey);
                }
            }
        });

    }

2.2带参数

        一个是订阅请求接口,一个是关闭定时任务接口。

  • 当客户端向 /backend/produce/runEfficiency/{startTime}/{endTime} 这个 WebSocket 地址发送消息时,pushProduceRunEfficiency 方法会被调用。
  • 这个方法会检查是否已有一个定时任务在运行。如果没有,它会创建一个新的定时任务,该任务会每30秒从 runEfficiencyService 获取运行效率数据,并通过 WebSocket 发送到指定的主题(destination)。
  • 前端(或任何监听该主题的 WebSocket 客户端)需要事先订阅这个主题,以便能够接收后端发送的数据。
 /**
     * (看板)
     *定时任务(每30秒推送一次)
     * @param startTime
     * @param endTime
     */
    @MessageMapping("/backend/produce/runEfficiency/{startTime}/{endTime}")
    public void pushProduceRunEfficiency(@DestinationVariable Long startTime, @DestinationVariable Long endTime) {
        log.info("服务端接收到消息: startTime={},endTime={}", startTime, endTime);
        if (scheduledTask.get("pushProduceRunEfficiency") == null) {
            ScheduledFuture<?> future = executorService.scheduleAtFixedRate(() -> {
                List<RunVO> runVOList = runEfficiencyService.run(startTime, endTime);
                String destination = "/topic/backend/produce/runEfficiency" + "/" + startTime + "/" + endTime;
                template.convertAndSend(destination, runVOList);
                log.info("已推送信息,每30秒推送一次:{}");
            }, 1, 30, TimeUnit.SECONDS);
            scheduledTask.put("pushProduceRunEfficiency", future);
        }else{
            log.info("定时任务已开启!");
        }

    }
    /**
     * 关闭/backend/produce/runEfficiency/{startTime}/{endTime}接口的定时任务
     *
     * @author weiq
     */
    @MessageMapping("/close/backend/produce/runEfficiency")
    public void cancelPushProduceRunEfficiency() {
        scheduledTask.forEach((StringKey, future) -> {
            if (future != null && !future.isCancelled() && StringKey.equals("pushProduceRunEfficiency")) {
                // 清除定时任务的引用
                scheduledTask.remove("pushProduceRunEfficiency");
                boolean cancel = future.cancel(true);
                if (cancel) {
                    log.info("已关闭定时任务Key={}",StringKey);
                } else {
                    log.info("失败定时任务Key={}",StringKey);
                }
            }
        });
    }

3.前端验证

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
    <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.2.0.min.js"
            integrity="sha256-JAW99MJVpJBGcbzEuXk4Az05s/XyDdBomFqNlM3ic+I=" crossorigin="anonymous"></script>

    <script type="text/javascript">
        var stompClient = null;

        function setConnected(connected) {
            document.getElementById("connect").disabled = connected;
            document.getElementById("disconnect").disabled = !connected;
            $("#response").html();
        }

        function connect() {
            console.log("开始连接吧")
            var socket = new SockJS("http://localhost:8501/ws/public");
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function (frame) {
                setConnected(true);
                console.log('Connected: ' + frame);
                //前端连接完成后,开始订阅主题
                // stompClient.subscribe('/topic/all', function (response) {
                stompClient.subscribe('/topic/backend/produce/summary', function (response) {
                    var responseData = document.getElementById('responseData');
                    var p = document.createElement('p');
                    p.style.wordWrap = 'break-word';
                    p.appendChild(document.createTextNode(response.body));
                    responseData.appendChild(p);
                });
            }, {});
        }

        function disconnect() {
            if (stompClient != null) {
                stompClient.disconnect();
            }
            setConnected(false);
            console.log("Disconnected");
        }

        //请求地址,向WebSocket 地址发送消息
        function sendMsg() {
            var content = document.getElementById('content').value;
            // stompClient.send("/all", {}, JSON.stringify({'content': content}));
            stompClient.send("/backend/produce/summary", {}, JSON.stringify({'content': content }));
        }
        //关闭WebSocket 请求的定时任务
        function sendMsg1() {
            var content = document.getElementById('content').value;
            // stompClient.send("/all", {}, JSON.stringify({'content': content}));
            stompClient.send("/close/backend/produce/summary", {}, JSON.stringify({'content': content }));
        }
        // function sendMsg1() {
        //     var content = document.getElementById('content').value;
        //     // stompClient.send("/all", {}, JSON.stringify({'content': content}));
        //     stompClient.send("/close/scene/stepActualTime/128", {}, JSON.stringify({'content': content }));
        // }
        //
        // function sendMsg2() {
        //     var content = document.getElementById('content').value;
        //     // stompClient.send("/all", {}, JSON.stringify({'content': content}));
        //     stompClient.send("/close/scene/stepActualTime/219", {}, JSON.stringify({'content': content }));
        // }
    </script>
</head>

<body notallow="disconnect()">
<noscript>
    <h2 style="color: #ff0000">
        Seems your browser doesn't support Javascript! Websocket relies on Javascript being
        enabled. Please enable
        Javascript and reload this page!
    </h2>
</noscript>
<div>
    <div>
        <labal>连接广播频道</labal>
        <button id="connect" onclick="connect()">Connect</button>
        <labal>取消连接</labal>
        <button id="disconnect" disabled="disabled" onclick="disconnect()">Disconnect</button>
    </div>

    <div id="conversationDiv">
        <labal>广播消息</labal>
        <input type="text" id="content"/>
        <button id="sendMsg" onclick="sendMsg();">Send</button>

    </div>
    <div id="conversationDiv1">
        <labal>广播消息1</labal>
        <input type="text" id="content1"/>
        <button id="sendMsg1" onclick="sendMsg1();">Send</button>

    </div>

<!--    <div id="conversationDiv2">-->
<!--        <labal>广播消息2</labal>-->
<!--        <input type="text" id="content2"/>-->
<!--        <button id="sendMsg2" onclick="sendMsg2();">Send</button>-->

<!--    </div>-->

    <div>
        <labal>接收到的消息:</labal>
        <p id="responseData"></p>
    </div>
</div>

</body>
</html>

后端启动,打开HTML测试页面,可看到运行结果!

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

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

相关文章

Waifu2x:使用深度卷积神经网络的动漫风格艺术的图像超分辨率

Github网址&#xff1a;nagadomi/waifu2x&#xff1a;动漫风格艺术的图像超分辨率 (github.com) 该项目主要讲述的是如何利用预训练的深度学习模型来达到无损扩大收缩和去噪&#xff0c;对于一般训练图像的小伙伴应该很清晰图像经常要通过resize操作固定大小&#xff0c;然后c…

Docker,anaconda环境的部署与迁移

功能上线将提上日程&#xff0c;但是如何将我windows环境下的程序放到linux服务器的测试环境跑通呢&#xff1f;这是我这整个清明假期将要解决的一件事&#xff0c;最蠢的办法就是看自己的环境下有哪些依赖&#xff0c;如何到服务器上一个一个下&#xff0c;但是首先这个方法很…

C#,简单,精巧,实用的按类型删除指定文件的工具软件

点击下载本文软件&#xff08;积分&#xff09;&#xff1a; https://download.csdn.net/download/beijinghorn/89059141https://download.csdn.net/download/beijinghorn/89059141 下载审核通过之前&#xff0c;请从百度网盘下载&#xff08;无积分&#xff09;&#xff1a;…

Flume进阶学习!

本文图片来自于8.flume实时监控文件hdfs sink使用演示_哔哩哔哩_bilibili Apache Flume 的启动过程及其配置文件和脚本 在官网下载的Flume的压缩包中&#xff0c;.lib文件有大量的jar包&#xff0c;按道理说只有.lib文件就可以运行Flume程序了。只不过需要java -jar命令还要加…

二维码:技术、商业与未来

title: 二维码&#xff1a;技术、商业与未来 date: 2024/4/3 19:12:28 updated: 2024/4/3 19:12:28 tags: 二维码技术商业应用移动支付物联网AR/VR融合智能家居数字化社会 第一章&#xff1a;引言 1. 二维码在数字化时代的重要性和普及程度 在数字化时代&#xff0c;二维码作…

【吊打面试官系列】Redis篇 -使用过 Redis 做异步队列么,你是怎么用的?

大家好&#xff0c;我是锋哥。今天分享关于 【使用过 Redis 做异步队列么&#xff0c;你是怎么用的&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 使用过 Redis 做异步队列么&#xff0c;你是怎么用的&#xff1f; 一般使用 list 结构作为队列&#xff0c;rpu…

31. UE5 RPG使用增强输入激活GameplayAbility(一)

在前面文章中&#xff0c;我们实现了对技能添加并直接激活功能&#xff0c;介绍了GA的相关参数配置。现在&#xff0c;我们还不能通过键位触发技能&#xff0c;正常在游戏时&#xff0c;我们需要通过键位触发技能&#xff0c;实现技能的激活。 在UE5里面添加了增强输入&#xf…

简历复印--原型模式

1.1 夸张的简历 简历的打印。"对编程来说&#xff0c;简单的复制粘贴极有可能造成重复代码的灾难。我所说的意思你根本还没听懂。那就以刚才的例子&#xff0c;我出个需求你写写看&#xff0c;要求有一个简历类&#xff0c;必须要有姓名&#xff0c;可以设置性别和年龄&am…

Unity类银河恶魔城学习记录12-2 p124 Character Stats UI源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili UI_Statslot.cs using System.Collections; using System.Collections.Gen…

root@localhost‘s password: Permission denied, please try again.

编辑、etc/ssh/sshd_config文件 ,将PermitRootLogin这行改为yes rootubuntu:/home/ubuntu# vim /etc/ssh/sshd_config 重新加载改文件 /etc/init.d/ssh restart

uniapp 设置globalStyle navigationBarTitleText 不显示

设置全局的navigationBarTitleText但是没有显示 没效果: 原因: 这里实际上设置了navigationBarTitleText 为"" 所以不会使用全局的设置 解决方法就是直接将这一行代码删除

【逆向思考 】【拓扑排序】1591. 奇怪的打印机 II

本文涉及的知识点 逆向思考 拓扑排序 LeetCode1591. 奇怪的打印机 II 给你一个奇怪的打印机&#xff0c;它有如下两个特殊的打印规则&#xff1a; 每一次操作时&#xff0c;打印机会用同一种颜色打印一个矩形的形状&#xff0c;每次打印会覆盖矩形对应格子里原本的颜色。 一…

【教程】宝塔default.db占用空间几十g解决方法|宝塔占用磁盘空间特别大解决方法|宝塔磁盘被占满怎么清理

目录 一、前言二、排查问题三、解决方法 一、前言 用过宝塔创建网站&#xff0c;大家应该都非常熟悉&#xff0c;但是用随着用的时间越来越多&#xff0c;宝塔所占用的空间也越来越多&#xff0c;不停的加大数据盘都没有用&#xff0c;我原先买了30G够用了&#xff0c;随着时间…

基本数据类型、包装类与字符串间的转换

&#xff08;1&#xff09;基本数据类型转为字符串 方式1&#xff1a;调用字符串重载的valueOf()方法 int a 10; //String str a;//错误的String str String.valueOf(a);方式2&#xff1a;更直接的方式 int a 10;String str a "";&#xff08;2&#xff09;…

汽车EDI:如何与奔驰建立EDI连接?

梅赛德斯-奔驰是世界闻名的豪华汽车品牌&#xff0c;无论是技术实力还是历史底蕴都在全球汽车主机厂中居于领先位置。奔驰拥有多种车型&#xff0c;多元化的产品布局不仅满足了不同用户画像的需求&#xff0c;也对其供应链体系有着极大的考验。 本文将为大家介绍梅赛德斯-奔驰乘…

【深入理解计算机系统第3版】有符号数和无符号数转换以及移位运算练习题2.23

题目 考虑下面的C函数&#xff1a; int fun1(unsigned word) {return (int) ((word << 24) >> 24); }int fun2(unsigned word) {return ((int) word << 24) >> 24; } 假设一个采用补码运算的机器上以32位程序来执行这些函数。还假设有符号数值的右移…

代码随想录算法训练营第30天|LeetCode236.二叉树的最小公共祖先

代码随想录算法训练营第30天|LeetCode236.二叉树的最小公共祖先 1、LeetCode236.二叉树的最小公共祖先 236. 二叉树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09; 自底向上查找&#xff0c;有点难度&#xff01; | LeetCode&#xff1a;236. 二叉树的最近公共祖先_哔…

如何通过ArkTS卡片的Canvas自定义绘制能力实现五子棋游戏卡片

介绍 本示例展示了如何通过ArkTS卡片的Canvas自定义绘制能力实现一个简单的五子棋游戏卡片。 使用Canvas绘制棋盘和黑白棋子的落子。通过卡片支持的点击事件进行交互&#xff0c;让用户在棋盘上进行黑白棋子的对局。通过TS的逻辑代码实现五子棋输赢判定、回退等逻辑计算&…

7 X 24h智能安全运维再升级!Fortinet 全面集成全新 FortiGuard SOCaaS

数字化时代网络安全威胁层出不穷&#xff0c;网络犯罪分子的狡诈攻击手段不断翻新&#xff0c;传统安全防御手段亟需进化。更为棘手的是&#xff0c;网络安全专业人才的匮乏&#xff0c;让众多企业陷入安全运营的困境。为了有效应对这一挑战&#xff0c;Fortinet全新推出FortiG…

3DGS实时高质量大规模场景渲染最新SOTA!

作者&#xff1a;小柠檬 | 来源&#xff1a;3DCV 在公众号「3DCV」后台&#xff0c;回复「原论文」可获取论文pdf 添加微信&#xff1a;dddvision&#xff0c;备注&#xff1a;3D高斯&#xff0c;拉你入群。文末附行业细分群 详细内容请关注3DCV 3D视觉精品课程&#xff1a;…