在SpringBoot应用中,采用同步方式导出Excel文件会导致服务器在生成文件期间阻塞,特别是在处理大量数据时,这种效率较低的方法会严重影响性能。为了解决这个问题,可以采用以下改进措施:首先将导出的数据进行拆分,然后利用CompletableFuture将导出任务异步化。通过easyExcel工具类并行导出多个Excel文件,最后将这些导出完成的文件压缩成ZIP格式,便于用户下载。
相比之下,使用FutureTask实现的多线程导出方法也能达到类似的效果,但CompletableFuture提供了更简洁的API和更强大的功能,更适合现代并发编程的需求。
具体实现:
@RestController
@RequestMapping("/export")
@RequiredArgsConstructor(onConstructor_ = {@Lazy, @Autowired})
public class ExportController {
private final ExcelExportService excelExportService;
@GetMapping("/zip")
public ResponseEntity<byte[]> exportToZip() throws Exception {
// 创建临时数据
List<Users> dataList = createDataList(100);
List<List<Users>> dataSets = ListUtil.partition(dataList, 10);
List<CompletableFuture<String>> futures = new ArrayList<>();
// 异步导出所有Excel文件
String outputDir = "D:\\data";
for (List<Users> dataSet : dataSets) {
futures.add(excelExportService.exportDataToExcel(dataSet, outputDir));
}
// 等待所有导出任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).get(10, TimeUnit.MINUTES);
// 堆代码 duidaima.com
// 收集Excel文件路径
List<String> excelFilePaths = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
// 压缩文件
File zipFile = new File("D:\\data\\output.zip");
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile))) {
for (String filePath : excelFilePaths) {
zipFile(new File(filePath), zipOut, new File(filePath).getName());
}
}
// 删除临时文件
for (String filePath : excelFilePaths) {
File file = new File(filePath);
file.delete();
}
// 返回ZIP文件
byte[] data = Files.readAllBytes(zipFile.toPath());
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=\"" + zipFile.getName() + "\"")
.contentType(MediaType.parseMediaType("application/zip"))
.body(data);
}
// 将文件添加到ZIP输出流中
private void zipFile(File file, ZipOutputStream zipOut, String entryName) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
ZipEntry zipEntry = new ZipEntry(entryName);
zipOut.putNextEntry(zipEntry);
byte[] bytesIn = new byte[4096];
int read;
while ((read = bis.read(bytesIn)) != -1) {
zipOut.write(bytesIn, 0, read);
}
zipOut.closeEntry();
}
}
/**
* 创建一个包含指定数量随机用户数据的列表。
*
* @param count 用户的数量
* @return 包含指定数量用户的列表
*/
public static List<Users> createDataList(int count) {
List<Users> dataList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < count; i++) {
String userId = "U" + String.format("%03d", i);
String userName = "用户" + String.format("%03d", i);
String userAge = String.valueOf(random.nextInt(60) + 18); // 年龄随机在18到77之间
String userSex = random.nextBoolean() ? "男" : "女";
String userAddress = "地址" + String.format("%03d", i);
String userEmail = userName.toLowerCase().replace("用户", "") + "@example.com";
Users user = new Users(userId, userName, userAge, userSex, userAddress, userEmail);
dataList.add(user);
}
return dataList;
}
}
异步并行生成excel文件,利用EasyExcel库来简化Excel的生成过程:
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy, @Autowired})
public class ExcelExportServiceImpl implements ExcelExportService {
private final ThreadPoolTaskExecutor taskExecutor;
@Override
public CompletableFuture<String> exportDataToExcel(List<Users> dataList, String outputDir) {
try {
Path temproaryFilePath = Files.createTempFile(Paths.get(outputDir), "excelFilePre", ".xlsx");
return CompletableFuture.supplyAsync(() -> {
try (OutputStream outputStream = new FileOutputStream(temproaryFilePath.toFile())) {
EasyExcel.write(outputStream, Users.class).sheet("用户信息").doWrite(dataList);
return temproaryFilePath.toString();
} catch (IOException e) {
throw new RuntimeException("Failed to export Excel file", e);
}
}, taskExecutor);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
线程池配置:
@Configuration
public class ThreadPoolConfig {
private static final int corePoolSize = 20;//线程池维护线程的最少数量
private static final int maximumPoolSize = 50;//线程池维护线程的最大数量
/**
* 默认线程池线程池
*
* @return Executor
*/
@Bean("taskExecutor")
public ThreadPoolTaskExecutor defaultThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数目
executor.setCorePoolSize(corePoolSize);
//指定最大线程数
executor.setMaxPoolSize(maximumPoolSize);
//队列中最大的数目
executor.setQueueCapacity(50);
//线程名称前缀
executor.setThreadNamePrefix("defaultThreadPool_");
//rejection-policy:当pool已经达到max size的时候,如何处理新任务
//CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
//对拒绝task的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//线程空闲后的最大存活时间
executor.setKeepAliveSeconds(60);
//加载
executor.initialize();
return executor;
}
}