- 主线程:处理请求响应,同时开启子线程,让子线程处理导出任务
- 子线程:将导出的文件写入到磁盘临时文件,临时文件上传到oss中获取上传文件的url路径,记录url路径到数据库中,最后再删除临时文件
- 通过单独一个页面查询导出文件流水的列表,进行下载文件
代码实现
线程池配置
@Component
public class ExcelExportThreadPoolConfig {
private static final int CPU_SIZE = Runtime.getRuntime().availableProcessors();
@Bean(value = "exportThreadPoolExecutor")
public ThreadPoolExecutor poolExecutorSetting() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CPU_SIZE * 2,
CPU_SIZE * 2,
3,
TimeUnit.MINUTES,
new LinkedBlockingQueue<>(20),
new CustomizableThreadFactory("文件导出线程池")
);
threadPoolExecutor.prestartAllCoreThreads();
return threadPoolExecutor;
}
}
controller层
/**
* easyExcel异步导出通用案例2
*
* @param response
* @return
*/
@GetMapping("asyncExportFileFlow")
public String asyncExportFileFlow(HttpServletResponse response) {
CompletableFuture.runAsync(() -> {
this.studentService.asyncExportFileFlow(response);
}, exportThreadPoolExecutor).exceptionally(e -> {
log.error("导出文件失败", e);
return null;
});
return "ok";
}
service层
@Override
public void exportUserData(HttpServletResponse response) {
List<Map<String, Object>> data = this.sysUserMapper.list();
if (data.isEmpty()) {
throw new RuntimeException("数据为空");
}
recordFileFlow(response, data, ExcelExportEnum.USER_EXCEL);
}
/**
* 记录导出文件的流水
*
* @param response
* @param data
* @param excelExportEnum
*/
private void recordFileFlow(HttpServletResponse response,
List<Map<String, Object>> data,
ExcelExportEnum excelExportEnum) {
ExportFileFlow exportFileFlow = new ExportFileFlow();
exportFileFlow.setFileName(excelExportEnum.getFilename());
exportFileFlow.setCreateTime(LocalDateTime.now());
exportFileFlow.setOperateUser("系统用户");
this.exportFileFlowMapper.insert(exportFileFlow);
long beginTime = System.currentTimeMillis();
try {
String fileUrl = this.exportTemplate.uploadFileToOss(response, excelExportEnum, data);
long endTime = System.currentTimeMillis();
exportFileFlow.setExportStatus(1);
exportFileFlow.setExportConsumeTime((endTime - beginTime) / 1000);
exportFileFlow.setFileUrl(fileUrl);
} catch (Exception e) {
exportFileFlow.setExportStatus(2);
exportFileFlow.setFailReason(e.toString());
exportFileFlow.setCreateTime(null);
throw new RuntimeException(e);
} finally {
this.exportFileFlowMapper.update(exportFileFlow);
}
}
public String uploadFileToOss(HttpServletResponse response,
ExcelExportEnum excelExportEnum,
List<Map<String, Object>> writeData) throws IOException {
BufferedOutputStream tempOutStream = null;
BufferedInputStream tempInStream = null;
Path newFilePath = null;
try {
// 1.加载类路径下的模板
ClassPathResource classPathResource = new ClassPathResource(excelExportEnum.getFileClassPath());
String originalFileName = classPathResource.getFilename();
if (StringUtils.isEmpty(originalFileName)) {
throw new RuntimeException("文件名不能为空");
}
// 2.创建临时目录
File file = new File(DISK_TEMPORARY_LOCATION);
// 目录不存在则新建,如果新建失败则终止
boolean createDiskDirFail = !file.exists() && !file.mkdirs();
if (createDiskDirFail) {
throw new RuntimeException("临时文件目录创建失败");
}
String fileSuffix = originalFileName.substring(originalFileName.lastIndexOf("."));
newFilePath = Paths.get(file.getPath() + UUID.randomUUID() + fileSuffix);
// 使用缓冲流,加快读写
tempOutStream = new BufferedOutputStream(Files.newOutputStream(newFilePath));
// 3.往模板中写数据,并将写好的文件写入到磁盘临时文件
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
// 用于指定生成 Excel 文件的路径
.file(tempOutStream)
// 指定模板位置
.withTemplate(classPathResource.getInputStream()).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().build();
excelWriter.fill(new FillWrapper("data", writeData), fillConfig, writeSheet);
excelWriter.finish();
// 4.上传oss
tempInStream = new BufferedInputStream(Files.newInputStream(newFilePath));
String fileUrl = this.aliOssTemplate.uploadFile(tempInStream, classPathResource.getFilename());
log.info("文件上传成功,地址:{}", fileUrl);
return fileUrl;
} finally {
// 5.最后再删掉磁盘上临时文件
assert newFilePath != null;
Files.delete(newFilePath);
assert tempOutStream != null;
tempOutStream.close();
assert tempInStream != null;
tempInStream.close();
}
}
public String uploadFile(InputStream inputStream, String originalFileName) {
if (StringUtils.isEmpty(originalFileName)) {
throw new RuntimeException("文件名不能为空");
}
// 1.重命名文件,oss目录是 2021-9-10/uuid.文件后缀
String fileSuffix = originalFileName.substring(originalFileName.lastIndexOf("."));
String newFileName = UUID.randomUUID().toString().replace("-", "");
String renameFileName = LocalDate.now() + "/" + newFileName + fileSuffix;
// 2.文件上传
OSS ossClient = null;
try {
ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, renameFileName, inputStream);
ossClient.putObject(putObjectRequest);
} finally {
assert ossClient != null;
ossClient.shutdown();
}
// 3.组装获取返回的url
//https://classroomsys.oss-cn-beijing.aliyuncs.com/objectdir/a.png
return "https://" + bucketName + "." + endpoint + "/" + renameFileName;
}
CREATE TABLE `export_file_flow` (
`id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '文件名',
`file_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件url',
`export_status` int(0) NULL DEFAULT 2 COMMENT '导出状态:1处理中,2处理成功,3处理失败',
`fail_reason` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '导出失败原因',
`export_consume_time` bigint(0) NULL DEFAULT NULL COMMENT '导出耗时(秒)',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
`operate_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作人',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 244 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文件下载列表' ROW_FORMAT = Dynamic;
标签:异步,null,String,utf8mb4,EasyExcel,excel,exportFileFlow,new,NULL
From: https://www.cnblogs.com/party-abu/p/17584198.html