From dae85270043789ebf5011d09d61bca7f6733a668 Mon Sep 17 00:00:00 2001 From: cwchen <1048842385@qq.com> Date: Mon, 2 Feb 2026 16:59:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8B=E8=BD=BD=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/service/data/ExportExcelService.java | 100 ++++++++++++------ .../bonus/web/service/data/ExportService.java | 13 ++- .../com/bonus/data/service/DILineService.java | 2 +- .../bonus/quartz/task/FileCleanupTask.java | 8 +- 4 files changed, 80 insertions(+), 43 deletions(-) diff --git a/bonus-admin/src/main/java/com/bonus/web/service/data/ExportExcelService.java b/bonus-admin/src/main/java/com/bonus/web/service/data/ExportExcelService.java index e54ed52..46d9fd5 100644 --- a/bonus-admin/src/main/java/com/bonus/web/service/data/ExportExcelService.java +++ b/bonus-admin/src/main/java/com/bonus/web/service/data/ExportExcelService.java @@ -50,7 +50,11 @@ public class ExportExcelService { private static final String REDIS_KEY_PREFIX = "export:task:"; // private static final String TEMP_PATH = System.getProperty("java.io.tmpdir") + File.separator + "export_task" + File.separator; - private static final String TEMP_PATH = BonusConfig.getLsFile() + File.separator + "export_task" + File.separator; +// private final String TEMP_PATH = BonusConfig.getLsFile() + File.separator + "export_task" + File.separator; + + private String getTempPath() { + return BonusConfig.getLsFile() + File.separator + "export_task" + File.separator; + } public String createExportTask(ParamsDto dto) { String taskId = UUID.randomUUID().toString(); @@ -68,25 +72,31 @@ public class ExportExcelService { try { Date start = dto.getStartTime(); Date end = dto.getEndTime(); + + // 预先转换好由于比较的 LocalDate LocalDate startLD = toLocalDate(start); LocalDate endLD = toLocalDate(end); - long dayDiff = DateUtil.between(start, end, DateUnit.DAY); - File tempDir = new File(TEMP_PATH); + long dayDiff = DateUtil.between(start, end, DateUnit.DAY); + log.info("任务[{}] 临时路径:{}", taskId, getTempPath()); + + File tempDir = new File(getTempPath()); if (!tempDir.exists()) tempDir.mkdirs(); File finalResultFile; String downloadFileName; if (dayDiff <= 0) { - // 单天任务:直接处理 + // --- 单天任务逻辑保持不变 --- downloadFileName = "数据识别_" + DateUtil.formatDate(start) + ".xlsx"; - finalResultFile = new File(TEMP_PATH + taskId + "_" + downloadFileName); + finalResultFile = new File(getTempPath() + taskId + "_" + downloadFileName); generateExcel(finalResultFile, start, end, taskId, 0, 100, true); } else { - // 多天任务:多线程处理 + // --- 多天任务:多线程处理 --- List dateRange = getDatesBetween(start, end); int totalDays = dateRange.size(); + + // 使用同步列表保证添加安全 List subFiles = Collections.synchronizedList(new ArrayList<>()); CountDownLatch latch = new CountDownLatch(totalDays); AtomicInteger finishedCount = new AtomicInteger(0); @@ -95,34 +105,40 @@ public class ExportExcelService { exportThreadPool.execute(() -> { try { String subFileName = taskId + "_数据识别_" + DateUtil.formatDate(currentDate) + ".xlsx"; - File subFile = new File(TEMP_PATH + subFileName); - // 传入 false,表示子线程不直接更新 Redis 整体进度,由主线程负责更新 - LocalDate currentLD = toLocalDate(end); - // 进行逻辑判断 - boolean isSameAsStart = currentLD.equals(startLD); - boolean isSameAsEnd = currentLD.equals(endLD); - if (isSameAsStart && !isSameAsEnd) { - // 情况 1:仅与开始日期相同 - LocalDateTime endOfDay = currentLD.atTime(LocalTime.MAX); - Date endDate = Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant()); - generateExcel(subFile, start, endDate, taskId, -1, -1, false); - } else if (!isSameAsStart && isSameAsEnd) { - // 情况 2:仅与结束日期相同 - Date startDate = Date.from(currentLD.atStartOfDay(ZoneId.systemDefault()).toInstant()); - generateExcel(subFile, startDate, end, taskId, -1, -1, false); - } else if (!isSameAsStart && !isSameAsEnd) { - // 情况 3:都不同 - Date startDate = Date.from(currentLD.atStartOfDay(ZoneId.systemDefault()).toInstant()); - LocalDateTime endOfDay = currentLD.atTime(LocalTime.MAX); - Date endDate = Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant()); - generateExcel(subFile, startDate, endDate, taskId, -1, -1, false); + File subFile = new File(getTempPath() + subFileName); + + // [关键修复] 获取当前循环日期的 LocalDate + LocalDate currentLD = toLocalDate(currentDate); + + // [逻辑简化] 计算当前切片的起止时间 + Date sliceStartTime; + Date sliceEndTime; + + // 1. 确定开始时间:如果是第一天,用全局start;否则用当天的 00:00:00 + if (currentLD.equals(startLD)) { + sliceStartTime = start; + } else { + sliceStartTime = Date.from(currentLD.atStartOfDay(ZoneId.systemDefault()).toInstant()); } + + // 2. 确定结束时间:如果是最后一天,用全局end;否则用当天的 23:59:59 + if (currentLD.equals(endLD)) { + sliceEndTime = end; + } else { + LocalDateTime endOfDay = currentLD.atTime(LocalTime.MAX); + sliceEndTime = Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant()); + } + + // 执行生成 (updateProgress=false) + generateExcel(subFile, sliceStartTime, sliceEndTime, taskId, -1, -1, false); + subFiles.add(subFile); } catch (Exception e) { - log.error("子任务执行失败: {}", currentDate, e); + log.error("子任务[{}] 日期[{}] 执行失败", taskId, DateUtil.formatDate(currentDate), e); + // 可以在这里记录失败的子文件,或者抛出异常让主流程感知 } finally { - // 更新整体进度:0-95% int finished = finishedCount.incrementAndGet(); + // 进度条平滑处理,保留最后几秒给打包 int currentProgress = (int) ((double) finished / totalDays * 95); updateStatusProgress(taskId, currentProgress, "running"); latch.countDown(); @@ -130,21 +146,35 @@ public class ExportExcelService { }); } - // 最多等待子任务处理 30 分钟 - latch.await(30, TimeUnit.MINUTES); + // 等待所有子任务完成 + boolean allFinished = latch.await(30, TimeUnit.MINUTES); + if (!allFinished) { + log.warn("任务[{}] 部分子线程超时,正在打包已完成部分", taskId); + } + + updateStatusProgress(taskId, 96, "running"); + + // [优化] 排序子文件。多线程执行完顺序是乱的,按文件名(日期)排序后再打包,解压后体验更好 + subFiles.sort(Comparator.comparing(File::getName)); - updateStatusProgress(taskId, 98, "running"); String dateRangeStr = DateUtil.format(start, "yyyyMMdd") + "-" + DateUtil.format(end, "yyyyMMdd"); downloadFileName = "批量数据识别_" + dateRangeStr + ".zip"; - finalResultFile = new File(TEMP_PATH + taskId + ".zip"); + finalResultFile = new File(getTempPath() + taskId + ".zip"); - ZipUtil.zip(finalResultFile, false, subFiles.toArray(new File[0])); - subFiles.forEach(File::delete); + // 只有当有文件生成时才打包 + if (!subFiles.isEmpty()) { + ZipUtil.zip(finalResultFile, false, subFiles.toArray(new File[0])); + // 删除临时子文件 + subFiles.forEach(File::delete); + } else { + throw new RuntimeException("未能生成任何数据文件"); + } } // 完成任务更新 completeTask(taskId, finalResultFile, downloadFileName); } catch (Exception e) { + log.error("导出任务异常", e); handleError(taskId, e); } } diff --git a/bonus-admin/src/main/java/com/bonus/web/service/data/ExportService.java b/bonus-admin/src/main/java/com/bonus/web/service/data/ExportService.java index 4d530fd..b6e08cc 100644 --- a/bonus-admin/src/main/java/com/bonus/web/service/data/ExportService.java +++ b/bonus-admin/src/main/java/com/bonus/web/service/data/ExportService.java @@ -42,7 +42,12 @@ public class ExportService { // 临时文件存放目录 // private static final String TEMP_DIR = System.getProperty("java.io.tmpdir") + "/export_task/"; - private static final String TEMP_DIR = BonusConfig.getLsFile() + "/export_task/"; +// private final String TEMP_DIR = BonusConfig.getLsFile() + "/export_task/"; +// private final String TEMP_DIR = BonusConfig.getLsFile() + "/export_task/"; + + private String getTempDir() { + return BonusConfig.getLsFile() + File.separator + "export_task" + File.separator; + } /** * 创建任务并立即返回 TaskID @@ -121,7 +126,7 @@ public class ExportService { groupedImages.computeIfAbsent(dateStr, k -> new ArrayList<>()).add(img); } - File tempDir = new File(TEMP_DIR); + File tempDir = new File(getTempDir()); if (!tempDir.exists()) tempDir.mkdirs(); String masterFileName = "图像识别数据_" + System.currentTimeMillis() + ".zip"; @@ -200,12 +205,12 @@ public class ExportService { */ private File createDailyZip(String dateStr, List images) throws IOException { // 确保临时目录存在 - File tempDir = new File(TEMP_DIR); + File tempDir = new File(getTempDir()); if (!tempDir.exists()) { tempDir.mkdirs(); } - File dailyZip = new File(TEMP_DIR, "temp_" + dateStr + "_" + UUID.randomUUID() + ".zip"); + File dailyZip = new File(getTempDir(), "temp_" + dateStr + "_" + UUID.randomUUID() + ".zip"); try (FileOutputStream fos = new FileOutputStream(dailyZip); ZipOutputStream dailyZos = new ZipOutputStream(fos)) { diff --git a/bonus-data/src/main/java/com/bonus/data/service/DILineService.java b/bonus-data/src/main/java/com/bonus/data/service/DILineService.java index 8ba21c4..c5f2977 100644 --- a/bonus-data/src/main/java/com/bonus/data/service/DILineService.java +++ b/bonus-data/src/main/java/com/bonus/data/service/DILineService.java @@ -44,7 +44,7 @@ public interface DILineService { * 删除线的坐标 * @return void * @author cwchen - * @date 2026/1/20 14:29 + * @date 2026/1/20 14:52 */ void deleteLine(); } diff --git a/bonus-quartz/src/main/java/com/bonus/quartz/task/FileCleanupTask.java b/bonus-quartz/src/main/java/com/bonus/quartz/task/FileCleanupTask.java index 7ed4c90..00be06b 100644 --- a/bonus-quartz/src/main/java/com/bonus/quartz/task/FileCleanupTask.java +++ b/bonus-quartz/src/main/java/com/bonus/quartz/task/FileCleanupTask.java @@ -24,15 +24,17 @@ public class FileCleanupTask { // 使用之前定义的临时目录路径 // private static final String TEMP_PATH = System.getProperty("java.io.tmpdir") + File.separator + "export_task" + File.separator; - private static final String TEMP_PATH = BonusConfig.getLsFile() + File.separator + "export_task" + File.separator; - +// private final String TEMP_PATH = BonusConfig.getLsFile() + File.separator + "export_task" + File.separator; + private String getTempPath() { + return BonusConfig.getLsFile() + File.separator + "export_task" + File.separator; + } /** * 每天凌晨 2 点执行清理任务 * cron 表达式: 秒 分 时 日 月 周 */ public void cleanupTempFiles() { log.info("开始执行导出临时文件清理任务..."); - File directory = new File(TEMP_PATH); + File directory = new File(getTempPath()); if (!directory.exists() || !directory.isDirectory()) { log.info("临时目录不存在,跳过清理。");