导出文件下载进度条简单实现

前言

       今天要跟大家分享的是一个导出数据进度条的简单实现,适用场景用在数据量大、组织数据耗时的情况下的简单实现。


一、设计思路

1、导出数据生成文件上传到OSS,
2、导出数据状态存redis缓存,
3、前端发导出请求后,返回的文件key
4、请求后端,后端查询缓存情况返回
5、前端解析是否完成标值,如果完成结束轮询,执行下载get下载,如果未完成,等待下一次轮询


二、设计时序图

在这里插入图片描述

三、核心代码

1.导出请求

下载请求

/**
     * 因子达标分析汇总表导出
     *
     * @param airEnvQualityQueryVo 因子达标分析汇总表导出
     * @return 统一出参
     */
    @PostMapping("/propSummaryData/export")
    @ApiOperation("因子达标分析汇总表导出")
    public RestMessage propSummaryData4Export(@RequestBody AirEnvQualityQueryVo airEnvQualityQueryVo) {
        Assert.notNull(airEnvQualityQueryVo, "查询参数不能为空");
        Assert.notNull(airEnvQualityQueryVo.getStartTime(), "开始时间不能为空");
        Assert.notNull(airEnvQualityQueryVo.getEndTime(),"结束时间不能为空");
        Assert.isTrue(StringUtils.isNotBlank(airEnvQualityQueryVo.getQueryType()),"查询类型不能为空");
        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
        String key = "propSummaryData:"+formatter.format(new Date());
        AsyncUtil.submitTask(key,() ->{
            //获取并组织excel数据
            String url;
            try {
                url = airEnvironmentExportService.propSummaryData4Export(airEnvQualityQueryVo,key);
            } catch (Exception e) {
                throw new BusinessException(e.getMessage());
            }
            return url;
        });
        return RestBuilders.successBuilder().data(key).build();
    }

serviceImpl

/**
     * 因子达标分析汇总表导出
     *
     * @param airEnvQualityQueryVo 因子达标分析汇总表导出
     * @return 统一出参
     */
    @Override
    public String propSummaryData4Export(AirEnvQualityQueryVo airEnvQualityQueryVo, String key) throws IOException {
        //获取汇聚数据
        AirEnvQualityResultOverviewVo resultOverviewVo = airEnvironmentQualityStatisticsService.getAirEnvQualityResultOverviewVo(airEnvQualityQueryVo);
        //数据转换
        resultOverviewVo.setTqRateCompStr(rateHandlerStr(resultOverviewVo.getTqRateComp()));
        resultOverviewVo.setSqRateCompStr(rateHandlerStr(resultOverviewVo.getSqRateComp()));
        //获取或者数据
        List<AirEnvQualityPropSummaryVo> airEnvQualityPropSummaryVos = airEnvironmentQualityStatisticsService.propSummaryData(airEnvQualityQueryVo);
        AtomicInteger done = new AtomicInteger();
        AsyncUtil.setTotal(key,airEnvQualityPropSummaryVos.size());
        airEnvQualityPropSummaryVos.forEach(vo ->{
            //数据转换
            vo.setBqReachRateStr(rateHandler(vo.getBqReachRate()));
            vo.setTqReachRateCompStr(rateHandlerStr(vo.getTqReachRateComp()));
            vo.setSqReachRateCompStr(rateHandlerStr(vo.getSqReachRateComp()));
            vo.setBqExceedRateStr(rateHandler(vo.getBqExceedRate()));
            vo.setTqExceedRateCompStr(rateHandlerStr(vo.getTqExceedRateComp()));
            vo.setSqExceedRateCompStr(rateHandlerStr(vo.getSqExceedRateComp()));
            done.getAndIncrement();
            AsyncUtil.setDone(key,done.get());
        });
        //组织导出数据
        Map<String,Object> map = new HashMap<>();
        map.put("p",resultOverviewVo);
        map.put("w",airEnvQualityPropSummaryVos);
        String url = getExcelUrl(map, "propSum.xlsx", "因子分析汇总");
        return url;
    }

2.核心工具类

AsyncUtil负责异步更新生成文件数据组织情况更新,存储到缓存

import cn.hutool.core.collection.CollectionUtil;
import com.easylinkin.oss.OSSBaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

@Component
public class AsyncUtil implements ApplicationContextAware {

  static Logger LOG = LoggerFactory.getLogger(AsyncUtil.class);
  public static ExecutorService executor = Executors.newFixedThreadPool(40);
  public static ScheduledExecutorService ex = Executors.newScheduledThreadPool(1);
  static List<String> keys = new ArrayList<>();
  static boolean scheduleIsStart = false;

  private static OSSBaseService ossService;

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    ossService = applicationContext.getBean(OSSBaseService.class);
  }

  public static RedisTemplate<String, RedisAsyResultData> getRedisTemplate() {
    return SpringUtils.getBean("redisTemplate", RedisTemplate.class);
  }

  static void updateKeyLiveTime() {
    if (!scheduleIsStart) {
      // 更新redis中缓存的过期时间
      ex.scheduleAtFixedRate(() -> {
        try {
          LOG.info("----- update AsyncResult keys length:{} -----",
              keys.size());
          if (CollectionUtil.isNotEmpty(keys)) {
            List<RedisAsyResultData> multiGet =
                getRedisTemplate().opsForValue().multiGet(keys);
            for (RedisAsyResultData result : multiGet) {
              if (result != null) {
                String key = result.getRedisKey();
                getRedisTemplate()
                    .expire(key, 5, TimeUnit.MINUTES);
              }
            }
          }
        } catch (Exception e) {
          scheduleIsStart = false;
          LOG.error(e.getMessage(), e);
        }
      }, 1, 3, TimeUnit.MINUTES);
      scheduleIsStart = true;
    }
  }

  public static RedisAsyResultData submitExportTask(String key, Supplier supplier) {
    RedisAsyResultData rs = new RedisAsyResultData();
    rs.setSuccess(false);
    rs.setRedisKey(key);
    rs.setDone(0);
    rs.setTotal(100);
    setToRedis(rs, key);
    if (!keys.contains(key)) {
      keys.add(key);
    }
    String finalKey = key;
    executor.submit(() -> {
      String msg = null;
      try {
        Object o = supplier.get();
        rs.setData(o);
        rs.setFlag(true);
      } catch (Exception e) {
        rs.setFlag(false);
        msg = e.getMessage();
        LOG.error(e.getMessage(), e);
      }
      rs.setSuccess(true);
      rs.setDone(rs.getTotal());
      if (null != msg) {
        rs.setError(msg);
      }
      keys.remove(finalKey);
      setToRedis(rs, finalKey);
    });
    updateKeyLiveTime();
    return rs;
  }

  /**
   * 设置进度
   * @param key
   * @param done
   * @return
   */
  public static void setDone(String key,Integer done){
    RedisAsyResultData result = getResult(key);
    Optional.ofNullable(result).ifPresent(re -> {
      re.setDone(done);
      saveResult(key,result);
    });
  }

  /**
   * 设置总数
   * @param key
   * @param total
   * @return
   */
  public static void setTotal(String key,Integer total){
    RedisAsyResultData result = getResult(key);
    Optional.ofNullable(result).ifPresent(re -> {
      re.setTotal(total);
      saveResult(key,result);
    });
  }

  public static RedisAsyResultData submitTask(String key, Supplier supplier) {
    AtomicReference<RedisAsyResultData> rs = new AtomicReference<>(new RedisAsyResultData());
    rs.get().setSuccess(false);
    rs.get().setRedisKey(key);
    rs.get().setDone(0);
    rs.get().setTotal(100);
    setToRedis(rs.get(), key);
    if (!keys.contains(key)) {
      keys.add(key);
    }
    String finalKey = key;
    executor.submit(() -> {
      String msg = null;
      try {
        Object o = supplier.get();
        RedisAsyResultData result = getResult(key);
        if (null != result){
          rs.set(result);
        }
        rs.get().setData(o);
        rs.get().setFlag(true);
      } catch (Exception e) {
        rs.get().setFlag(false);
        msg = e.getMessage();
        LOG.error(e.getMessage(), e);
      }
      rs.get().setSuccess(true);

      rs.get().setDone(rs.get().getTotal());
      if (null != msg) {
        rs.get().setError(msg);
      }
      keys.remove(finalKey);
      setToRedis(rs.get(), finalKey);
    });
    updateKeyLiveTime();
    return rs.get();
  }

  private static void setToRedis(RedisAsyResultData result, String redisKey) {
    getRedisTemplate().opsForValue().set(redisKey, result, 5, TimeUnit.MINUTES);
  }

  public static RedisAsyResultData getResult(String key) {
    RedisAsyResultData excelResult =
        getRedisTemplate().opsForValue().get(key);
    if (null != excelResult) {
      return excelResult;
    }
    return null;
  }

  public static void saveResult(String key, RedisAsyResultData result) {
    setToRedis(result, key);
  }

  public static byte[] FileToByte(String filePath) throws Exception{
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    try {
      fis = new FileInputStream(filePath);
      bis = new BufferedInputStream(fis);
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      int c = bis.read();
      while (c != -1) {
        // 数据存储到ByteArrayOutputStream中
        baos.write(c);
        c = bis.read();
      }
      fis.close();
      bis.close();
      // 转换成二进制
      byte[] bytes = baos.toByteArray();
      return bytes;
    }catch (Exception e){
      e.printStackTrace();
      throw e;
    }finally {
      try {
        if (fis != null ) {
          fis.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
        throw e;
      } finally {
        try {
          if (bis != null ) {
            bis.close();
          }
        } catch (IOException e) {
          e.printStackTrace();
          throw e;
        }
      }
    }
  }
}

3.查询导出文件生成情况接口

/**
   * 根据key获取导出接口
   * @param key
   * @return
   */
  @GetMapping("getRedisResult/{key}")
  public RestMessage getRedisResult(@PathVariable String key){
    Assert.hasLength(key,"key不能为空");
    return RestBuilders.successBuilder().data(AsyncUtil.getResult(key)).build();
  }

key为导出请求返回的

四、效果

在这里插入图片描述

前端进度条由每一次轮询请求返回的total、done计算

在这里插入图片描述
       最后一次轮询,判断flag的值true,或者自行判断total与done相等,又或者判断data是否又返回url表示是否生成完成,然后用返回的url进行get请求执行下载。

总结

  • 简单的实现进度条,用在数据需要长时间一条条生成时,看进度条特别明显
  • 轮询其实也可以用websocket替代(这样可以离开页面做其他操作,当然这样也是可以的,就是轮询要做到全局请求了,业务模块多的下载的时候前后端都压力变大)
  • 这里其实还用到了easypoi的模板导出,大家可以自己看看api
          就写到这里,希望能帮到大家,uping!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/52672.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

动态sql以及常用的标签

什么是动态sql&#xff1a; 指根据不同的条件生成不同的sql 搭建环境&#xff1a; 建表&#xff1a; create table blog( id varchar(50) not null comment 博客id, title varchar(100) not null comment 博客标题, author varchar(30) not null comment 博客作者, create_ti…

第三章 ref与reactive

ref ref 变为响应式数据shallowRef 浅层响应式数据&#xff08;响应式到 .value为止&#xff09;isRef 判断是否为ref响应式数据triggerRef 强制触发依赖更新customRef 自定义ref函数 <template><div class"App">{{ stu }}<button click"chang…

国际化警告Fall back to translate ‘creator‘ key with ‘zn‘ locale.

发现是自己粗心写错了一个单词 这个需要改成zh messages里面也是zh:zh

忘记安卓图案/密码锁如何解锁?

如何解锁Android手机图案锁&#xff1f;如何删除忘记的密码&#xff1f;Android 手机锁定后如何重置&#xff1f;这是许多智能手机用户在网上提出的几个问题。为了回答这些问题&#xff0c;我们想出了一些简单有效的方法来解锁任何设备而不丢失数据。 忘记手机密码可能会令人恐…

大数据技术之ClickHouse---入门篇---介绍

星光下的赶路人star的个人主页 一棵树长到它想长到的高度之后&#xff0c;它才知道怎样的空气适合它 文章目录 1、Clickhouse入门1.1 什么是Clickhouse1.1.1 Clickhouse的特点1.1.1.1 列示储存1.1.1.2 DBMS的功能1.1.1.3 多样化引擎1.1.1.4 高吞吐写入能力1.1.1.5 数据分区与线…

机器学习:自动编码器Auto-encoder

Self-supervised Learning Framework 不用标注数据就能学习的任务&#xff0c;比如Bert之类的。但最早的方法是Auto-encoder。 Outline Auto-encoder encoder输出的向量&#xff0c;被decoder还原的图片&#xff0c;让输出的图片与输入的图片越接近越好。 将原始的高维向量变…

ChatGPT+MidJourney 3分钟生成你的动画故事

chatgpt是真的火了&#xff0c;chatgpt产生了一个划时代的意义——自chatgpt起&#xff0c;AI是真的要落地了。 chatgpt能做的事情太多了&#xff0c;多到最初开发模型的程序员自己&#xff0c;也没法说得清楚chatgpt都能做啥&#xff0c;似乎只要你能想得到&#xff0c;它都有…

自动callback

using UnityEngine;public class AsyncCallbackScript : MonoBehaviour {public delegate void fun(string msg);void Start(){fun test AAA;//test.BeginInvoke("天王盖地虎", asyncCallback > BBB(), null);test.BeginInvoke("天王盖地虎 宝塔镇河妖"…

测试用例实战

测试用例实战 三角形判断 三角形测试用例设计 测试用例编写 先做正向数据&#xff0c;再做反向数据。 只要有一条边长为0&#xff0c;那就是不符合要求&#xff0c;不需要再进行判断&#xff0c;重复。 四边形 四边形测试用例

Linux6.2 ansible 自动化运维工具(机器管理工具)

文章目录 计算机系统5G云计算第一章 LINUX ansible 自动化运维工具&#xff08;机器管理工具&#xff09;一、概述二、ansible 环境安装部署三、ansible 命令行模块1.command 模块2.shell 模块3.cron 模块4.user 模块5.group 模块6.copy 模块7.file 模块8.hostname 模块9.ping …

day49-Todo List(待办事项列表)

50 天学习 50 个项目 - HTMLCSS and JavaScript day49-Todo List&#xff08;待办事项列表&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" co…

word xls有用小技巧

不少office、代码编辑等软件&#xff0c;很简单高效小技巧。Word xlsx 某一行或列不动&#xff1a; 视图》冻结窗格》冻结首行 eclispe 全局搜索 CtrlH 制定变量、名称搜索 鼠标左键点中CtrlAltG

el-table 表格头部合并

<el-table v-loading"listLoading" :key"tableKey" :data"list" stripe border fit highlight-current-rowstyle"width: 100%;" size"mini"><el-table-column label"第一行" align"center">…

使用DataX实现mysql与hive数据互相导入导出

一、概论 1.1 什么是DataX DataX 是阿里巴巴开源的一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库(MySQL、Oracle 等)、HDFS、Hive、ODPS、HBase、FTP 等各种异构数据源之间稳定高效的数据同步功能。 1.2 DataX 的设计 为了解决异构数据源同步问题&#xf…

HTML5前端开发工程师的岗位职责说明(合集)

HTML5前端开发工程师的岗位职责说明1 职责 1、根据产品设计文档和视觉文件&#xff0c;利用HTML5相关技术开发移动平台的web前端页面; 2、基于HTML5.0标准进行页面制作&#xff0c;编写可复用的用户界面组件; 3、持续的优化前端体验和页面响应速度&#xff0c;并保证兼容性和…

C# NDArray System.IO.FileLoadException报错原因分析

C# NDArray System.IO.FileLoadException 报错原因分析&#xff1a; 1.NuGet程序包版本有冲突 2.统一项目版本 1.打开解决方案NuGet程序包设置 2.查看是否有版本冲突 3.统一版本冲突

【数据结构与算法】基数排序

基数排序 基数排序&#xff08;Radix Sort&#xff09;属于“分配式排序”&#xff0c;又称“桶子法”或 bin sort&#xff0c;顾名思义&#xff0c;它是通过键值的各个位的值&#xff0c;将要排序的元素分配至某些“桶”中&#xff0c;达到排序的作用。基数排序法是属于稳定性…

【UE5】快速认识入门

目录 &#x1f31f;1. 快速安装&#x1f31f;2. 简单快捷键操作&#x1f31f;3. 切换默认的打开场景&#x1f31f;4. 虚幻引擎术语 &#x1f31f;1. 快速安装 进入Unreal Engine 5官网进行下载即可&#xff1a;UE5 &#x1f4dd;官方帮助文档 打开后在启动器里创建5.2.1引擎…

Java并发系列之一:JVM线程模型

什么是线程模型&#xff1a; Java字节码运行在JVM中&#xff0c;JVM运行在各个操作系统上。所以当JVM想要进行线程创建回收这种操作时&#xff0c;势必需要调用操作系统的相关接口。也就是说&#xff0c;JVM线程与操作系统线程之间存在着某种映射关系&#xff0c;这两种不同维…

Qt信号与槽机制的本质

引入 对象与对象之间的通信有多个方式&#xff0c;如果我们要提供一种对象之间的通信机制。这种机制&#xff0c;要能够给两个不同对象中的函数建立映射关系&#xff0c;前者被调用时后者也能被自动调用。 再深入一些&#xff0c;两个对象如果都互相不知道对方的存在&#xff…