项目场景:
项目中需要使用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时,函数式接口的作用就是执行耗时较长的查询。也就是说,把不同的查询方法作为参数包裹进异步方法中。从而达到查询,导出异步的结果。
这里则是实际执行导出的方法
@Overridepublic 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@Overridepublic 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());}))));}