前端订阅推送WebSocket定时任务

news2024/12/24 9:41:57

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/1562109.html

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

相关文章

路径规划——搜索算法详解(六):LPA*算法详解与Matlab代码

上文讲解了D*算法&#xff0c;D*算法为在动态环境下进行路径规划的场景提出了可行的解决方案&#xff0c;本文将继续介绍另外一种动态规划路径的方法——Lifelong Planning A*&#xff08;LPA*&#xff09;算法。 该算法可以看作是A*的增量版本&#xff0c;是一种在固定起始点…

语音克隆技术浪潮:探索OpenAI Voice Engine的奇妙之旅

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Bridge Champ与Ignis公链:探索Web3游戏的新未来

在数字化和去中心化的浪潮中&#xff0c;Web3游戏与公链的融合为游戏行业带来了新的变革。特别是&#xff0c;Bridge Champ和Ignis公链的结合&#xff0c;展示了一种全新的游戏生态模式&#xff0c;不仅为玩家提供了更加公平、透明的游戏体验&#xff0c;同时也为游戏开发和运营…

Higress 基于自定义插件访问 Redis

作者&#xff1a;钰诚 简介 基于 wasm 机制&#xff0c;Higress 提供了优秀的可扩展性&#xff0c;用户可以基于 Go/C/Rust 编写 wasm 插件&#xff0c;自定义请求处理逻辑&#xff0c;满足用户的个性化需求&#xff0c;目前插件已经支持 redis 调用&#xff0c;使得用户能够…

C++ 注册Nacos

下载源码&#xff1a; git clone GitHub - nacos-group/nacos-sdk-cpp: C client for Nacos 编译源码 cd nacos-sdk-cpp cmake . make 生成库文件 在nacos-sdk-cpp 下 注册nacos 将include 和libnacos-cli.so libnacos-cli-static.a 放入你的工程 如果Nacos服务地址:…

3.26号arm

1. SPI相关理论 1.1 概述 spi是一种同步全双工串行总线&#xff0c;全称串行外围设备接口 通常SPI通过4个引脚与外部器件相连&#xff1a; MISO&#xff1a;主设备输入/从设备输出引脚。该引脚在从模式下发送数据&#xff0c;在主模式下接收数据。 MOSI&#xff1a;主设备输…

LangChain入门:9.使用FewShotPromptTemplate实现智能提示工程

在构建智能提示工程时&#xff0c;LangChain 提供了强大的 FewShotPromptTemplate 模型&#xff0c;它可以帮助我们更好地利用示例来指导大模型生成更加优质的提示。 在这篇博文中&#xff0c;我们将使用 LangChain 的 FewShotPromptTemplate 模型来设计一个智能提示工程&#…

StarRocks使用Minio备份和还原

1.安装minio minio api端口&#xff1a;9090 下文用到这个端口 必须提前创建好桶: packfdv5 名称自定义和后面对上就可以 2.创建备份仓库 格式&#xff1a; CREATE REPOSITORY <repository_name> WITH BROKER ON LOCATION "s3a://<bucket_name>/backup…

47.goto语句

目录 一.goto语句 二.语法格式 三.举例 四.视频教程 一.goto语句 goto语句可以使程序在没有任何条件的情况下跳转到指定位置&#xff0c;所以goto语句也就跳转语句。 二.语法格式 格式1&#xff1a;goto label&#xff1a;//其他代码 lable&#xff1a;//其他代码格式2&a…

【Node.js从基础到高级运用】二十、Node.js 强大的REPL

引言 Node.js REPL&#xff08;Read-Eval-Print Loop&#xff09;是一种交互式的命令行工具&#xff0c;它允许开发者快速地执行JavaScript代码&#xff0c;并查看结果。这个功能在进行快速原型设计、调试、学习JavaScript或Node.js时非常有用。 启动REPL 首先&#xff0c;确保…

【总结】在嵌入式设备上可以离线运行的LLM--Llama

文章目录 Llama 简介运用另一种&#xff1a;MLC-LLM 一个令人沮丧的结论在资源受限的嵌入式设备上无法运行LLM&#xff08;大语言模型&#xff09;。 一丝曙光&#xff1a;tinyLlama-1.1b&#xff08;10亿参数&#xff0c;需要至少2.98GB的RAM&#xff09; Llama 简介 LLaMA…

智慧安防监控EasyCVR视频调阅和设备录像回看无法自动播放的原因排查与解决

智慧安防监控EasyCVR视频管理平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。国标GB28181协议视频监控/视频汇聚EasyCVR平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、…

Spring Boot项目启动速度优化

1、配置自动配置排除列表&#xff0c;减少启动自动配置扫描&#xff0c;配置项spring.autoconfigure.exclude 2、启动类添加索引注解Indexed&#xff0c;去除启动过程中 Components 的扫描步骤&#xff0c;直接从索引文件读取。 import org.springframework.stereotype.lndexe…

ansible-tower安装

特别注意&#xff1a;不需要提前安装ansible&#xff0c;因为ansible tower中的setup.sh脚本会下载对应的ansible版本 ansible tower不支持Ubuntu系统,对cenos系统版本也有一定的限制&#xff0c;建议使用centos7.9。 准备一台全新的机器安装&#xff0c;因为ansible tower需要…

第21章-直连路由和静态路由

1. 直连路由 1&#xff09;定义&#xff1a;指路由器接口直接相连的网段的路由&#xff1b; 2&#xff09;特点&#xff1a; ① 不需要特别的配置&#xff0c;双UP(物理层数据链路层)&#xff1b; ② 在路由器的接口上配置IP地址即可&#xff1b; ③ 开机自动产生&#xff1b; …

Docker容器赋能TitanIDE:引领编程新纪元的集成开发环境

Docker是一种容器化技术&#xff0c;它可以将应用程序和其所有的依赖项打包到一个轻量级、可移植的容器中。以下是Docker的基本概念和优势&#xff1a; 基本概念&#xff1a; 镜像&#xff08;Image&#xff09;&#xff1a;一个镜像是一个只读的模板&#xff0c;可以用于创建…

docker + miniconda + python 环境安装与迁移

本文主要列出从安装到安装python环境到迁移环境的整体步骤。 windows与linux之间进行测试。 一、docker 安装和测试 【linux端】 可以参考其他教程&#xff0c;不在此赘述&#xff0c;以windows端举例。 【windows端】 我的是windows10 家庭中文版&#xff0c;docker 安装和…

如何查询网站是否被搜索引擎收录

怎么看网站有没有被百度收录 对于网站所有者来说&#xff0c;了解自己的网站是否被百度搜索引擎收录是非常重要的。只有被收录&#xff0c;网站才能在百度搜索结果中展现&#xff0c;从而获取流量和曝光。下面介绍几种方法&#xff0c;让您快速了解自己的网站是否被百度收录。…

MySQL之存储引擎,详细总结

在介绍存储引擎之前我们先了解了解MySQL的体系结构&#xff1a; 连接层 最上层是一些客户端和链接服务&#xff0c;主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限 服务层 第二层架构主要完成大多数的核心…

海康Ehome2.0与5.0设备接入EasyCVR视频汇聚平台时的配置区别

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…