小工具知识网

当前位置: 首页 > 行业知识 > 前端订阅后端推送WebSocket定时任务

行业知识

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

2026-11-12 22:44:43 来源:互联网转载

0.需求

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

1.前言知识

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

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

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

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

2.后端实现

2.1不带参数

2.1.1添加依赖:

         org.springframework.boot        spring-boot-starter-websocket    

2.1.2websocket配置:

/** * 通过EnableWebSocketMessageBroker * 开启使用STOMP协议来传输基于代理(message broker)的消息, * 此时浏览器支持使用@MessageMapping 就像支持@RequestMapping一样。 *///WebSocket的配置类@Configuration//开启对WebSocket的支持@EnableWebSocketMessageBrokerpublic 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 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.前端验证

            Title                        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 }));        // }        

    Seems your browser doesn't support Javascript! Websocket relies on Javascript being enabled. Please enable Javascript and reload this page!

    连接广播频道 Connect 取消连接 Disconnect 广播消息 Send 广播消息1 Send 接收到的消息:

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

上一篇:轻松设置网关,简单教程助你轻松搞定!

下一篇:揭秘网站源码下载,操作需谨慎,安全先行!