task-56: 采集结果 Excel 改为临时文件+原子落位,流式写入或落位失败时清理临时文件,目标路径不残留半成品

This commit is contained in:
2026-08-30 17:05:56 +08:00
parent 15269c6ed9
commit adcfa28af3
2 changed files with 291 additions and 2 deletions
@@ -11,6 +11,9 @@ import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
@@ -51,14 +54,25 @@ public class CollectDataExcelAssemblyService {
List<CollectDataResultRowVo> items,
List<CollectDataSummaryRowDto> summaries,
Supplier<List<CollectDataResultRowVo>> rawItemsSupplier) {
if (outputXlsx == null) {
throw new BusinessException("生成采集数据 Excel 失败: 输出文件路径为空");
}
SXSSFWorkbook workbook = new SXSSFWorkbook(200);
workbook.setCompressTempFiles(true);
try (FileOutputStream outputStream = new FileOutputStream(outputXlsx)) {
// 先写目标同目录的临时文件,成功后原子落位:写入或落位失败时目标路径
// 不残留半成品,临时文件由 catch/finally 清理(SXSSF 滚动窗口文件由 dispose 释放)。
File tmpFile = new File(outputXlsx.getAbsolutePath() + ".tmp");
try {
writeDetailSheet(workbook, items);
writeSummarySheet(workbook, summaries, rawItemsSupplier);
workbook.write(outputStream);
try (FileOutputStream outputStream = new FileOutputStream(tmpFile)) {
workbook.write(outputStream);
}
Files.move(tmpFile.toPath(), outputXlsx.toPath(),
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (Exception ex) {
log.warn("[collect-data] write workbook failed: {}", ex.getMessage());
deleteQuietly(tmpFile);
throw new BusinessException("生成采集数据 Excel 失败: " + ex.getMessage());
} finally {
try {
@@ -66,6 +80,15 @@ public class CollectDataExcelAssemblyService {
} catch (Exception ignored) {
}
workbook.dispose();
deleteQuietly(tmpFile);
}
}
private void deleteQuietly(File file) {
try {
Files.deleteIfExists(file.toPath());
} catch (IOException ex) {
log.warn("[collect-data] delete workbook temp file failed: {}", ex.getMessage());
}
}