管理系统开发技(8): websocket 推送进度条百分比并展示

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

前言

本次功能点需要解决的问题是使用 websocket 完成批量生成中的页面进度条加载显示问题。

本次的难点是:

  • 1、针对大数据下的批量生成采用多线程进行优化
  • 2、通过websocket 推送进度条给前端
  • 3、前端通过订阅并展示进度条

本次页面展示的是简易版,后续还会持续优化,但是功能已经基本实现了。

一、数据并发处理

在调用后端接口的时候,这里是需要异步处理的。返回给前端响应,然后异步处理业务逻辑。后面的进度条任务就交给websocket 来监听处理了。

而这个异步处理任务的时候,我们需要用到线程池去处理,但是这又有个问题,我们需要等待所有线程执行结束之后做个最后的动作,把剩余的list 更新到数据库中。而这就需要判断所有的线程是否执行完毕。因为主线程是自上往下执行的,它并不需要等待你异步的执行结果就往下执行了,所以,这块需要等待。

1.1 线程池

根据上述分析,代码逻辑是这样的,通过线程池进行处理

        // 1、异步线程处理
        new Thread(() -> { 
            for (DeviceInfo deviceInfo : deviceInfos) {
                pool.execute(new Runnable() {
                    @Override
                    public void run() {
                    // 2、执行业务逻辑
        }).start();
        // 3、返回响应
        return ResponseData.successResponse();
复制代码

1.2 如何计算百分比

此时,根据经验,在并发处理一个数的时候,我们需要使用到原子类,

可以在循环外面定义一个 原子类 ic ,然后在循环体内对齐进行自增操作,与总数做除法运算

DecimalFormat df = new DecimalFormat();
df.setMaximumFractionDigits(2);//设置保留几位小数
df.setMinimumFractionDigits(2);
AtomicInteger ai = new AtomicInteger(0);
String result = df.format(ai.incrementAndGet() * 100.00 / amout);
复制代码

1.3 如何等待所有线程执行结束

在所有线程执行结束之后,我们需要把剩余的数据更新到数据库中,而等待线程执行结束的方法常用的有以下几种:

1.3.1 通过pool 的isTerminated() 方法

例如下面的例子:

public class Test {  
  
    public static void main(String args[]) throws InterruptedException {  
        ExecutorService exe = Executors.newFixedThreadPool(50);  
        for (int i = 1; i <= 5; i++) {  
            exe.execute(new SubThread(i));  
        }  
        exe.shutdown();  
        while (true) {  
            if (exe.isTerminated()) {  
                System.out.println("结束了!");  
                break;  
            }  
            Thread.sleep(200);  
        }  
    }  
复制代码

当调用 exe.shutdown() 方法时,线程池不再接收任何新任务,此时线程池并不会立刻退出,直到线程池中的任务都已经处理完成,才会退出,然后弄个 while(true) 监听 isTerminated 方法来判断是否线程池中的所有线程都已经执行完毕,然后执行最后的逻辑。

理论上,可以用这个来实现我这功能,但是测试了一波之后发现无法处理我的业务,就没采用了。

1.3.2 CyclicBarrier 循环屏障

循环屏障 在使用过之后才发现,它字面意思是个可循环的屏障。它要做的事情就是让一组线程到达一个屏障时被阻塞,知道最后一个线程到达屏障时,然后执行下一步操作,然后清0,继续到达下一个屏障。它内部的计数器是可以被重新执行的。

因此,它是可以流处理的,每次线程执行到一个屏障的时候就 入一次库。理论上是可以做到的,但是为了不压垮数据库,也不采纳。

1.3.3 CountDownLatch 闭锁

它是一个同步工具类,用来协调多个线程之间的同步。它的作用是,在完成某些运算时,只有其他所有线程的运算全部完成,当前线程的运算才能继续执行

其实它就是一个计数器,每个线程来一次就把总值减一,最后到0 的时候在执行最后的逻辑。

因为我们的总值是可以获取到的,所以可以创建出同等大小的计数器,具体代码如下:

   // 1、创建一个计数器
   CountDownLatch countDownLatch = new CountDownLatch(amout);
            for (DeviceInfo deviceInfo : deviceInfos) {
                pool.execute(new Runnable() {
                    @Override
                    public void run() {
                     
                        //2、处理业务逻辑,然后减一 
                       countDownLatch.countDown();
                     
                    }
                });
            }
            try {
                // 3、等待,知道计数器为0 的时候执行后续代码
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 4、插入剩余list
           if (list.size() != 0) {
               deviceInfoMapper.updateByBatch(list);
               list.clear();
           }
复制代码

二、websocket 使用

websocket 这块直接调用的这个方法,是本地封装的,用到了 SimpMessagingTemplate ,有兴趣的可以百度下怎么集成。

    @PostMapping("/sendMsg")
    public Boolean sendMsg(@RequestBody SocketDTO socketDTO) {
        if (checkCertificate(socketDTO.getPassword())) {
            log.error("socket account or password Incorrect -> password = {}", socketDTO.getPassword());
            return false;
        }
        simpMessagingTemplate.convertAndSend(socketDTO.getTopic(), socketDTO.getData());
        log.debug("socket send success -> topic = {} , data = {}", socketDTO.getTopic(), JSONObject.toJSONString(socketDTO.getData()));
        return true;
    }
复制代码

而,我们需要用的就是调用 websocket 发送消息 百分比数据 给前端即可

   socketApi.sendMsg(SocketDTO
        .builder()
        .password(configComponent.getPassword())
        .topic(ElectricityStationConst.DEVICE_BATCH_PRODUCE + sysId)
        .data(result)
        .build()
        );
复制代码

三、前端订阅展示百分比

 <el-progress :text-inside="true" :stroke-width="26" :percentage="processvalue" v-show="processshow" ></el-progress>
​
mounted() {
    this.init()
    this.stomp()
  },
  methods:{
    stomp(){
      this.$bus.on(TopicConst.DEVICE_BATCH_PRODUCE+StoreService.getStationId(),data =>{
          this.$data.processshow=true
          this.$data.processvalue=parseInt(data)
          if(this.$data.processvalue==100){
            this.$message.success("批量打印完成");
            this.$data.processshow=false
          }
        })
    // 批量生成二维码关系
    productBatch(row){
      console.log(StoreService.getStationId())
      this.$api.req("/am/device/img/produceBatch",row,res=>{
        this.$message.success("二维码批量生成中,请稍等.....")
        this.$stomp.sub(TopicConst.DEVICE_BATCH_PRODUCE+StoreService.getStationId(),data =>{
          this.$bus.emit(TopicConst.DEVICE_BATCH_PRODUCE+StoreService.getStationId(),data)
        })
        this.$data.loading = false;
        this.$data.processshow=true;
      },res=>{
        this.$message.error(res.msg)
      })
    },
 },
复制代码

image-20210806100235808

不过,vue 的进度条好像只能展示整数,而不能传入小数的,不然控制台会出现很多提醒,因此这里我又把值转成了整数。

image-20210806112011552

最后,终于实现了这一小小的功能,这一过程确实成长了不少。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享