Vue进度条实现+后段多线程插入

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


实现思路

最近公司项目刚好有一个大批量Excel数据导入到数据库的需求,使用框架自带的文件导入可以实现,但是用户体验不佳,想着优化一下,以下做个记录,先看效果
在这里插入图片描述

需求:前端选中Excel文档,后台实现多线程批量插入数据库,前端同时展示实时进度条

和同为打工仔的好兄弟们讨论了实现思路如下:
1、前端-将Excel数据和一个时间戳唯一key传入后台的导入接口;
2、后台-解析数据,此时间戳唯一key作为此次导入数据的插入行数的redis唯一key,用于获取上传进度;
3、后台-使用框架自带的@Async注解写一个Task任务类,实现多线程批量插入(此处由于是框架自带的,我并未深入研究),插入执行完后以前端传过来的时间戳为key,插入行数+=为值,创建redis变量,并设置30秒自动销毁;
3、前端-导入请求发出时,同时创建一个定时器 轮巡发送获取导入行数请求,后台以时间戳为key查询redis中存放的影响行数返回给前端;
4、前端-拿到插入行数后计算导入进度并渲染到进度条中
5、前端-导入成功-关闭进度条

具体实现如下

一、后端实现

思路:后台-解析数据,此时间戳唯一key作为此次导入数据的插入行数的redis唯一key,用于获取上传进度;
根据思路我们直接上代码,此处就只展示关键代码,其他的解析数据那些根据自己实际情况写就好

1.多线程导入

········获取导入的Excel集合和redis唯一key······

········数据查重 等等等······

以下代码含义是:
将导入的Excel集合根据Count条目数,截取然后使用多线程分批处理

			//单次处理条目数
            int count=500;
            for (int i = 0; i < (Math.round((dataCellCapacityDtoList.size() / count)+0.5)); i++) {
                int startLen = i * count;
                int endLen = ((i + 1) * count > dataCellCapacityDtoList.size() ? dataCellCapacityDtoList.size() : (i + 1) * count);
                //截取指定条目数
                List<DataCellCapacityDto> newList = dataCellCapacityDtoList.subList(startLen, endLen);
                //多线程执行插入
                Future<Integer> integerFuture = addCellFactoryDataTask.insertBatchCellFactoryData(newList);
                Integer row = integerFuture.get();
                importRow = importRow + row;
                if(row==newList.size()){
//                    System.out.println("---------------------------插入成功,影响行数:"+row+"--------------------");
                    //更新redis变量
                    redisUtils.set(importCellDataParam.getKey(),importRow);
                    //设定 30秒 过期时间
                    redisUtils.expire(importCellDataParam.getKey(),30);
                }
            }

线程任务类代码:

@Component
public class AddCellFactoryDataTask {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    //电芯出厂数据
    @Autowired
    private DataCellCapacityMapper dataCellCapacityMapper;


    @Async
    public void doTask2(int i) throws InterruptedException{
        logger.info("Task2-Native"+i+" started.");
    }

    @Async
    public Future<Integer> insertBatchCellFactoryData(List<DataCellCapacityDto> dataCellCapacityDtoList) throws InterruptedException{
//        long startTime = System.currentTimeMillis();
        //获得当前线程的名称
//        System.out.println("---------------------线程名称:"+Thread.currentThread().getName()+"-----------------");
        Integer row = dataCellCapacityMapper.importCellCapacityBatch(dataCellCapacityDtoList);
//        long insetEndTime = System.currentTimeMillis();
//        System.out.println("-------------------插入执行耗时:"+(insetEndTime-startTime)+"/ms----------------------");
        return new AsyncResult(row);
    }
}

线程是用的项目自带的,别问怎么实现,因为我也没深究,网上基本上都有教程,能跑起来就是好样的。

2.获取导入进度接口

这个就很简单啦,就是把前端的时间戳key传个后台然后依次为redis唯一key查影响行数

@Override
    public int getImportCount(String  key) {
        Object importRow = redisUtils.get(key);
        //此处为了解决唯一key还未生成时的空指针异常
        if(ObjectUtil.isNull(importRow)){
            return 0;
        }
        return (Integer)importRow;
    }

二、前端

由于导入组件是封装在crud组件的,所以前端的遇到的难点就是父页面中如何控制子组件的显示隐藏,以及父子组件的传值,可以参阅以下代码
官方给的Excel导入中有一个on-progress事件也可以实现文件上传时触发事件。

		  that.showProgress=!that.showProgress;
          //获取当前时间戳作为redis唯一key
          let redisKey = parseInt(new Date().getTime() / 1000) + '';
          //组装上传实体类
          let importDatas = {
            key: redisKey,
            excelDatas: excelDatas
          };
          //上传
          crudDataCellCapacity.importCellCapacityBatch(importDatas).then(response => {
            that.$notify.success({
              title: '成功',
              message: '导入成功1!'
            });
            console.log("导入成功")
            that.crud.loading = false;
            //刷新
            that.crud.toQuery();
          }).catch((e) => {
            that.crud.loading = false;
            that.$notify.error({
              title: '错误',
              message: '导入失败系统发生错误!'
            });
          })

1.父组件传值给子组件

由于导入组件是封装好引入使用的,所以需要在父页面中传值给子组件控制进度条的显示/隐藏
思路就是:在子组件的props中定义好属性,然后就可以通过在父页面引入时传值到子组件中,从而控制进度条隐藏/显示;

子组件

<!--进度条-->
<el-progress :hidden=showProgress :text-inside="true" :stroke-width="24" :percentage=progressValue status="success"></el-progress>

-----省略一万行代码------

/*进度条值*/
    progressValue:{
      type:Number,
      default:0
    },
    showProgress: {
      type: Boolean,
      default: true
    },

父页面

 <crudOperation
    :permission="permission"
    :progressValue="progressValue"
    :showProgress="showProgress"/>
----省略一万行代码-----
 	  //进度条值
      progressValue:0,
      //进度条是否显示 http://t.csdn.cn/cdMZa
      //这里的思路是通过!取反来控制进度条是否显示
      showProgress:true,

上传Excel(用Ajax也是一样的,前端我不是很懂 这个是封装好的,不过基本大差不差)

		 //上传
          crudDataCellCapacity.importCellCapacityBatch(importDatas).then(response => {
            that.$notify.success({
              title: '成功',
              message: '导入成功1!'
            });
            console.log("导入成功")
            that.crud.loading = false;
            //刷新
            that.crud.toQuery();
          }).catch((e) => {
            that.crud.loading = false;
            that.$notify.error({
              title: '错误',
              message: '导入失败系统发生错误!'
            });
          })

实现思路参阅:
父组件传值给子组件

2.定时器获取上传进度

获取上传进度
下面的that就是this对象,因为我是封装好的回调函数,所以导致我函数里this指向发生了改变,因为不知道怎么解决,所以我在最外层把this赋值给了that而已

		//启动定时器 间隔200毫秒获取上传进度
          let timer = setInterval(function () {
            let i = 0;
            crudDataCellCapacity.getImportCount(redisKey).then(importRow => {
              console.log("上传进度=" + importRow + "/" + excelDatas.length + "=" + (importRow / excelDatas.length))
              //保留两位小数
              let val = importRow / excelDatas.length * 100;
              // let realVal = parseFloat("48.99999").toFixed(2);
              let realVal = Math.round(val);
              //将上传进度渲染到进度条中
              that.progressValue = realVal;
              if (importRow == excelDatas.length) {
                //如果导入行数=Excel行数 就销毁定时器
                console.log("定时器被销毁");
                clearInterval(timer);
                console.log("关闭进度条");
                that.showProgress=!that.showProgress;
                location.reload()
                return;
              }
            })
          }, 200);

总结

此文主要是用于记录自己开发中所遇到的问题和解决办法,有用可以参阅,无用勿喷,感谢

(附:感谢打工仔龙工以及打工仔王工的技术、思路支持)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值