项目需求
导出生成大批量数据的文件,一个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>
注:抛开导出前的参数校验,只关注导出操作 。
主要代码
逻辑说明:
- 导出前将请求参数更新到导出记录中。
- 类加载器加载需要导出数据的实体类
- 设置一个数据量指针,记录到每个文件的数据量
- 达到阈值时指定文件写出到磁盘并清缓。
- 重置数据量指针,新增一条文件记录(循环)
- 数据量指针未到阈值时但数据已经查询完成---->>写入剩余数据
- 查询该流水号的所有文件记录
- 压缩文件并返回压缩文件地址
- 更新到导出记录中
主流程
publicvoidbigDataExport(PortDto dto)throws Exception {long start = System.currentTimeMillis();
log.info("开始导出,批次号:<{}>, 开始时间:{}", dto.getDownLoadNo(), DateUtil.now());//修改导出记录
LambdaUpdateWrapper<PortDto> updateWrapper =newLambdaUpdateWrapper<>();
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 =newExportParams();//加载导出数据实体类
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(newDate(),"HHmmssSSS")+".xlsx";
ExcelExportUtil.closeExportBigExcel();
FileOutputStream fos =newFileOutputStream(fileProp.getPath().getPath()+ fileName);
workbook.write(fos);
fos.close();
element =0;//更新地址
Map<String, Object> map =newHashMap<>();
map.put("downloadNo", dto.getDownLoadNo());
map.put("filePath", fileProp.getPath().getPath()+ fileName);
map.put("createTime",newDate());this.baseMapper.insertPathRecord(map);
log.info("文件写入完成,文件名:{}", fileName);continue;}
iterator.remove();}//写入剩余文件if(element !=0){
String fileName = dto.getFileName()+"_"+ DateUtil.format(newDate(),"HHmmssSSS")+".xlsx";
ExcelExportUtil.closeExportBigExcel();
FileOutputStream fos =newFileOutputStream(fileProp.getPath().getPath()+ fileName);
workbook.write(fos);
fos.close();
element =0;//更新地址
Map<String, Object> map =newHashMap<>();
map.put("downloadNo", dto.getDownLoadNo());
map.put("filePath", fileProp.getPath().getPath()+ fileName);
map.put("createTime",newDate());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);thrownewBusinessException("");}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,newDate());
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(newDate(),"HHmmssSSS")+".zip";
String zipPath = fileProp.getPath().getPath()+ fileName;
Archiver archiver = CompressUtil.createArchiver(
CharsetUtil.CHARSET_UTF_8,
ArchiveStreamFactory.ZIP,newFile(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。导出完成后根据操作人发送邮件通知导出结果。
版权归原作者 听风听雨听世界 所有, 如有侵权,请联系我们删除。