From 5bb594030714c8b3299172ce30bcd56b357703cc Mon Sep 17 00:00:00 2001 From: cwchen <1048842385@qq.com> Date: Wed, 7 Jan 2026 18:12:30 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=BA=E9=99=B7=E5=88=A0=E9=99=A4=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/DefectDelRecordController.java | 113 +++++++ .../dao/IDayDefectRateDao.java | 19 ++ .../dao/IDefectDelRecordDao.java | 37 +++ .../entity/DayDefectRateDto.java | 2 + .../defectStatistics/entity/ExportTask.java | 22 ++ .../defectStatistics/entity/ImageRecord.java | 23 ++ .../defectStatistics/entity/TowsDelVo.java | 31 ++ .../service/IDefectDelRecordService.java | 35 +++ .../impl/DayDefectRateServiceImpl.java | 5 + .../service/impl/DefectDelRecordService.java | 50 +++ .../service/impl/ExportService.java | 290 ++++++++++++++++++ .../config/AsycTaskExecutorConfig.java | 26 ++ .../controller/PermissionController.java | 2 +- .../boot/manager/plan/FileCleanupTask.java | 75 +++++ src/main/resources/application.yml | 96 ------ src/main/resources/i18n/lang.properties | 5 + src/main/resources/i18n/lang_en_US.properties | 4 + src/main/resources/i18n/lang_zh_CN.properties | 6 +- .../defectStatistics/DayDefectRateMapper.xml | 16 +- .../DefectDelRecordMapper.xml | 35 +++ .../js/defectStatistics/defectDelRecord.js | 272 ++++++++++++++++ .../defectStatistics/defectDelRecord.html | 107 +++++++ .../defectStatistics/export-progress.html | 268 ++++++++++++++++ 23 files changed, 1440 insertions(+), 99 deletions(-) create mode 100644 src/main/java/com/bonus/boot/manager/defectStatistics/controller/DefectDelRecordController.java create mode 100644 src/main/java/com/bonus/boot/manager/defectStatistics/dao/IDefectDelRecordDao.java create mode 100644 src/main/java/com/bonus/boot/manager/defectStatistics/entity/ExportTask.java create mode 100644 src/main/java/com/bonus/boot/manager/defectStatistics/entity/ImageRecord.java create mode 100644 src/main/java/com/bonus/boot/manager/defectStatistics/entity/TowsDelVo.java create mode 100644 src/main/java/com/bonus/boot/manager/defectStatistics/service/IDefectDelRecordService.java create mode 100644 src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/DefectDelRecordService.java create mode 100644 src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/ExportService.java create mode 100644 src/main/java/com/bonus/boot/manager/plan/FileCleanupTask.java delete mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/mappers/defectStatistics/DefectDelRecordMapper.xml create mode 100644 src/main/resources/static/js/defectStatistics/defectDelRecord.js create mode 100644 src/main/resources/static/pages/defectStatistics/defectDelRecord.html create mode 100644 src/main/resources/static/pages/defectStatistics/export-progress.html diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/controller/DefectDelRecordController.java b/src/main/java/com/bonus/boot/manager/defectStatistics/controller/DefectDelRecordController.java new file mode 100644 index 0000000..1e71da8 --- /dev/null +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/controller/DefectDelRecordController.java @@ -0,0 +1,113 @@ +package com.bonus.boot.manager.defectStatistics.controller; + +import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto; +import com.bonus.boot.manager.defectStatistics.entity.ExportTask; +import com.bonus.boot.manager.defectStatistics.entity.TowsDelVo; +import com.bonus.boot.manager.defectStatistics.service.IDefectDelRecordService; +import com.bonus.boot.manager.defectStatistics.service.impl.ExportService; +import com.bonus.boot.manager.manager.annotation.DecryptAndVerify; +import com.bonus.boot.manager.manager.annotation.LogAnnotation; +import com.bonus.boot.manager.manager.entity.EncryptedReq; +import com.bonus.boot.manager.manager.utils.ServerResponse; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.io.File; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @className:DefectDelController + * @author:cwchen + * @date:2026-01-07-14:00 + * @version:1.0 + * @description:缺陷删除记录-web层 + */ +@RestController +@RequestMapping("/defectDelRecord") +public class DefectDelRecordController { + + @Resource(name = "IDefectDelRecordService") + private IDefectDelRecordService service; + + @Resource(name = "ExportService") + private ExportService exportService; + + @PostMapping(value = "getList") + @DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理 + @LogAnnotation(operModul = "缺陷删除记录", operation = "查询数据概览", operDesc = "系统级事件", operType = "查询") + public ServerResponse getList(EncryptedReq data) { + PageHelper.startPage(data.getData().getPage(), data.getData().getLimit()); + List list = service.getList(data.getData()); + PageInfo pageInfo = new PageInfo<>(list); + return ServerResponse.createSuccessPage(pageInfo, data.getData().getPage(), data.getData().getLimit()); + } + + /** + * 1. 启动导出任务 + */ + @GetMapping("/export") + @DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理 + public Map startExport(EncryptedReq encryptedReq) { + // 启动异步任务 + String taskId = exportService.createExportTask(encryptedReq.getData()); + Map data = new HashMap<>(); + data.put("taskId", taskId); + Map result = new HashMap<>(); + result.put("code", 200); + result.put("msg", "任务已启动"); + result.put("data", data); + return result; + } + + /** + * 2. 查询进度 + * 对应前端: getExportProgressAPI + */ + @GetMapping("/progress") + @DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理 + public Map getProgress(EncryptedReq encryptedReq) { + ExportTask task = exportService.getTaskStatus(encryptedReq.getData().getTaskId()); + // 修改 404 情况 + if (task == null) { + Map errorResult = new HashMap<>(); + errorResult.put("code", 404); + errorResult.put("msg", "任务不存在"); + return errorResult; + } + // 修改成功情况 + Map successResult = new HashMap<>(); + successResult.put("code", 200); + successResult.put("data", task); + return successResult; + } + + /** + * 3. 下载文件 + */ + @GetMapping("/download") + @DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理 + public ResponseEntity downloadFile( + EncryptedReq encryptedReq) throws IOException { + + File file = exportService.getExportFile(encryptedReq.getData().getTaskId()); + + if (file == null || !file.exists()) { + return ResponseEntity.notFound().build(); + } + org.springframework.core.io.Resource resource = new org.springframework.core.io.FileSystemResource(file); + String fileName = URLEncoder.encode(file.getName(), "UTF-8").replaceAll("\\+", "%20"); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"") + .body(resource); + } +} diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/dao/IDayDefectRateDao.java b/src/main/java/com/bonus/boot/manager/defectStatistics/dao/IDayDefectRateDao.java index 978a82a..f37ca68 100644 --- a/src/main/java/com/bonus/boot/manager/defectStatistics/dao/IDayDefectRateDao.java +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/dao/IDayDefectRateDao.java @@ -1,5 +1,6 @@ package com.bonus.boot.manager.defectStatistics.dao; +import com.bonus.boot.manager.defectStatistics.entity.ComprehensiveVo; import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto; import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateVo; import org.apache.ibatis.annotations.Param; @@ -45,4 +46,22 @@ public interface IDayDefectRateDao { * @date 2025/12/10 15:18 */ void delTowsData(DayDefectRateDto dto); + + /** + * 获取缺陷详情 + * @param dto + * @return ComprehensiveVo + * @author cwchen + * @date 2026/1/7 16:48 + */ + ComprehensiveVo getDefectDetail(DayDefectRateDto dto); + + /** + * 记录删除的图片 + * @param vo + * @return void + * @author cwchen + * @date 2026/1/7 16:52 + */ + void addTowsDelData(ComprehensiveVo vo); } diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/dao/IDefectDelRecordDao.java b/src/main/java/com/bonus/boot/manager/defectStatistics/dao/IDefectDelRecordDao.java new file mode 100644 index 0000000..3182c77 --- /dev/null +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/dao/IDefectDelRecordDao.java @@ -0,0 +1,37 @@ +package com.bonus.boot.manager.defectStatistics.dao; + +import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto; +import com.bonus.boot.manager.defectStatistics.entity.ImageRecord; +import com.bonus.boot.manager.defectStatistics.entity.TowsDelVo; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @className:IDefectDelRecordDao + * @author:cwchen + * @date:2026-01-07-14:03 + * @version:1.0 + * @description:缺陷删除数据层 + */ +@Repository(value = "IDefectDelRecordDao") +public interface IDefectDelRecordDao { + + /** + * 缺陷删除记录 + * @param dto + * @return List + * @author cwchen + * @date 2026/1/7 14:22 + */ + List getList(DayDefectRateDto dto); + + /** + * 导出缺陷数据查询 + * @param dto + * @return List + * @author cwchen + * @date 2026/1/7 16:59 + */ + List getImageData(DayDefectRateDto dto); +} diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/entity/DayDefectRateDto.java b/src/main/java/com/bonus/boot/manager/defectStatistics/entity/DayDefectRateDto.java index 854e5d8..785e370 100644 --- a/src/main/java/com/bonus/boot/manager/defectStatistics/entity/DayDefectRateDto.java +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/entity/DayDefectRateDto.java @@ -18,4 +18,6 @@ public class DayDefectRateDto extends PageEntity { private String startDate; private String endDate; + + private String taskId; } diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/entity/ExportTask.java b/src/main/java/com/bonus/boot/manager/defectStatistics/entity/ExportTask.java new file mode 100644 index 0000000..9568876 --- /dev/null +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/entity/ExportTask.java @@ -0,0 +1,22 @@ +package com.bonus.boot.manager.defectStatistics.entity; + +import lombok.Data; + +/** + * @className:ExportTask + * @author:cwchen + * @date:2025-12-26-10:15 + * @version:1.0 + * @description:导出任务 + */ +@Data +public class ExportTask { + private String taskId; + private Integer progress; // 0 - 100 + private String status; // "processing", "completed", "failed" + private String message; // 错误信息 + private String downloadUrl; + private String fileName; + private String finalFilePath; // 服务器本地存储路径 + private String filePath; // 服务器本地临时文件路径 +} diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/entity/ImageRecord.java b/src/main/java/com/bonus/boot/manager/defectStatistics/entity/ImageRecord.java new file mode 100644 index 0000000..5732745 --- /dev/null +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/entity/ImageRecord.java @@ -0,0 +1,23 @@ +package com.bonus.boot.manager.defectStatistics.entity; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * @className:ImageRecord + * @author:cwchen + * @date:2025-12-26-10:39 + * @version:1.0 + * @description: 下载图片实体类 + */ +@Data +public class ImageRecord { + + private String url; + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date createTime; +} diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/entity/TowsDelVo.java b/src/main/java/com/bonus/boot/manager/defectStatistics/entity/TowsDelVo.java new file mode 100644 index 0000000..74df956 --- /dev/null +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/entity/TowsDelVo.java @@ -0,0 +1,31 @@ +package com.bonus.boot.manager.defectStatistics.entity; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * @className:TowsDelVo + * @author:cwchen + * @date:2026-01-07-14:11 + * @version:1.0 + * @description:缺陷删除-vo + */ +@Data +public class TowsDelVo { + + private Long id; + /**图片路径*/ + private String imgPath; + /**二次分析图片*/ + private String twoAnalysisUrl; + /**删除时间*/ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date delTime; + /**图片编号*/ + private String imgNo; + +} diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/service/IDefectDelRecordService.java b/src/main/java/com/bonus/boot/manager/defectStatistics/service/IDefectDelRecordService.java new file mode 100644 index 0000000..c8f6355 --- /dev/null +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/service/IDefectDelRecordService.java @@ -0,0 +1,35 @@ +package com.bonus.boot.manager.defectStatistics.service; + +import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto; +import com.bonus.boot.manager.defectStatistics.entity.ImageRecord; +import com.bonus.boot.manager.defectStatistics.entity.TowsDelVo; + +import java.util.List; + +/** + * @className:IDefectDelRecordService + * @author:cwchen + * @date:2026-01-07-14:02 + * @version:1.0 + * @description:缺陷删除记录-业务层 + */ +public interface IDefectDelRecordService { + + /** + * 缺陷删除记录 + * @param data + * @return List + * @author cwchen + * @date 2026/1/7 14:20 + */ + List getList(DayDefectRateDto data); + + /** + * 导出缺陷数据查询 + * @param dto + * @return List + * @author cwchen + * @date 2026/1/7 16:59 + */ + List getImageData(DayDefectRateDto dto); +} diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/DayDefectRateServiceImpl.java b/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/DayDefectRateServiceImpl.java index cd248ec..5c1b903 100644 --- a/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/DayDefectRateServiceImpl.java +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/DayDefectRateServiceImpl.java @@ -4,6 +4,7 @@ import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import com.bonus.boot.manager.defectStatistics.dao.IDayDefectRateDao; +import com.bonus.boot.manager.defectStatistics.entity.ComprehensiveVo; import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto; import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateVo; import com.bonus.boot.manager.defectStatistics.service.IDayDefectRateService; @@ -125,6 +126,10 @@ public class DayDefectRateServiceImpl implements IDayDefectRateService { @Override public AjaxResult delTowsData(DayDefectRateDto dto) { try { + ComprehensiveVo vo = dayDefectRateDao.getDefectDetail(dto); + if (Objects.nonNull(vo)) { + dayDefectRateDao.addTowsDelData(vo); + } dayDefectRateDao.delTowsData(dto); return AjaxResult.success(); } catch (Exception e) { diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/DefectDelRecordService.java b/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/DefectDelRecordService.java new file mode 100644 index 0000000..67dcc19 --- /dev/null +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/DefectDelRecordService.java @@ -0,0 +1,50 @@ +package com.bonus.boot.manager.defectStatistics.service.impl; + +import com.bonus.boot.manager.defectStatistics.dao.IDefectDelRecordDao; +import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto; +import com.bonus.boot.manager.defectStatistics.entity.ImageRecord; +import com.bonus.boot.manager.defectStatistics.entity.TowsDelVo; +import com.bonus.boot.manager.defectStatistics.service.IDefectDelRecordService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; + +/** + * @className:DefectDelRecordService + * @author:cwchen + * @date:2026-01-07-14:02 + * @version:1.0 + * @description:缺陷删除业务逻辑层 + */ +@Service(value = "IDefectDelRecordService") +@Slf4j +public class DefectDelRecordService implements IDefectDelRecordService { + + @Resource(name = "IDefectDelRecordDao") + private IDefectDelRecordDao defectDelRecordDao; + + @Override + public List getList(DayDefectRateDto dto) { + try { + List list = defectDelRecordDao.getList(dto); + return list == null ? Collections.emptyList() : list; + } catch (Exception e) { + log.error(e.toString(), e); + return Collections.emptyList(); + } + } + + @Override + public List getImageData(DayDefectRateDto dto) { + try { + List list = defectDelRecordDao.getImageData(dto); + return list == null ? Collections.emptyList() : list; + } catch (Exception e) { + log.error(e.toString(), e); + return Collections.emptyList(); + } + } +} diff --git a/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/ExportService.java b/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/ExportService.java new file mode 100644 index 0000000..8c74e83 --- /dev/null +++ b/src/main/java/com/bonus/boot/manager/defectStatistics/service/impl/ExportService.java @@ -0,0 +1,290 @@ +package com.bonus.boot.manager.defectStatistics.service.impl; + + +import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto; +import com.bonus.boot.manager.defectStatistics.entity.ExportTask; +import com.bonus.boot.manager.defectStatistics.entity.ImageRecord; +import com.bonus.boot.manager.defectStatistics.service.IDefectDelRecordService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * @className:ExportService + * @author:cwchen + * @date:2025-12-26-10:21 + * @version:1.0 + * @description:识别图片导出-业务层 + */ +@Slf4j +@Service(value = "ExportService") +public class ExportService { + + @Resource + private RedisTemplate redisTemplate; + + @Resource(name = "IDefectDelRecordService") + private IDefectDelRecordService service; + + // Redis Key 前缀 + private static final String REDIS_KEY_PREFIX = "EXPORT:TASK:"; + // 任务在 Redis 中的过期时间 + private static final long EXPIRE_TIME = 24; + + // 临时文件存放目录 + private static final String TEMP_DIR = System.getProperty("java.io.tmpdir") + "/export_task/"; + + // 缺陷文件前缀路径 + private static final String PREFIX_PATH = "http://10.67.21.124:1905/hfTowsBmw/hftowsf/"; + + /** + * 创建任务并立即返回 TaskID + */ + public String createExportTask(DayDefectRateDto dto) { + String taskId = UUID.randomUUID().toString(); + + ExportTask task = new ExportTask(); + task.setTaskId(taskId); + task.setProgress(0); + task.setStatus("processing"); + + // 存入 Redis + saveTaskToRedis(task); + + // 异步执行 + processExportAsync(taskId, dto); + + return taskId; + } + + /** + * 从 Redis 获取任务状态 + */ + public ExportTask getTaskStatus(String taskId) { + Object obj = redisTemplate.opsForValue().get(REDIS_KEY_PREFIX + taskId); + if (obj instanceof ExportTask) { + return (ExportTask) obj; + } + return null; + } + + public File getExportFile(String taskId) { + ExportTask task = getTaskStatus(taskId); + // 注意:文件路径还是存在本地磁盘的。 + // 如果是多实例部署,这里需要改为上传到 OSS/S3 并返回云存储链接, + // 或者使用 NFS/共享存储挂载该目录。 + // 下面代码假设仍在单机磁盘或挂载盘上。 + if (task != null && task.getFinalFilePath() != null) { + return new File(task.getFinalFilePath()); + } + return null; + } + + /** + * 辅助方法:保存/更新任务到 Redis + */ + private void saveTaskToRedis(ExportTask task) { + String key = REDIS_KEY_PREFIX + task.getTaskId(); + // 设置 Key 和 过期时间 + redisTemplate.opsForValue().set(key, task, EXPIRE_TIME, TimeUnit.HOURS); + } + + /** + * 核心异步处理逻辑 (业务逻辑大体不变,变的是状态更新方式) + */ + @Async("taskExecutor2") + public void processExportAsync(String taskId, DayDefectRateDto dto) { + // 先从 Redis 拿最新的状态 + ExportTask task = getTaskStatus(taskId); + if (task == null) return; // 任务可能已过期或被删 + + File masterZipFile = null; + + try { + List allImages = mockDatabaseQuery(dto); + + if (allImages.isEmpty()) { + throw new RuntimeException("没有查询到数据"); + } + + Map> groupedImages = new HashMap<>(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + for (ImageRecord img : allImages) { + String dateStr = sdf.format(img.getCreateTime()); + groupedImages.computeIfAbsent(dateStr, k -> new ArrayList<>()).add(img); + } + + File tempDir = new File(TEMP_DIR); + if (!tempDir.exists()) tempDir.mkdirs(); + + String masterFileName = "缺陷数据_" + System.currentTimeMillis() + ".zip"; + masterZipFile = new File(tempDir, masterFileName); + + int totalDates = groupedImages.size(); + int processedCount = 0; + + try (FileOutputStream fos = new FileOutputStream(masterZipFile); + ZipOutputStream masterZos = new ZipOutputStream(fos)) { + + for (Map.Entry> entry : groupedImages.entrySet()) { + String date = entry.getKey(); + List dailyImages = entry.getValue(); + + File dailyZipFile = createDailyZip(date, dailyImages); + + ZipEntry zipEntry = new ZipEntry(date + ".zip"); + masterZos.putNextEntry(zipEntry); + + try (FileInputStream dailyFis = new FileInputStream(dailyZipFile)) { + IOUtils.copy(dailyFis, masterZos); + } + masterZos.closeEntry(); + dailyZipFile.delete(); + + processedCount++; + // 更新 Redis 中的进度 + int currentProgress = (int) ((double) processedCount / totalDates * 90); + updateProgressInRedis(taskId, currentProgress); + } + masterZos.finish(); + } + + // 完成状态更新到 Redis + task.setProgress(100); + task.setStatus("completed"); + // 这里生成下载接口的地址 + task.setDownloadUrl("/defectDelRecord/download?taskId=" + taskId); + task.setFileName(masterFileName); + task.setFinalFilePath(masterZipFile.getAbsolutePath()); + saveTaskToRedis(task); + + } catch (Exception e) { + e.printStackTrace(); + task.setStatus("failed"); + task.setMessage("导出失败: " + e.getMessage()); + saveTaskToRedis(task); + + if (masterZipFile != null && masterZipFile.exists()) { + masterZipFile.delete(); + } + } + } + + private void updateProgressInRedis(String taskId, int progress) { + ExportTask task = getTaskStatus(taskId); + if (task != null) { + if (progress > 99) progress = 99; + if (progress > task.getProgress()) { + task.setProgress(progress); + saveTaskToRedis(task); // 写回 Redis + } + } + } + + /** + * 生成某一天的压缩包 (已修改:支持从服务器本地路径读取文件) + * + * @param dateStr 日期字符串 + * @param images 包含本地路径信息的图片记录列表 + * @return 压缩后的临时文件 + */ + private File createDailyZip(String dateStr, List images) throws IOException { + // 1. 确保临时目录存在 + File tempDir = new File(TEMP_DIR); + if (!tempDir.exists()) { + tempDir.mkdirs(); + } + + File dailyZip = new File(TEMP_DIR, "temp_" + dateStr + "_" + UUID.randomUUID() + ".zip"); + + try (FileOutputStream fos = new FileOutputStream(dailyZip); + ZipOutputStream dailyZos = new ZipOutputStream(fos)) { + + for (ImageRecord img : images) { + String imageUrl = PREFIX_PATH + img.getUrl(); +// String imageUrl = "http://192.168.0.14:9090/smart-bid/personnelDatabase/2025/11/26/2dcc8d4ad41941c98593446f5eacb536.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20260107%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260107T091412Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=bc233cd628e21f4f8a62864e461eba04a16d88207ea3b239be9eb774ed958c39"; + + if (imageUrl == null || !imageUrl.startsWith("http")) { + log.warn("无效的图片URL: {}", imageUrl); + continue; + } + + HttpURLConnection connection = null; + try { + // 2. 建立 HTTP 连接 + URL url = new URL(imageUrl); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); // 5秒连接超时 + connection.setReadTimeout(10000); // 10秒读取超时 + + int responseCode = connection.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + log.error("下载失败, HTTP响应码: {}, URL: {}", responseCode, imageUrl); + writeErrorEntry(dailyZos, imageUrl, "HTTP Error: " + responseCode); + continue; + } + + // 3. 从 URL 获取文件名 (或生成唯一名) + String fileName = imageUrl.substring(imageUrl.lastIndexOf('/') + 1); + if (fileName.contains("?")) { // 处理带参数的 URL + fileName = fileName.substring(0, fileName.indexOf('?')); + } + String zipEntryName = UUID.randomUUID().toString().substring(0, 8) + "_" + fileName; + + // 4. 创建 Zip 条目并拷贝流 + ZipEntry entry = new ZipEntry(zipEntryName); + dailyZos.putNextEntry(entry); + + try (InputStream is = connection.getInputStream(); + BufferedInputStream bis = new BufferedInputStream(is)) { + + // 使用 IOUtils 将网络流直接拷贝到 ZIP 流 + IOUtils.copy(bis, dailyZos); + } + dailyZos.closeEntry(); + log.info("成功将远程图片添加到压缩包: {}", imageUrl); + + } catch (Exception e) { + log.error("下载远程图片异常: URL={}, Error={}", imageUrl, e.getMessage()); + writeErrorEntry(dailyZos, imageUrl, "Exception: " + e.getMessage()); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + } + return dailyZip; + } + + /** + * 辅助方法:向压缩包写入错误信息文件 + */ + private void writeErrorEntry(ZipOutputStream zos, String path, String reason) { + try { + ZipEntry errorEntry = new ZipEntry("error_" + UUID.randomUUID().toString().substring(0, 8) + ".txt"); + zos.putNextEntry(errorEntry); + String errorMsg = "读取文件失败: " + path + "\n原因: " + reason; + zos.write(errorMsg.getBytes()); + zos.closeEntry(); + } catch (IOException ignored) { + } + } + + private List mockDatabaseQuery(DayDefectRateDto dto) { + return service.getImageData(dto); + } +} diff --git a/src/main/java/com/bonus/boot/manager/manager/config/AsycTaskExecutorConfig.java b/src/main/java/com/bonus/boot/manager/manager/config/AsycTaskExecutorConfig.java index 3e9ddc4..b11f521 100644 --- a/src/main/java/com/bonus/boot/manager/manager/config/AsycTaskExecutorConfig.java +++ b/src/main/java/com/bonus/boot/manager/manager/config/AsycTaskExecutorConfig.java @@ -6,6 +6,9 @@ import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + /** * 线程池配置、启用异步 * @@ -22,4 +25,27 @@ public class AsycTaskExecutorConfig { return taskExecutor; } + + @Bean("taskExecutor2") + public Executor taskExecutor2() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + // 核心线程数 + executor.setCorePoolSize(5); + // 最大线程数 + executor.setMaxPoolSize(10); + // 队列容量 + executor.setQueueCapacity(100); + // 线程存活时间 + executor.setKeepAliveSeconds(60); + // 线程名称前缀 + executor.setThreadNamePrefix("task-executor-"); + // 拒绝策略 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 等待所有任务结束后再关闭线程池 + executor.setWaitForTasksToCompleteOnShutdown(true); + // 等待时间 + executor.setAwaitTerminationSeconds(60); + executor.initialize(); + return executor; + } } diff --git a/src/main/java/com/bonus/boot/manager/manager/controller/PermissionController.java b/src/main/java/com/bonus/boot/manager/manager/controller/PermissionController.java index 868c22a..872c80a 100644 --- a/src/main/java/com/bonus/boot/manager/manager/controller/PermissionController.java +++ b/src/main/java/com/bonus/boot/manager/manager/controller/PermissionController.java @@ -54,7 +54,7 @@ public class PermissionController { private static final Set ONLINE_IGNORE_IDS = Stream.of(58L, 60L,95L, 72L, 96L, 82L, 75L) .collect(Collectors.toSet()); - private static final Set OFFNLINE_IGNORE_IDS = Stream.of(101L, 102L) + private static final Set OFFNLINE_IGNORE_IDS = Stream.of(101L, 102L,103L) .collect(Collectors.toSet()); diff --git a/src/main/java/com/bonus/boot/manager/plan/FileCleanupTask.java b/src/main/java/com/bonus/boot/manager/plan/FileCleanupTask.java new file mode 100644 index 0000000..9767f81 --- /dev/null +++ b/src/main/java/com/bonus/boot/manager/plan/FileCleanupTask.java @@ -0,0 +1,75 @@ +package com.bonus.boot.manager.plan; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.TimeUnit; +/** + * @className:FileCleanupTask + * @author:cwchen + * @date:2025-12-30-17:07 + * @version:1.0 + * @description:文件清理任务类 + */ + + +@Slf4j +@Component +@EnableScheduling +public class FileCleanupTask { + + // 使用之前定义的临时目录路径 + private static final String TEMP_PATH = System.getProperty("java.io.tmpdir") + File.separator + "export_task" + File.separator; + + /** + * 每天凌晨 2 点执行清理任务 + * cron 表达式: 秒 分 时 日 月 周 + */ + @Scheduled(cron = "0 0 2 * * ?") + public void cleanupTempFiles() { + log.info("开始执行导出临时文件清理任务..."); + File directory = new File(TEMP_PATH); + + if (!directory.exists() || !directory.isDirectory()) { + log.info("临时目录不存在,跳过清理。"); + return; + } + + File[] files = directory.listFiles(); + if (files == null || files.length == 0) { + log.info("临时目录为空,无需清理。"); + return; + } + + long currentTimeMillis = System.currentTimeMillis(); + long twentyFourHoursMillis = TimeUnit.HOURS.toMillis(24); + int deleteCount = 0; + + for (File file : files) { + try { + // 获取文件属性中的创建时间 + BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class); + long creationTime = attr.creationTime().toMillis(); + + // 如果当前时间 - 创建时间 > 24小时,则删除 + if (currentTimeMillis - creationTime > twentyFourHoursMillis) { + if (file.delete()) { + deleteCount++; + log.debug("已删除过期临时文件: {}", file.getName()); + } else { + log.warn("无法删除文件: {}", file.getName()); + } + } + } catch (Exception e) { + log.error("处理文件时出错: {}, 错误: {}", file.getName(), e.getMessage()); + } + } + + log.info("清理任务完成,共删除 {} 个过期文件。", deleteCount); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 268e8c0..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,96 +0,0 @@ -# 服务器配置 -server: - port: 1907 - servlet: - context-path: /hfTowsBmw - session: - timeout: 10 - tomcat: - uri-encoding: UTF-8 - max-http-header-size: 102400 - -# Spring 配置 -spring: - # 数据源配置 - datasource: - url: jdbc:mysql://192.168.0.14:2009/hftows?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false - username: root - password: Bonus@admin123! - driver-class-name: com.mysql.cj.jdbc.Driver - max-idle: 10 - max-wait: 60000 - min-idle: 5 - initial-size: 5 - dynamic: - primary: mysqldb - - # Redis 配置 - redis: - host: 127.0.0.1 - port: 6379 - # password: HAY@xyksj666 - - # 文件上传配置 - servlet: - multipart: - enabled: true - max-file-size: -1 - max-request-size: -1 - - http: - multipart: - maxFileSize: 10Mb - maxRequestSize: 10Mb - - # 国际化配置 - messages: - basename: i18n.lang - encoding: UTF-8 - - # Thymeleaf 配置 - thymeleaf: - cache: false - encoding: UTF-8 - mode: HTML5 - suffix: .html - prefix: classpath:/static/ - -# MyBatis 配置 -mybatis: - mapper-locations: classpath:mappers/*/*Mapper.xml - type-aliases-package: com.bonus.boot.manager.*.entity - -# 日志配置 -log: - config: classpath:logback-boot.xml - level: - root: info - my: debug - file: logs/sys-back.log - maxsize: 30MB - -# 应用自定义配置 -token: - expire: - seconds: 360000 - -files: - path: /data/files/ - win: - path: d:/files/ - -hfTowsBmw: - aq: - enable: false - -# 登录验证码配置 -loginCode: - expiration: 3 - prefix: login_code - -# SIGAR 库路径配置 -sigar: - library-paths: - windows64: L:\\dll\\sigar-amd64-winnt.dll - windows32: L:\\dll\\sigar-x86-winnt.dll - linux64: /opt/libs/sigar/libsigar-amd64-linux.so \ No newline at end of file diff --git a/src/main/resources/i18n/lang.properties b/src/main/resources/i18n/lang.properties index cc767e1..0814b55 100644 --- a/src/main/resources/i18n/lang.properties +++ b/src/main/resources/i18n/lang.properties @@ -316,6 +316,11 @@ Angle = Angle proportion=占比 quantity=数量 roll_angle_rate=Roll/Angle比例 +secondary_analysis_picture=二次分析图片 +master_drawing=原图 +view=查看 +deleteTime=删除时间 + # laydate 语言 laydateEn = \ No newline at end of file diff --git a/src/main/resources/i18n/lang_en_US.properties b/src/main/resources/i18n/lang_en_US.properties index 9bdf58e..b54749c 100644 --- a/src/main/resources/i18n/lang_en_US.properties +++ b/src/main/resources/i18n/lang_en_US.properties @@ -322,3 +322,7 @@ roughening_num = the number of brushed hairs roughening_width = burr length roughening_width_proportion = roughening length ratio roll_angle_rate=Roll/AngleProportion +secondary_analysis_picture=secondary analysis picture +master_drawing=master drawing +view=view +deleteTime=delete time diff --git a/src/main/resources/i18n/lang_zh_CN.properties b/src/main/resources/i18n/lang_zh_CN.properties index cb72c6a..75aba3c 100644 --- a/src/main/resources/i18n/lang_zh_CN.properties +++ b/src/main/resources/i18n/lang_zh_CN.properties @@ -320,4 +320,8 @@ roughening = 拉毛 roughening_num = 拉毛个数 roughening_width = 毛边长度 roughening_width_proportion = 拉毛长度比 -roll_angle_rate=Roll/Angle比例 \ No newline at end of file +roll_angle_rate=Roll/Angle比例 +secondary_analysis_picture=二次分析图片 +master_drawing=原图 +view=查看 +deleteTime=删除时间 \ No newline at end of file diff --git a/src/main/resources/mappers/defectStatistics/DayDefectRateMapper.xml b/src/main/resources/mappers/defectStatistics/DayDefectRateMapper.xml index 60dab30..aba56fd 100644 --- a/src/main/resources/mappers/defectStatistics/DayDefectRateMapper.xml +++ b/src/main/resources/mappers/defectStatistics/DayDefectRateMapper.xml @@ -2,8 +2,15 @@ + + + INSERT INTO bm_tows_del(img_path,img_no,two_analysis_url) + VALUES ( + #{imgInitialUrl},#{imgNum},#{imgTwoAnalysisUrl} + ) + - + + + diff --git a/src/main/resources/mappers/defectStatistics/DefectDelRecordMapper.xml b/src/main/resources/mappers/defectStatistics/DefectDelRecordMapper.xml new file mode 100644 index 0000000..0b7b922 --- /dev/null +++ b/src/main/resources/mappers/defectStatistics/DefectDelRecordMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/static/js/defectStatistics/defectDelRecord.js b/src/main/resources/static/js/defectStatistics/defectDelRecord.js new file mode 100644 index 0000000..5016872 --- /dev/null +++ b/src/main/resources/static/js/defectStatistics/defectDelRecord.js @@ -0,0 +1,272 @@ +var form, layer, table, tableIns, formSelects; +// 应用类别下拉选 +var baleNumList; +var pageNum = 1, limitSize = 10; // 默认第一页,分页数量为10 +const isOnline = localStorage.getItem("online"); +layui.use(['form', 'layer', 'table', 'laydate'], function () { + + form = layui.form; + layer = layui.layer; + table = layui.table; + formSelects = layui.formSelects; + baleNumList = getBaleNumSelectList(); + setSelectValue(baleNumList, 'baleNum', package_number_select); + form.render(); + var laydate = layui.laydate; + // 日期范围 + laydate.render({ + elem: "#operateTime", + range: true, + lang: dataLang, + value:new Date().toISOString().split('T')[0] +" - "+new Date().toISOString().split('T')[0], + shortcuts: [ + { + text: last_7_days, + value: function () { + var value = []; + var date1 = new Date(); + date1.setDate(date1.getDate() - 7); + date1.setHours(0, 0, 0, 0); + value.push(date1); + var date2 = new Date(); + date2.setDate(date2.getDate() - 1); + date2.setHours(23, 59, 59, 999); + value.push(date2); + return value; + } + }, + { + text: last_30_days, + value: function () { + var value = []; + var date1 = new Date(); + date1.setDate(date1.getDate() - 30); + date1.setHours(0, 0, 0, 0); + value.push(date1); + var date2 = new Date(); + date2.setDate(date2.getDate() - 1); + date2.setHours(23, 59, 59, 999); + value.push(date2); + return value; + } + }, + { + text: last_3_months, + value: function () { + var value = []; + var date1 = new Date(); + date1.setMonth(date1.getMonth() - 3); + date1.setDate(1); + date1.setHours(0, 0, 0, 0); + value.push(date1); + var date2 = new Date(); + date2.setDate(1); + date2.setHours(0, 0, 0, 0); + date2 = date2.getTime() - 1; + value.push(new Date(date2)); + return value; + } + } + ] + }); + pages(1, 10, 1); + //重置按钮事件 + $("#reset").click(function () { + $("#operateTime").val(getNowTime() + " - " + getNowTime()); + query(); + form.render(); + }); +}); + +function pages(pageNum, pageSize, typeNum) { + var params = getReqParams(pageNum, pageSize, typeNum); + var url = dataUrl + "/defectDelRecord/getList"; + ajaxRequest(url, "POST", params, true, function () { + }, function (result) { + console.log(result); + if (result.code === 200) { + if (result.data) { + initTable(result.data, result.limit, result.curr) + laypages(result.count, result.curr, result.limit) + } + } else if (result.code === 500) { + layer.alert(result.msg, {icon: 2}) + } + }, function (xhr) { + error(xhr) + }); +} + +function laypages(total, page, limit) { + layui.use(['laypage'], function () { + var laypage = layui.laypage; + laypage.render({ + elem: 'voi-page', + count: total, + curr: page, + limit: limit, + limits: [10, 20, 50, 100, 200, 500], + layout: ['prev', 'page', 'next', 'skip', 'count', 'limit'], + groups: 5, + jump: function (obj, first) { + if (!first) { + pageNum = obj.curr, limitSize = obj.limit; + pages(obj.curr, obj.limit, null); + } + } + }); + }) +} + +/*初始化表格*/ +function initTable(dataList, limit, page) { + var loadingMsg = layer.msg(load_tip, {icon: 16, scrollbar: false, time: 0,}); + tableIns = table.render({ + elem: "#table_data", + height: "full-140", + toolbar: '#toolbarDemo', + defaultToolbar: ['filter'], //本章讲述的参数!!!!!!!!!! + data: dataList, + limit: limit, + cols: [ + setTableField(page, limit) + ], + done: function (res, curr, count) { + layer.close(loadingMsg); + table.resize("table_data"); + count || this.elem.next(".layui-table-view").find(".layui-table-header").css("display", "inline-block"); + count || this.elem.next(".layui-table-view").find(".layui-table-box").css("overflow", "auto"); + }, + }); +} + +function setTableField(page, limit) { + let cols = [ + { + title: order_number, + width: '5%', + rowspan: 2, + unresize: true, + align: "center", + style: "word-break: break-all;", + templet: function (d) { + return (page - 1) * limit + d.LAY_INDEX + 1; + } + }, + { + field: 'imgNum', width: '30%', align: "center", title: photo_number, + templet: function (d) { + var html = ''; + if (d.id == "" || d.id == "" || d.id == "null" || d.id == null) { + return ''; + } + html += "" + d.id + ""; + return html; + } + }, + { + width: '20%', align: "center", title: master_drawing, + templet: function (d) { + return ""+view+""; + } + }, + { + width: '20%', align: "center", title: secondary_analysis_picture, + templet: function (d) { + return ""+view+""; + } + }, + {field: 'delTime', width: '25%', align: "center", title: deleteTime}, + ] + return cols; +} + +/**原图 / 二次缺陷图片*/ +function getImgNum(obj, type) { + layer.photos({ + photos: { + "title": photo_details, + "start": 0, + "data": [ + { + "alt": "图片详情", + "pid": 1, + // "src": "https://unpkg.com/outeres@0.1.1/demo/layer.png" + "src": imgpathurl + (type === 1 ? obj.imgPath : obj.twoAnalysisUrl), + } + ] + }, + // --- 调整背景透明度 --- + shade: [0.3, '#000'], // 0.3 表示 30% 透明度的黑色,背景不会那么黑 + // ------------------- + maxWidth: 800, + maxHeight: 600, + anim: 5 + }); +} + +// 获取参数 +function getReqParams(page, limit, type) { + var obj = {}; + if (!type) { + obj = { + page: page + "", + limit: limit + "", + baleNum: $('#baleNum').val(), + operateTime: $('#operateTime').val(), + startDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[0] : '', + endDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[1] : '', + }; + } else { + obj = { + page: '1', + limit: '10', + keyWord: '', + operateTime: '', + startDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[0] : '', + endDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[1] : '', + }; + } + + obj = { + encryptedData: encryptCBC(JSON.stringify(obj)) + } + return obj; +} + +// 查询/重置 +function query() { + pages(1, limitSize); +} + +function reloadData() { + pages(pageNum, limitSize); +} + +function exportData() { + // 1. 模拟获取 queryParams (对应 this.queryParams) + const queryParams = { + startDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[0] : '', + endDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[1] : '', + }; + + // 3. 将参数编码 + const paramsStr = encodeURIComponent(JSON.stringify(queryParams)); + + // 4. 拼接 URL + const url = `../../pages/defectStatistics/export-progress.html?params=${paramsStr}`; + + // 5. 计算窗口居中位置 + const width = 750; + const height = 500; + const left = (window.screen.width - width) / 2; + const top = (window.screen.height - height) / 2; + + // 6. 打开新窗口 + window.open( + url, + '_blank', + `width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes` + ); +} + diff --git a/src/main/resources/static/pages/defectStatistics/defectDelRecord.html b/src/main/resources/static/pages/defectStatistics/defectDelRecord.html new file mode 100644 index 0000000..677a6f0 --- /dev/null +++ b/src/main/resources/static/pages/defectStatistics/defectDelRecord.html @@ -0,0 +1,107 @@ + + + + + Roll/Angle比例 + + + + + + + + + + + + + +
+ +
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/src/main/resources/static/pages/defectStatistics/export-progress.html b/src/main/resources/static/pages/defectStatistics/export-progress.html new file mode 100644 index 0000000..c43879a --- /dev/null +++ b/src/main/resources/static/pages/defectStatistics/export-progress.html @@ -0,0 +1,268 @@ + + + + + 导出进度 + + + + + + + +
+
+
+

导出进度

+
+
+ +
+
+ + 0% +
+
+
+
+
正在生成压缩包,请稍候...
+
+ +
+
+
压缩包生成完成!
+ +
+ +
+
+
导出失败,请稍后重试
+ +
+ +
+
+
+ + + + \ No newline at end of file