使用@FunctionalInterface进行异步导出Excel数据
项目场景:
项目中需要使用Excel导出大量数据,首先要进行大量的查询,但是由于数据量太大,导出的时候会让前端在pending,所以需要使用异步的方式先让前端返回。
解决方案:
使用@FunctionalInterface方式来进行异步导出。
首先,我们来确定函数式接口在哪里
@FunctionalInterface
public interface ExportDataQueryInterface {
List<?> queryExportData();
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SheetInfoBean2 {
/**
* sheet页名称
*/
private String sheetName;
/**
* sheet标题bean
*/
private Class<?> headClass;
/**
* sheet页数据
*/
private ExportDataQueryInterface dataList;
}
如代码所示,函数式接口隐藏在SheetInfoBean中,我们在通过异步方法调用Bean时,函数式接口的作用就是执行耗时较长的查询。也就是说,把不同的查询方法作为参数包裹进异步方法中。从而达到查询,导出异步的结果。
这里则是实际执行导出的方法
@Override
public void exportToExcelFileAsync(Map<String,Object> context, TaskModule exportType,List<SheetInfoBean2> sheetInfoBeans) {
String fileName = exportType.getDescription()+"-"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+".xlsx";
TaskDto taskDto = new TaskDto();
taskDto.setFileName(fileName);
taskDto.setType(TaskTypeEnum.EXPORT);
taskDto.setTaskState(TaskStateEnum.EXECUTE);
taskDto.setTaskModule(exportType);
Long taskId = taskClient.add(taskDto);
taskDto.setId(taskId);
SpringUtils.getBean(ExcelExportService.class).exportToExcelFileAsync(taskDto,context,sheetInfoBeans);
}
异步执行,lambda在这里开始真正执行。因为这里调用了SheetInfobeans
@Async
@Override
public void exportToExcelFileAsync(TaskDto taskDto, Map<String, Object> context, List<SheetInfoBean2> sheetInfoBeans){
ThreadContext.set(context);
String filePath = System.getProperty("java.io.tmpdir")+taskDto.getFileName();
File file = new File(filePath);
FileInputStream fileInputStream = null;
try{
//创建excel文件
ExcelWriter excelWriter = EasyExcel.write(filePath).build();
WriteSheet writeSheet;
for (SheetInfoBean2 bean : sheetInfoBeans) {
List<?> list = bean.getDataList().queryExportData();
// 构建sheet对象
writeSheet = EasyExcel.writerSheet(bean.getSheetName()).head(bean.getHeadClass()).build();
// 写出sheet数据
excelWriter.write(list, writeSheet);
}
excelWriter.close();
fileInputStream = new FileInputStream(filePath);
//更新状态
UploadResp upload = fileClient.upload(new MockMultipartFile(file.getName(),file.getName(), ContentType.APPLICATION_OCTET_STREAM.toString(),fileInputStream), null, BizType.EXPORT.getName());
Long fileId = upload.getFileId();
taskDto.setFileId(fileId);
taskDto.setSize(file.length());
taskDto.setTaskState(TaskStateEnum.FINISHED);
taskClient.update(taskDto);
}catch (Exception e){
log.error(e.getMessage(),e);
}finally {
if(file.exists()){
file.deleteOnExit();
}
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
log.error(e.getMessage(),e);
}
}
ThreadContext.clear();
}
}
Lambda 表达式的执行:
当 bean.getDataList().queryExportData() 被调用时,才会触发 Supplier<List<?>> 中的 Lambda 表达式执行。也就是说,Lambda 表达式是在这里开始执行的。
使用示例:
excelExportService.exportToExcelFileAsync(
ThreadContext.get(),
TaskModule.COUPON_RECORD,
List.of(new SheetInfoBean2("优惠券记录", CouponRecordExcelDto.class, (() -> {
return SpringUtils.getBean(this.getClass()).query(params, pageRequest)
.getContent()
.stream().map(
genericDto -> {
Map<String, Object> flattenedFields = genericDto.getFlattenedFields();
return BeanUtil.copyProperties(flattenedFields, CouponRecord.class);
}).collect(Collectors.toList());
}))));
}