spring+websocket+quartz实现消息定时推送

本文介绍如何使用WebSocket、Spring MVC和Quartz实现服务器定时向客户端推送消息的功能。详细讲解了开发环境搭建、代码实现及前后端交互过程。

websocket

简单的说,websocket是真正实现了全双工通信的服务器向客户端推的互联网技术。

全双工与单工、半双工的区别?

  1. 全双工:简单地说,就是可以同时进行信号的双向传输(A->B且B->A),是瞬时同步的。
  2. 单工、半双工:一个时间段内只有一个动作发生。

推送和拉取的区别?

  1. 推:由服务器主动发消息给客户端,就像广播。优势在于,信息的主动性和及时性。
  2. 拉:由客户端主动请求所需要的数据。

实现消息通信的几种方式?

  1. 传统的http协议实现方式:。
  2. 传统的socket技术。
  3. websocket协议实现方式。

接下来我们主要讲第三种,使用websocket协议,来实现服务端定时向客户端推送消息。

  1. 开发环境:jdk1.8、tomcat7
  2. 后台:springmvc、websocket、quartz
  3. 前台:html5中新增的API
  4. 开发工具:IDEA、maven

实现步骤

一、环境搭建

(1)导入相关约束:
在pom文件中加入需要的约束,spring相关的约束,请各位自己导入,这里我就不贴出来了。

<!-- 定时器的包 -->
    <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz</artifactId>
      <version>2.3.0</version>
    </dependency>
<!-- 
	spring-support.jar 这个jar 文件包含支持UI模版(Velocity,FreeMarker,JasperReports),邮件服务,脚本服务(JRuby),缓存Cache(EHCache),任务计划Scheduling(uartz)方面的类。 
	外部依赖spring-context, (spring-jdbc, Velocity, FreeMarker, JasperReports, BSH, Groovy, JRuby, Quartz, EHCache) 
 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>5.1.1.RELEASE</version>
    </dependency>
<!-- websocket的包 -->
    <dependency>
      <groupId>javax.websocket</groupId>
      <artifactId>javax.websocket-api</artifactId>
      <version>1.1</version>
      <scope>provided</scope>
    </dependency>
    
<!--
ps:如果使用原始的配置方式,需要导入spring-websocket、spring-messaging的包,我们这里就通过注解实现
-->

(2)配置xml文件
web.xml中就配置前端控制器,大家自行配置。然后,加载springmvc的配置文件。
springmvc.xml文件中

    <!-- 自动将控制器加载到bean -->
    <context:component-scan base-package="com.socket.web" />
	<!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
        <property name="contentType" value="text/html; charset=utf-8"/>
    </bean>
    <!-- 自动注册 DefaultAnnotationHandlerMapping 与 AnnotationMethodHandlerAdapter 两个 bean, 解决了 @Controller 注解的使用前提配置 -->
    <mvc:annotation-driven/>
    
    <!-- 使用fastjson 解析json   因为本人的项目中用到了fastjson,所以这段配置大家可以忽略。 -->
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json</value>
                    </list>
                </property>
                <property name="features">
                    <list>
                        <value>WriteMapNullValue</value>
                        <value>QuoteFieldNames</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

到此,环境就基本搭建完成了。

二、完成后台的功能

这里我就直接贴出代码了,上面有相关的注释。
首先,完成websocket的实现类。

package com.socket.web.socket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: 清风一阵吹我心
 * @ProjectName: socket
 * @Package: com.socket.web.socket
 * @ClassName: WebSocketServer
 * @Description:
 * @Version: 1.0
 **/
//ServerEndpoint它的功能主要是将目前的类定义成一个websocket服务器端。注解的值将被用于监听用户连接的终端访问URL地址。
@ServerEndpoint(value = "/socket/{ip}")
@Component
public class WebSocketServer {

    //使用slf4j打日志
    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);

    //用来记录当前在线连接数
    private static int onLineCount = 0;

    //用来存放每个客户端对应的WebSocketServer对象
    private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<String, WebSocketServer>();

    //某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    //客户端的ip地址
    private String ip;

    /**
     * 连接建立成功,调用的方法,与前台页面的onOpen相对应
     * @param ip ip地址
     * @param session 会话
     */
    @OnOpen
    public void onOpen(@PathParam("ip")String ip,Session session){
        //根据业务,自定义逻辑实现
        this.session = session;
        this.ip = ip;
        webSocketMap.put(ip,this);  //将当前对象放入map中
        addOnLineCount();  //在线人数加一
        LOGGER.info("有新的连接加入,ip:{}!当前在线人数:{}",ip,getOnLineCount());
    }

    /**
     * 连接关闭调用的方法,与前台页面的onClose相对应
     * @param ip
     */
    @OnClose
    public void onClose(@PathParam("ip")String ip){
        webSocketMap.remove(ip);  //根据ip(key)移除WebSocketServer对象
        subOnLineCount();
        LOGGER.info("WebSocket关闭,ip:{},当前在线人数:{}",ip,getOnLineCount());
    }

    /**
     * 当服务器接收到客户端发送的消息时所调用的方法,与前台页面的onMessage相对应
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message,Session session){
        //根据业务,自定义逻辑实现
        LOGGER.info("收到客户端的消息:{}",message);
    }

    /**
     * 发生错误时调用,与前台页面的onError相对应
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session,Throwable error){
        LOGGER.error("WebSocket发生错误");
        error.printStackTrace();
    }


    /**
     * 给当前用户发送消息
     * @param message
     */
    public void sendMessage(String message){
        try{
            //getBasicRemote()是同步发送消息,这里我就用这个了,推荐大家使用getAsyncRemote()异步
            this.session.getBasicRemote().sendText(message);
        }catch (IOException e){
            e.printStackTrace();
            LOGGER.info("发送数据错误:,ip:{},message:{}",ip,message);
        }
    }

    /**
     * 给所有用户发消息
     * @param message
     */
    public static void sendMessageAll(final String message){
        //使用entrySet而不是用keySet的原因是,entrySet体现了map的映射关系,遍历获取数据更快。
        Set<Map.Entry<String, WebSocketServer>> entries = webSocketMap.entrySet();
        for (Map.Entry<String, WebSocketServer> entry : entries) {
            final WebSocketServer webSocketServer = entry.getValue();
            //这里使用线程来控制消息的发送,这样效率更高。
            new Thread(new Runnable() {
                public void run() {
                    webSocketServer.sendMessage(message);
                }
            }).start();
        }
    }

    /**
     * 获取当前的连接数
     * @return
     */
    public static synchronized int getOnLineCount(){
        return WebSocketServer.onLineCount;
    }

    /**
     * 有新的用户连接时,连接数自加1
     */
    public static synchronized void addOnLineCount(){
        WebSocketServer.onLineCount++;
    }

    /**
     * 断开连接时,连接数自减1
     */
    public static synchronized void subOnLineCount(){
        WebSocketServer.onLineCount--;
    }

    public Session getSession(){
        return session;
    }
    public void setSession(Session session){
        this.session = session;
    }

    public static ConcurrentHashMap<String, WebSocketServer> getWebSocketMap() {
        return webSocketMap;
    }

    public static void setWebSocketMap(ConcurrentHashMap<String, WebSocketServer> webSocketMap) {
        WebSocketServer.webSocketMap = webSocketMap;
    }
}

然后写我们的定时器(quartz),这里我就不详解定时器了。大家可以自行去了解。
这里我使用的是xml注解的方式,创建一个job类,此类不需要继承任何类和实现任何接口。

package com.socket.web.quartz;

import com.socket.web.socket.WebSocketServer;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: 清风一阵吹我心
 * @ProjectName: socket
 * @Package: com.socket.web.quartz
 * @ClassName: TestJob
 * @Description:
 * @Version: 1.0
 **/
public class TestJob {

    public void task(){
        //获取WebSocketServer对象的映射。
        ConcurrentHashMap<String, WebSocketServer> map = WebSocketServer.getWebSocketMap();
        if (map.size() != 0){
            for (Map.Entry<String, WebSocketServer> entry : map.entrySet()) {
                WebSocketServer webSocketServer = entry.getValue();
                try {
                    //向客户端推送消息
                    webSocketServer.getSession().getBasicRemote().sendText("每隔两秒,向客户端推送一次数据");
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }else {
            System.out.println("WebSocket未连接");
        }
    }
}

定时器的实现类就完成了,我们还需要在springmvc.xml中进行配置
springmvc.xml配置:

<!-- 要执行的任务类 -->
    <bean id="testJob" class="com.socket.web.quartz.TestJob"></bean>

    <!-- 将需要执行的定时任务注入job中 -->
    <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="testJob"/>
        <!-- 任务类中需要执行的方法 -->
        <property name="targetMethod" value="task"></property>
        <!-- 上一次未执行完成的,要等待有再执行。 -->
        <property name="concurrent" value="false" />
    </bean>

    <!-- 基本的定时器,会绑定具体的任务。 -->
    <bean id="trigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
        <property name="jobDetail" ref="jobDetail"/>
        <property name="startDelay" value="3000"/>
        <property name="repeatInterval" value="2000"/>
    </bean>

    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="trigger"/>
            </list>
        </property>
    </bean>

接下来是controller层的代码,就一个登录的功能。

package com.socket.web.controller;

import com.socket.domain.User;
import com.sun.org.apache.bcel.internal.generic.RETURN;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.UUID;

/**
 * @Author: 清风一阵吹我心
 * @ProjectName: socket
 * @Package: com.socket.web
 * @ClassName: ChatController
 * @Description:
 * @CreateDate: 2018/11/9 11:04
 * @Version: 1.0
 **/
@RequestMapping("socket")
@Controller
public class ChatController {

    /**
     * 跳转到登录页面
     * @return
     */
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String goLogin(){
        return "login";
    }

    /**
     * 跳转到聊天页面
     * @param request
     * @return
     */
    @RequestMapping(value = "/home",method = RequestMethod.GET)
    public String goMain(HttpServletRequest request){
        HttpSession session = request.getSession();
        if (null == session.getAttribute("USER_SESSION")){
            return "login";
        }
        return "home";
    }

    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public String login(User user, HttpServletRequest request){
        HttpSession session = request.getSession();
        //将用户放入session
        session.setAttribute("USER_SESSION",user);
        return "redirect:home";
    }

}

以上就是登录的代码了,基本上就是伪代码,只要输入用户名就可以了,后面的逻辑,大家可以根据自己的业务来实现。

最后就是前台页面的设计了,登录,login.jsp

<%--
  Created by IntelliJ IDEA.
  User: 清风一阵吹我心
  Date: 2018/11/9
  Time: 10:41
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<c:set var="path" value="${pageContext.request.contextPath}"/>
<html>
<head>
    <title>登录</title>
</head>
<body>
<form action="${path}/socket/login" method="post">
    登录名:<input type="text" name="username"/>
    <input type="submit" value="登录"/>
</form>
</body>
</html>

消息接收页面,home.jsp

<%--
  Created by IntelliJ IDEA.
  User: 清风一阵吹我心
  Date: 2018/11/9
  Time: 10:41
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>聊天</title>
    
    <script type="text/javascript">
        //判断当前浏览器是否支持WebSocket
        var webSocket = null;
        if ('WebSocket' in window) {
            webSocket = new WebSocket("ws://localhost:9001/socket/127.0.0.1");
        }
        else if ('MozWebSocket' in window) {
            webSocket = new MozWebSocket("ws://localhost:9001/socket/127.0.0.1");
        }
        else {
            alert('Not support webSocket');
        }

        //打开socket,握手
        webSocket.onopen = function (event) {
            alert("websocket已经连接");
        }
        //接收推送的消息
        webSocket.onmessage = function (event) {
            console.info(event);
            alert(event.data);
        }
        //错误时
        webSocket.onerror = function (event) {
            console.info("发生错误");
            alert("websocket发生错误" + event);
        }

        //关闭连接
        webSocket.onclose = function () {
            console.info("关闭连接");
        }

        //监听窗口关闭
        window.onbeforeunload = function (event) {
            webSocket.close();
        }
    </script>
</head>
<body>

</body>
</html>

基本上,数据推送的功能就完成了,下面附上效果图。
启动tomcat。后台定时器两秒刷新一次,判断是否有websocket连接。
在这里插入图片描述

登录页面:
在这里插入图片描述

数据推送页面:
在这里插入图片描述

服务器定时向客户端推送数据的功能就完成了,有不明白的可以给博主留言,如果有什么错误,也希望各位朋友指出,谢谢大家。

附上源码:spring+websocket+quartz实现消息定时推送

引用星爷的至理名言。

做人如果没有梦想,就跟咸鱼有什么区别!

<think>我们使用WebSocket结合Quartz定时任务来实现大屏数据的实时刷新。基本思路是:后端通过Quartz定时任务周期性地从数据源(如数据库)获取最新数据,然后通过WebSocket推送给前端大屏。这样前端无需主动轮询,即可实现数据的自动更新。 具体步骤: 1. **后端搭建WebSocket服务**: 使用Spring Boot集成WebSocket,创建WebSocket服务端,用于与前端建立长连接并推送数据。 2. **配置Quartz定时任务**: 使用Quartz创建定时任务,定时执行数据查询逻辑,并将查询结果通过WebSocket推送给前端。 3. **前端建立WebSocket连接**: 前端大屏页面创建WebSocket客户端,连接到后端的WebSocket服务,并监听消息事件,接收数据并更新视图。 参考引用[1]和[2]中的示例,我们可以构建如下: ## 一、后端实现Spring Boot + WebSocket + Quartz) ### 1. 引入依赖 在`pom.xml`中添加依赖: ```xml <!-- WebSocket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!-- Quartz --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> ``` ### 2. 配置WebSocket 创建WebSocket配置类: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myWebSocketHandler(), "/socket/*") .setAllowedOrigins("*"); } @Bean public WebSocketHandler myWebSocketHandler() { return new MyWebSocketHandler(); } } ``` 创建自定义的WebSocket处理器,用于管理连接和发送消息: ```java public class MyWebSocketHandler extends TextWebSocketHandler { // 保存所有活动的WebSocket会话 private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 当新的WebSocket连接建立时,将其加入会话池 String sessionId = session.getId(); sessions.put(sessionId, session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 可以处理从前端传来的消息(可选) } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { // 连接关闭时移除会话 sessions.remove(session.getId()); } // 向所有连接的客户端广播消息 public static void broadcast(String message) throws IOException { for (WebSocketSession session : sessions.values()) { if (session.isOpen()) { session.sendMessage(new TextMessage(message)); } } } } ``` ### 3. 配置Quartz定时任务 创建Quartz任务,定时从数据库获取数据并推送: 首先,定义任务类: ```java @Component public class DataRefreshJob implements Job { @Autowired private DataService dataService; @Override public void execute(JobExecutionContext context) { // 1. 从数据库或其他数据源获取最新数据 String data1 = dataService.getData1(); // 假设获取数据1 String data2 = dataService.getData2(); // 假设获取数据2 // 2. 将数据组装成JSON或其他格式(为了前端区分,可以定义不同的主题) JSONObject json = new JSONObject(); json.put("topic", "test1"); // 主题1 json.put("data", data1); String message1 = json.toJSONString(); json.put("topic", "test2"); // 主题2 json.put("data", data2); String message2 = json.toJSONString(); try { // 3. 通过WebSocket推送 MyWebSocketHandler.broadcast(message1); MyWebSocketHandler.broadcast(message2); } catch (IOException e) { e.printStackTrace(); } } } ``` 配置Quartz调度器(使用Spring Boot的`SchedulerFactoryBean`): ```java @Configuration public class QuartzConfig { @Bean public JobDetail dataRefreshJobDetail() { return JobBuilder.newJob(DataRefreshJob.class) .withIdentity("dataRefreshJob") .storeDurably() .build(); } @Bean public Trigger dataRefreshJobTrigger() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(5) // 每5秒执行一次 .repeatForever(); return TriggerBuilder.newTrigger() .forJob(dataRefreshJobDetail()) .withIdentity("dataRefreshTrigger") .withSchedule(scheduleBuilder) .build(); } } ``` 注意:确保`DataService`能够被注入到Job中,可以通过在Job类上使用`@DisallowConcurrentExecution`来避免并发问题(如果需要)。 ## 二、前端实现 前端大屏页面建立WebSocket连接,并监听不同主题的数据,更新对应的DOM元素。 参考[1]中的前端代码,我们可以创建多个WebSocket连接(每个主题一个),或者在一个连接中通过消息的主题来区分不同数据(推荐使用一个连接,通过消息中的主题字段区分)。 这里我们采用一个连接,并让后端推送消息中包含主题字段(如前所述,消息为JSON格式,包含`topic`和`data`字段)。 前端代码示例: ```html <!DOCTYPE html> <html> <head> <title>大屏数据实时刷新</title> <script> // 创建WebSocket连接 var socket = new WebSocket("ws://你的服务器地址/socket/大屏标识"); // 连接建立时 socket.onopen = function() { console.log("WebSocket连接已建立"); }; // 接收消息 socket.onmessage = function(event) { var message = JSON.parse(event.data); var topic = message.topic; var data = message.data; // 根据主题更新不同的DOM元素 if (topic === 'test1') { document.getElementById('onlineuser').innerHTML = data; } else if (topic === 'test2') { document.getElementById('visituser').innerHTML = data; } }; // 错误处理 socket.onerror = function(error) { console.log('WebSocket错误: ' + error); }; // 关闭连接 socket.onclose = function() { console.log('WebSocket连接关闭'); }; // 页面关闭前关闭WebSocket window.onbeforeunload = function() { socket.close(); }; </script> </head> <body> <div>在线用户数: <span id="onlineuser">0</span></div> <div>访问用户数: <span id="visituser">0</span></div> </body> </html> ``` ## 三、优化考虑 1. **增量更新**:在Quartz任务中,查询数据时使用增量方式(如通过时间戳获取上次刷新后的新数据),避免全量查询。 2. **连接管理**:使用多个主题时,可以设计更高效的消息结构,减少消息数量。 3. **心跳机制**:为确保连接活性,可以加入心跳检测。 通过以上步骤,我们可以实现大屏数据的实时刷新(如每5秒刷新一次)。 注意:在高并发场景下,需要优化WebSocket服务端,例如使用分布式WebSocket(如借助Redis发布订阅)和负载均衡。 引用[1]中提到了使用Quartz定时推送,引用[2]中也采用了Quartz和RabbitMQ,但我们这里没有使用消息队列,而是直接推送。如果数据更新频率高且需要解耦,可以考虑引入消息中间件(如RabbitMQ),但根据需求选择。 请根据实际情况进行调整。 </think>### 使用WebSocketQuartz实现大屏数据实时刷新的详细方案 #### 一、技术架构原理 ```mermaid sequenceDiagram 大屏前端->>WebSocket服务端: 建立长连接 loop Quartz定时任务 Quartz调度器->>数据服务层: 触发数据查询 数据服务层->>数据库: 执行增量SQL查询 数据服务层->>WebSocket服务端: 封装新数据 WebSocket服务端->>大屏前端: 推送JSON数据 end Note right of WebSocket服务端: 避免传统AJAX轮询的开销 ``` #### 二、核心实现步骤 ##### 1. 后端实现Spring Boot) **(1) WebSocket服务端配置** ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(dashboardHandler(), "/dashboard") .setAllowedOrigins("*"); // 允许跨域 } @Bean public WebSocketHandler dashboardHandler() { return new DashboardWebSocketHandler(); } } ``` **(2) WebSocket消息处理器** ```java public class DashboardWebSocketHandler extends TextWebSocketHandler { private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) { sessions.put(session.getId(), session); } // 推送给所有连接的客户端 public static void broadcast(String message) throws IOException { for (WebSocketSession session : sessions.values()) { if (session.isOpen()) { session.sendMessage(new TextMessage(message)); } } } } ``` **(3) Quartz定时任务配置** ```java @Configuration public class QuartzConfig { @Bean public JobDetail dataRefreshJobDetail() { return JobBuilder.newJob(DataRefreshJob.class) .withIdentity("dashboardDataJob") .storeDurably() .build(); } @Bean public Trigger dataRefreshTrigger() { return TriggerBuilder.newTrigger() .forJob(dataRefreshJobDetail()) .withIdentity("dashboardTrigger") .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) // 每5秒执行 .build(); } } ``` **(4) 定时任务实现(核心数据获取)** ```java public class DataRefreshJob implements Job { @Autowired private DataService dataService; @Override public void execute(JobExecutionContext context) { // 1. 获取增量数据(避免全表扫描) Map<String, Object> newData = dataService.getIncrementalData( LocalDateTime.now().minusSeconds(5)); // 获取过去5秒数据 // 2. 通过WebSocket推送 try { String jsonData = new ObjectMapper().writeValueAsString(newData); DashboardWebSocketHandler.broadcast(jsonData); } catch (IOException e) { e.printStackTrace(); } } } ``` **(5) 数据服务层(增量查询优化)** ```java @Service public class DataService { @Autowired private JdbcTemplate jdbcTemplate; public Map<String, Object> getIncrementalData(LocalDateTime lastFetchTime) { // 使用时间戳过滤增量数据 String sql = "SELECT metric1, metric2 FROM dashboard_data " + "WHERE update_time > ?"; return jdbcTemplate.queryForMap(sql, lastFetchTime); } } ``` ##### 2. 前端实现(大屏数据接收) ```html <script> // 1. 建立WebSocket连接 const socket = new WebSocket('ws://your-domain.com/dashboard'); // 2. 数据接收处理 socket.onmessage = (event) => { const data = JSON.parse(event.data); // 更新大屏组件(以ECharts为例) chart.setOption({ series: [{ data: data.metric1 // 实时更新图表数据 }] }); // 更新数字指标 document.getElementById('metric2').innerText = data.metric2; }; // 3. 异常处理 socket.onerror = (error) => { console.error('WebSocket错误:', error); // 自动重连机制 setTimeout(connectWebSocket, 5000); }; </script> ``` #### 三、关键优化点 1. **增量数据获取** - 使用`update_time > last_fetch`条件避免全表扫描 - 数据库需建立时间戳索引提升查询效率 2. **连接管理** - 添加心跳机制保活:`ping/pong`每30秒交互 - 断线自动重连(前端实现) 3. **性能优化** ```java // Quartz配置连接池(application.properties) spring.quartz.properties.org.quartz.threadPool.threadCount=5 // 控制并发线程数 ``` 4. **数据压缩** ```java // 服务端启用压缩(WebSocketConfig) registry.addHandler(dashboardHandler(), "/dashboard") .addInterceptors(new HttpSessionHandshakeInterceptor()) .withSockJS() .setClientLibraryUrl("/webjars/sockjs-client/1.5.1/sockjs.min.js"); ``` #### 四、方案优势 1. **实时性**:数据延迟控制在1-5秒内 2. **资源节约**:相比AJAX轮询减少80%请求量[^1] 3. **扩展性**:支持多主题数据推送(如test1/test2通道) 4. **兼容性**:通过SockJS兼容IE9+等老旧浏览器[^3] > **注意事项** > 1. 生产环境需配置Nginx反向代理支持WebSocket > 2. 当连接数>5000时建议使用Redis发布订阅扩展[^2] > 3. 数据库查询频率需根据TPS动态调整(参考`Quartz Cron表达式`)
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值