【智能排班系统】排班任务异步计算,WebSocket实时通知任务的计算状态

177 阅读6分钟

🎯导读:本文介绍了一种利用WebSocket技术优化排班系统用户体验的方法。通过异步计算和实时通信,系统能在不等待计算完成的情况下快速响应用户请求,并通过WebSocket在计算完成后通知用户。文中详细描述了WebSocket的工作原理、应用场景及其实现方式,包括后端配置、依赖引入、WebSocket端点定义以及客户端连接初始化逻辑。这一方案有效提升了系统的交互性和效率。 🏠️ 项目仓库:智能排班系统 📙 项目介绍:【智能排班系统】开源说明

场景介绍

在排班系统中,任务的排班计算需要消耗一定的时间,如果发送一个排班请求,要等待计算完成之后再返回结果的话,请求的时间比较长,用户等待时间较长,用户体验会比较差。

为了优化用户的体验,并更好地压榨cpu的性能,通常会使用多线程的方式异步地计算多个任务,这样前端发送计算请求之后,后端并不会等到任务计算结束才给前端返回数据,而是将计算任务提交到线程池,就立即返回 “计算任务添加计算成功” 。等待任务计算完成之后,再通过WebSocket通知前端任务计算完成,这时候用户即可去查看任务的计算结果。

在这里插入图片描述

WebSocket介绍

WebSocket是一种在单个TCP连接上进行全双工通信的协议。它被设计用于替代传统的HTTP轮询方式,以实现客户端与服务器之间的实时双向通信。WebSocket提供了低延迟的通信通道,并且在建立连接后,客户端和服务器都可以主动发送数据(说白话就是,平时我们都是用前端向后端发送请求,但是后端无法主动和前端聊天,WebSocket就是让两者可以自由沟通的技术)

简介

WebSocket协议的核心优势在于其能够维持一个持久的连接,使得数据传输更为高效。一旦连接建立完成,WebSocket就可以绕过HTTP的请求-响应模型,允许数据在任意方向上自由流动。这种特性非常适合需要实时交互的应用场景,比如在线聊天、实时数据分析、多人协作编辑文档等。

应用场景

  1. 实时聊天应用:WebSocket可以让用户在浏览器中进行即时消息传递,无需频繁轮询服务器。
  2. 在线协作工具:如实时文档编辑器,允许多个用户同时编辑文档并看到彼此的变化。
  3. 实时股票报价和金融数据:提供股票价格、货币汇率等实时更新的数据流。
  4. 在线游戏:尤其是那些需要低延迟通信的游戏,如多人在线游戏或实时策略游戏。
  5. 物联网(IoT):用于监控设备状态和接收来自传感器的实时数据。
  6. 远程桌面或屏幕共享:实现实时的屏幕更新和交互。
  7. 实时地图和导航:提供动态更新的地图数据和路径规划信息。

后端

依赖

<!-- websocket -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
</dependency>

配置类

该配置类的作用是:配置WebSocket支持,自动发现和注册所有使用 @ServerEndpoint 注解的WebSocket端点,使得开发者能够轻松地添加新的WebSocket端点,而不需要额外的手动配置。

package com.dam.config.webSocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    /**
     * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

组件

这段代码定义了一个WebSocket服务器端点,用于处理客户端与服务器之间的实时通信。以下是该类的主要功能和组成部分:

  1. @ServerEndpoint 注解

    • 标记了这个类是一个WebSocket端点。
    • 指定了WebSocket端点的URL路径为 /shift-scheduling-calculate-service/imserver/{token},其中 {token} 是一个路径参数,用来标识不同的用户。
  2. @Component 注解

    • WebSocketServer 注册为Spring框架中的一个组件,这样Spring容器就可以管理它。
  3. 静态成员变量

    • tokenAndSessionMap:使用 ConcurrentHashMap 存储在线客户端的会话信息,键是客户端提供的令牌(token),值是客户端的 Session 对象。
  4. 日志记录器

    • 使用 SLF4J 的 Logger 接口记录日志。
  5. 生命周期方法

    • onOpen:当浏览器与WebSocket服务器成功建立连接时调用。这里将客户端的会话信息存储在 tokenAndSessionMap 中,并记录日志。
    • onClose:当客户端断开连接时调用。从 tokenAndSessionMap 中移除相应的会话,并记录日志。
    • onError:当发生错误时调用。记录错误日志。
  6. 消息发送方法

    • sendMessage:用于向指定的客户端发送消息。如果发送过程中出现异常,则记录错误日志。

主要作用

  • 实现客户端与服务器之间的实时通信。
  • 维护一个在线客户端的会话列表,以便能够对特定客户端发送消息。
  • 提供了连接建立、断开连接和错误处理的回调方法。

通过这个类,你可以实现客户端与服务器之间基于WebSocket的实时数据交换,例如发送消息、更新状态等操作。

package com.dam.component;

import com.dam.utils.JwtUtil;
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.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @author websocket服务
 */
@ServerEndpoint(value = "/shift-scheduling-calculate-service/imserver/{token}")
@Component//将WebSocketServer注册为spring的一个bean
public class WebSocketServer {

    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * 记录当前在线连接的客户端的session
     */
    public static final Map<String, Session> tokenAndSessionMap = new ConcurrentHashMap<>();

    /**
     * 浏览器和服务端连接建立成功之后会调用这个方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        tokenAndSessionMap.put(token, session);
        log.info("有新用户加入,username={}, 当前在线人数为:{}", JwtUtil.getUsername(token), tokenAndSessionMap.size());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session, @PathParam("token") String token) {
        String username = JwtUtil.getUsername(token);
        tokenAndSessionMap.remove(username);
        log.info("有一连接关闭,移除username={}的用户session, 当前在线人数为:{}", username, tokenAndSessionMap.size());
    }

    /**
     * 发生错误的时候会调用这个方法
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    /**
     * 服务端发送消息给客户端
     */
    public void sendMessage(String message, Session toSession) {
        try {
            log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败", e);
        }
    }

}

使用(服务端给客户端发送消息)

注入webSocketServer对象

@Autowired
private WebSocketServer webSocketServer;

计算完成给客户端发送消息

webSocketServer.sendMessage(JSON.toJSONString(message), WebSocketServer.tokenAndSessionMap.get(token));

前端

定义一个变量来存储websocket连接对象,方便连接之后的后续使用

在这里插入图片描述

下面方法用来初始化webSocket对象,向后端服务器发起连接申请

/**
 * 初始化webSocket
 */
initWebSocket() {
  let _this = this
  if (typeof (WebSocket) == 'undefined') {
    console.log('您的浏览器不支持WebSocket协议')
  } else {
    console.log('您的浏览器支持WebSocket协议')
    let socketUrl = 'ws://' + IP_AND_PORT + '/scheduling/imserver/' + getToken()
    if (socket != null) {
      socket.close()
      socket = null
    }
    // 开启一个websocket服务
    socket = new WebSocket(socketUrl)
    //打开日志事件
    socket.onopen = function () {
      console.log('websocket已打开')
    }
    //浏览器端收消息,获得从服务端发送过来的文本消息
    socket.onmessage = function (msg) {
      // console.log("收到数据====" + msg.data);
      let message = _this.parseWithBigInt(msg.data);
      if (message.type === 0) {
        // console.log("message.taskId" + message.taskId);
        _this.editTaskStatus(message.taskId, message.isSuccess, message.cause)
      } else if (message.type === 1) {
        console.log('多算法计算完成')
        _this.$notify({
          title: '成功',
          message: '任务 ' + message.taskId + ' 多算法计算完成',
          type: 'success',
          duration: 0
        })
      }

    }
    //关闭事件
    socket.onclose = function () {
      console.log('websocket已关闭')
    }
    //发生了错误事件
    socket.onerror = function () {
      console.log('websocket发生了错误')
    }
  }
},

在这里插入图片描述