异步导入

在网上找了很多资料,导入五花八门。由于我参与到导入功能是从架构层面上做优化,解决大数据量,并发,耗时等性能问题。

我先出了方案文档如下

导出统一用异步实现提高用户体验,导出分页标准根据各自的也无需求定(全量导出不仅性能低,数据量特别大的情况下还会导致内存溢出)。

异步导出页面设计如下:

导出时间,表格名称,导出状态(导出中,导出完成,导出异常),导出进度条,操作(下载)

异步导出针对文件加密处理

点击导出,将文件上传到OSS,文件名加密规则为MD5(用户ID+创建时间)

点击下载进入统一下载接口,通过用户认证鉴权查询出用户ID,在通过下载ID查询出文件创建时间,读出文件流响应给前端。

针对导出进行加密处理,是为了防止获取到文件URL随意进行下载。在安全性,保密性上面没有保障。

减少内存设计

不管是否大数据量,控制内存最多不超过10000条数据,集合存储获取到响应一万条数据,写入excel,清空list,在进行查询,在清空依次类推……最后上传至OSS。

定时任务

定时扫描用户导出表,超过一天的数据状态置为无效。

表设计如下

CREATETABLE`user_export`(

`id`bigint(20)unsignedNOTNULLAUTO_INCREMENT,

`module_name`varchar(50)DEFAULT''COMMENT'模块名称',

`tab_name`varchar(50)DEFAULT''COMMENT'表格名称',

`export_status`int(1)DEFAULT'0'COMMENT'0导出中1导出完成2导出异常',

`content`varchar(1000)DEFAULT''COMMENT'异常描述信息',

`create_time`bigint(13)DEFAULTNULLCOMMENT'创建时间',

`create_by`bigint(20)DEFAULTNULLCOMMENT'创建人',

`update_time`bigint(15)DEFAULTNULLCOMMENT'最后修改时间',

`update_by`bigint(20)DEFAULTNULLCOMMENT'最后修改人',

`status`int(1)DEFAULT'0'COMMENT'0有效1无效',

PRIMARYKEY(`id`)

)ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='用户导出表';

针对大数据量内存溢出问题,改变了原有的导出模式,之前采用模板的形式进行导出。

现在用了Poi导出,通过写入excel清空List解决了大数据内存溢出问题,还提高了导出效率经过测试五万多条数据三十秒左右导出完成。占用内存大小取决于配置项目export.size,size的大小就是每次写入excel条数的大小也是list取值的大小。

针对上面每次写入excel会生成多个文件,采用了先将数据存入excel,在存入本地磁盘。最后统一上传到os最后导出来是一个excel文件。

针对并发问题,采用了多线程ScheduledExecutorService类,类似于timer一样,现在配置的是五个线程,超过排队,线程执行间隔时间配置为一秒。

关键代码如下

创建book对象

SXSSFWorkbookwbk=newSXSSFWorkbook(size);

String[]assetHeadTemp为表头

String[]assetNameTemp表头和数据库映射数组

JsonConfigconfig=newJsonConfig();

config.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);

JSONArrayjsonArray=JSONArray.fromObject(list,config);

sheet生成表头

Sheetsh=asyncService.createSheetHeader(wbk,assetHeadTemp);

//读取模板对象

ByteArrayOutputStreambyteOut=null;

for(inti=1;i<=num;i++){

//asyncVo该对象为异步实体对象jsonArray将响应结果集转成json数组方便后续赋值size内存保存条数的大小i分页查询第几页,i==num判断是否为最后一页wbkbook对象shexcelassetNameTemp数据库表头映射byteOut每次分页输出流(因为是异步多线程放入这里可以避多线程对象共享问题)asyncService.asyncExport(asyncVo,jsonArray,size,i,i==num,wbk,sh,assetNameTemp,byteOut);

if(num>1&&i<num){

userLedgerDto.setPageIndex(i+1);

log.info("AsyncServiceImplAsyncExport查询开始num={}time={}",i,DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));

list=getUserAllocation(userLedgerDto).getRows();

jsonArray=JSONArray.fromObject(list,config);

log.info("AsyncServiceImplAsyncExport查询结束num={}time={}",i,DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));

}

}

publicvoidasyncExport(AsyncVoasyncVo,JSONArraylist,intpageSize,intpageIndex,booleanisLastRow,SXSSFWorkbookwbk,Sheetsh,String[]assetNameTemp,ByteArrayOutputStreambyteOut)throwsUnsupportedEncodingException{

log.info("startAsyncServiceImplAsyncExport");

//创建jxsl对象

StringcustomerId=asyncVo.getCustomerId();

StringoutName=URLEncoder.encode(MD5.encrypt(customerId+asyncVo.getCreateTime()),"utf-8")+".xlsx";

log.info("AsyncServiceImplAsyncExport开始创建对象写入模板-{}",DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));

try{

if(list.size()>0){

for(inti=0;i<list.size();i++){

Rowrow_value=sh.createRow((pageIndex-1)*pageSize+i+1);

//遍历jsonarray数组,把每一个对象转成json对象

JSONObjectjob=list.getJSONObject(i);

//得到每个对象中的属性值

for(intk=0;k<assetNameTemp.length;k++){

CellcellValue=row_value.createCell(k);

cellValue.setCellValue(job.get(assetNameTemp[k])!=null?job.get(assetNameTemp[k]).toString():"");

}

}

}

list.clear();//每次存储len行,用完了将内容清空,以便内存可重复利用

//上传至OSS

if(isLastRow){

byteOut=newByteArrayOutputStream();

wbk.write(byteOut);

log.info("AsyncServiceImplAsyncExport写入完成,开始上传至OSS-{}",DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));

byte[]buff=byteOut.toByteArray();

InputStreaminput=newByteArrayInputStream(buff);

aliyunOSSUtil.putObject(classificationXLSX,folderSystem,outName,input,buff.length);

log.info("AsyncServiceImplAsyncExport上传完成,方法结束-{}",DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));

//在数据库中写入导出人导出状态:导出成功

asyncVo.setExportStatus("1");

inti=asyncDao.updateUserExport(asyncVo);

if(0==i){

log.info("AsyncServiceImplAsyncExport导出成功,状态写入-{}","失败");

}

//写入完毕才能释放流

try{

byteOut.flush();

byteOut.close();

wbk.close();

}catch(Exceptione){

log.info("AsyncServiceImpl对象关闭失败",e);

}

}

}catch(Exceptione){

//在数据库中写入导出人导出文件名导出时间导出状态:导出异常

asyncVo.setContent(e.toString());

asyncVo.setExportStatus("2");

asyncDao.updateUserExport(asyncVo);

log.info("AsyncServiceImplAsyncExporthaserror",e);

}

}

该方法是阿里云api自带可供参考

/**

*上传文件到OSS(不加密存储)

*

*@paramclassificationEnum分类

*@paramfolderEnum文件夹

*@paramfileName文件名称

*@paramfileLength文件长度

*@paraminputStream文件流

*@return文件名

*@throwsIOException

*/

publicStringputObject(ClassificationEnumclassificationEnum,FolderEnumfolderEnum,StringfileName,InputStreaminputStream,longfileLength)throwsIOException{

createFolder(classificationEnum,folderEnum);

//创建上传Object的Metadata

ObjectMetadataobjectMetadata=newObjectMetadata();

//必须设置

objectMetadata.setContentLength(fileLength);

objectMetadata.setCacheControl("no-cache");

objectMetadata.setHeader("Pragma","no-cache");

StringfileNameExtension=fileName.substring(fileName.lastIndexOf("."));

StringcontentType=getContentType(fileNameExtension);

objectMetadata.setContentType(contentType);

objectMetadata.setContentDisposition("inline;filename="+fileName);

//上传Object

ossClient.putObject(bucket,StringUtils.join(classificationEnum.getPath(),folderEnum.getPath(),fileName),inputStream,objectMetadata);

inputStream.close();

returnfileName;

}

相关推荐