项目需求

​ 导出生成大批量数据的文件,一个Excel中最多存有五十万条数据,查询多余五十万的数据写多个Excel中。导出完成是生成的多个Excel文件打包压缩成zip,而后更新导出记录中的压缩文件路径。

​ 大数据量文件一般采用异步生成文件,导出时首先授权生成一个流水号,而后将数据携带流水号请求导出接口。

抛开实际业务,做成一个比较公共的导出功能。

参数说明

{
"className": "ValideData", //导出的数据的实体类,类中有别名和顺序相关的注解
"createUser": "", //操作人
"downLoadNo": "202203181504732568468066304", //下载流水号
"fileName": "机卡绑定", //文件名 fileName+HHmmssSSS.xlsx
"keys": [ //redis key的数据,分批获取数据
],
"remark": "机卡绑定", //备注(不关注)
"type": "机卡绑定" //导出类型(不关注)
}

坐标

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.21</version>
</dependency>

注:抛开导出前的参数校验,只关注导出操作 。

主要代码

逻辑说明:

  1. 导出前将请求参数更新到导出记录中。
  2. 类加载器加载需要导出数据的实体类
  3. 设置一个数据量指针,记录到每个文件的数据量
  4. 达到阈值时指定文件写出到磁盘并清缓。
  5. 重置数据量指针,新增一条文件记录(循环)
  6. 数据量指针未到阈值时但数据已经查询完成---->>写入剩余数据
  7. 查询该流水号的所有文件记录
  8. 压缩文件并返回压缩文件地址
  9. 更新到导出记录中

主流程

public void bigDataExport(PortDto dto) throws Exception {
long start = System.currentTimeMillis();
log.info("开始导出,批次号:<{}>, 开始时间:{}", dto.getDownLoadNo(), DateUtil.now()); //修改导出记录
LambdaUpdateWrapper<PortDto> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(PortDto::getDownLoadNo, dto.getDownLoadNo());
//生成导出记录
int row = this.baseMapper.update(dto, updateWrapper);
if (row > 0) {
log.info("批次号:<{}>准备生成文件", dto.getDownLoadNo());
try {
Iterator<String> iterator = keys.iterator();
Workbook workbook = null;
ExportParams params = new ExportParams(); //加载导出数据实体类
Class<?> aClass = Class.forName(entityBasePackage + dto.getClassName()); int element = 0;
while (iterator.hasNext()) {
String key = iterator.next();
Collection<?> list = getList(key, aClass);
element += list.size();
workbook = ExcelExportUtil.exportBigExcel(params, aClass, list); //文件数据达到阈值
if (element >= maxDataCount) {
String fileName = dto.getFileName() + "_" + DateUtil.format(new Date(),
"HHmmssSSS") + ".xlsx";
ExcelExportUtil.closeExportBigExcel();
FileOutputStream fos =
new FileOutputStream(fileProp.getPath().getPath() + fileName);
workbook.write(fos);
fos.close();
element = 0;
//更新地址
Map<String, Object> map = new HashMap<>();
map.put("downloadNo", dto.getDownLoadNo());
map.put("filePath", fileProp.getPath().getPath() + fileName);
map.put("createTime", new Date());
this.baseMapper.insertPathRecord(map);
log.info("文件写入完成,文件名:{}", fileName);
continue;
}
iterator.remove();
} //写入剩余文件
if (element != 0) {
String fileName = dto.getFileName() + "_" + DateUtil.format(new Date(),
"HHmmssSSS") + ".xlsx";
ExcelExportUtil.closeExportBigExcel();
FileOutputStream fos = new FileOutputStream(fileProp.getPath().getPath() + fileName);
workbook.write(fos);
fos.close();
element = 0;
//更新地址
Map<String, Object> map = new HashMap<>();
map.put("downloadNo", dto.getDownLoadNo());
map.put("filePath", fileProp.getPath().getPath() + fileName);
map.put("createTime", new Date());
this.baseMapper.insertPathRecord(map);
log.info("文件写入完成,文件名:{}", fileName);
} long end = System.currentTimeMillis();
log.info("导出结束,批次号:<{}>, 结束时间:{}, 耗时:{}", dto.getDownLoadNo(), DateTime.of(end),
DateUtil.formatBetween(end - start));
} catch (Exception e) {
log.info("批次号<{}>导出异常:", dto.getDownLoadNo(), e);
throw new BusinessException("");
} finally {
log.info("批次号<{}>生成文件结束,准备压缩文件,修改状态", dto.getDownLoadNo());
//合并文件到导出文件记录主表
//当只有一个文件记录时直接更新主表文件地址
List<PortDto> recordList = exportDao.getPathRecord(dto);
if (recordList.size() > 1) {
//zipPath
dto.setFilePath(zcat(dto, recordList));
} else {
//xlsxPath
dto.setFilePath(recordList.size()==0? "":recordList.get(0).getFilePath());
}
updateWrapper.clear();
updateWrapper.set(PortDto::getFilePath, dto.getFilePath());
updateWrapper.set(PortDto::getSuccessTime, new Date());
updateWrapper.set(PortDto::getStatus, "1");
updateWrapper.eq(PortDto::getDownLoadNo, dto.getDownLoadNo());
this.baseMapper.update(null, updateWrapper);
log.info("批次号<{}>更新下载记录表文件地址,修改状态成功", dto.getDownLoadNo());
}
}
}

文件压缩

/**
* 多文件压缩
* @param dto 导出信息
* @Param recordList 文件路径
* @return void
* @throws
* @author Surpass
* @date 2022/3/17 9:59
*/
private String zcat(PortDto dto, List<PortDto> recordList) throws Exception {
String fileName = dto.getFileName() + "_" + DateUtil.format(new Date(), "HHmmssSSS") + ".zip";
String zipPath = fileProp.getPath().getPath() + fileName;
Archiver archiver = CompressUtil.createArchiver(
CharsetUtil.CHARSET_UTF_8,
ArchiveStreamFactory.ZIP,
new File(zipPath)
);
for (PortDto portDto : recordList) {
archiver.add(FileUtil.file(portDto.getFilePath()));
}
archiver.finish();
archiver.close();
return zipPath;
}

查询数据

/**
* 查询redis数据
* @param key
* @param cls
* @return java.util.Collection<?>
* @throws
* @author Surpass
* @date 2022/3/18 15:51
*/
private Collection<?> getList(String key, Class<?> cls) {
List<String> list = redis.getList(key);
return list.stream().map(item -> JSONObject.parseObject(item, cls)).collect(Collectors.toList());
}

补充

导出还设置了队列计数器来限制同一时间最大的导出请求,使用aop在申请流水号时计数器+1,导出完成或者异常时队列计数器-1。导出完成后根据操作人发送邮件通知导出结果。

Export大数据量导出和打包的更多相关文章

  1. poi 操作Excel 以及大数据量导出

    maven 依赖 (版本必须一致,否则使用SXSSFworkbook 时程序会报错) <dependency> <groupId>org.apache.poi</grou ...

  2. SQL Server 使用bcp进行大数据量导出导入

    转载:http://www.cnblogs.com/gaizai/archive/2010/04/17/1714389.html SQL Server的导出导入方式有: 在SQL Server中提供了 ...

  3. 使用内存映射文件MMF实现大数据量导出时的内存优化

    前言 导出功能几乎是所有应用系统必不可少功能,今天我们来谈一谈,如何使用内存映射文件MMF进行内存优化,本文重点介绍使用方法,相关原理可以参考文末的连接 实现 我们以单次导出一个excel举例(csv ...

  4. EasyPoi大数据导入导出百万级实例

    EasyPoi介绍: 利用注解的方式简化了Excel.Word.PDF等格式的导入导出,而且是百万级数据的导入导出.EasyPoi官方网址:EasyPoi教程_V1.0 (mydoc.io).下面我写 ...

  5. java 导出Excel 大数据量,自己经验总结!

    出处: http://lyjilu.iteye.com/ 分析导出实现代码,XLSX支持: /** * 生成<span style="white-space: normal; back ...

  6. NPOI大数据量多个sheet导出源码(原)

    代码如下: #region NPOI大数据量多个sheet导出 /// <summary> /// 大数据量多个sheet导出 /// </summary> /// <t ...

  7. java excel大数据量导入导出与优化

    package com.hundsun.ta.utils; import java.io.File; import java.io.FileOutputStream; import java.io.I ...

  8. POI3.8解决导出大数据量excel文件时内存溢出的问题

    POI3.8的SXSSF包是XSSF的一个扩展版本,支持流处理,在生成大数据量的电子表格且堆空间有限时使用.SXSSF通过限制内存中可访问的记录行数来实现其低内存利用,当达到限定值时,新一行数据的加入 ...

  9. elasticsearch5.0集群大数据量迁移方法及注意事项

    当es集群的数据量较小的情况下elasticdump这个工具比较方便,但是当数据量达到一定级别比如上百G的时候,elasticdump速度就很慢了,此时我们可以使用快照的方法进行备份 elasticd ...

随机推荐

  1. iOS 获取通讯录中联系人的所有属性 by - zfqj

    1 ABAddressBookRef addressBook = ABAddressBookCreate(); 2 3 CFArrayRef results = ABAddressBookCopyAr ...

  2. 部署YUM仓库 (最近睡眠质量很差,你什么时候搬过来住)

    部署YUM仓库 1.YUM概述 YUM(Yellow dog Updater Modified) 基于RPM包构建的软件更新机制 可以自动解决依赖关系 所有软件由集中的YUM软件仓库提供 2.主备安装 ...

  3. 北京太速科技-第六代Intel i7四核八线程6U VPX主控板

    一.产品概述 该产品是一款基于第六代Intel i7四核八线程的高性能6U VPX刀片式计算机.产品提供了可支持全网状交换的高速数据通道,其中P1,P2各支持4个PCIe x4 Gen3总线接口,P3 ...

  4. v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...

  5. suse 12 二进制部署 Kubernetets 1.19.7 - 第06章 - 部署kube-apiserver组件

    文章目录 1.6.部署kube-apiserver 1.6.0.创建kubernetes证书和私钥 1.6.1.生成kubernetes证书和私钥 1.6.2.创建metrics-server证书和私 ...

  6. RadonDB MySQL on K8s 2.1.2 发布!

    RadonDB MySQL on Kubernetes 于 2 月 17 日发布了新版本 2.1.2 .该版本在节点的重建.增删等方面进行了全面升级. 致谢: 首先感谢 @andyli029 @ace ...

  7. [题解]UVA11027 Palindromic Permutation

    链接:http://vjudge.net/problem/viewProblem.action?id=19602 描述:给出一个字符串,求重新排列后第n个回文串,若没有则输出"XXX&quo ...

  8. 好久没写作业了,因为组里分配了任务,学习了Resnet和DenseNet,把概要po上来和大家分享。

    Res: 学长说,不要看别人的博客.看多了就看傻了!俗话说,不听老人言,吃亏在眼前. 第一篇论文来咯!Deep Residual Learning for Image Recognition!国人写的 ...

  9. Go1.14版本vendor和gomodule冲突问题

    Go1.14版本vendor和gomodule冲突问题 go1.14版本使用go mod tidy构建依赖时会出现问题(见链接), 这个问题在go1.12版本是不会出现的. https://githu ...

  10. SaaS平台是什么,为什么字节、腾讯等大厂都在抢相关人才

    SaaS平台很多人可能没听说是什么,但是从事TO  B公司的员工来说,SaaS平台应该都有所耳闻.从2016年开始,腾讯开始发力TO B算起,到处在挖TO B公司的骨干人才,而熟悉SaaS平台的人才竞 ...