缺陷删除记录功能开发
This commit is contained in:
parent
2492d7561b
commit
5bb5940307
|
|
@ -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<DayDefectRateDto> data) {
|
||||
PageHelper.startPage(data.getData().getPage(), data.getData().getLimit());
|
||||
List<TowsDelVo> list = service.getList(data.getData());
|
||||
PageInfo<TowsDelVo> pageInfo = new PageInfo<>(list);
|
||||
return ServerResponse.createSuccessPage(pageInfo, data.getData().getPage(), data.getData().getLimit());
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. 启动导出任务
|
||||
*/
|
||||
@GetMapping("/export")
|
||||
@DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理
|
||||
public Map<String, Object> startExport(EncryptedReq<DayDefectRateDto> encryptedReq) {
|
||||
// 启动异步任务
|
||||
String taskId = exportService.createExportTask(encryptedReq.getData());
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("taskId", taskId);
|
||||
Map<String, Object> 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<String, Object> getProgress(EncryptedReq<DayDefectRateDto> encryptedReq) {
|
||||
ExportTask task = exportService.getTaskStatus(encryptedReq.getData().getTaskId());
|
||||
// 修改 404 情况
|
||||
if (task == null) {
|
||||
Map<String, Object> errorResult = new HashMap<>();
|
||||
errorResult.put("code", 404);
|
||||
errorResult.put("msg", "任务不存在");
|
||||
return errorResult;
|
||||
}
|
||||
// 修改成功情况
|
||||
Map<String, Object> successResult = new HashMap<>();
|
||||
successResult.put("code", 200);
|
||||
successResult.put("data", task);
|
||||
return successResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. 下载文件
|
||||
*/
|
||||
@GetMapping("/download")
|
||||
@DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理
|
||||
public ResponseEntity<org.springframework.core.io.Resource> downloadFile(
|
||||
EncryptedReq<DayDefectRateDto> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TowsDelVo>
|
||||
* @author cwchen
|
||||
* @date 2026/1/7 14:22
|
||||
*/
|
||||
List<TowsDelVo> getList(DayDefectRateDto dto);
|
||||
|
||||
/**
|
||||
* 导出缺陷数据查询
|
||||
* @param dto
|
||||
* @return List<ImageRecord>
|
||||
* @author cwchen
|
||||
* @date 2026/1/7 16:59
|
||||
*/
|
||||
List<ImageRecord> getImageData(DayDefectRateDto dto);
|
||||
}
|
||||
|
|
@ -18,4 +18,6 @@ public class DayDefectRateDto extends PageEntity {
|
|||
private String startDate;
|
||||
|
||||
private String endDate;
|
||||
|
||||
private String taskId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; // 服务器本地临时文件路径
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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<TowsDelVo>
|
||||
* @author cwchen
|
||||
* @date 2026/1/7 14:20
|
||||
*/
|
||||
List<TowsDelVo> getList(DayDefectRateDto data);
|
||||
|
||||
/**
|
||||
* 导出缺陷数据查询
|
||||
* @param dto
|
||||
* @return List<ImageRecord>
|
||||
* @author cwchen
|
||||
* @date 2026/1/7 16:59
|
||||
*/
|
||||
List<ImageRecord> getImageData(DayDefectRateDto dto);
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<TowsDelVo> getList(DayDefectRateDto dto) {
|
||||
try {
|
||||
List<TowsDelVo> list = defectDelRecordDao.getList(dto);
|
||||
return list == null ? Collections.<TowsDelVo>emptyList() : list;
|
||||
} catch (Exception e) {
|
||||
log.error(e.toString(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ImageRecord> getImageData(DayDefectRateDto dto) {
|
||||
try {
|
||||
List<ImageRecord> list = defectDelRecordDao.getImageData(dto);
|
||||
return list == null ? Collections.<ImageRecord>emptyList() : list;
|
||||
} catch (Exception e) {
|
||||
log.error(e.toString(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, Object> 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<ImageRecord> allImages = mockDatabaseQuery(dto);
|
||||
|
||||
if (allImages.isEmpty()) {
|
||||
throw new RuntimeException("没有查询到数据");
|
||||
}
|
||||
|
||||
Map<String, List<ImageRecord>> 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<String, List<ImageRecord>> entry : groupedImages.entrySet()) {
|
||||
String date = entry.getKey();
|
||||
List<ImageRecord> 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<ImageRecord> 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<ImageRecord> mockDatabaseQuery(DayDefectRateDto dto) {
|
||||
return service.getImageData(dto);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class PermissionController {
|
|||
private static final Set<Long> ONLINE_IGNORE_IDS = Stream.of(58L, 60L,95L, 72L, 96L, 82L, 75L)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
private static final Set<Long> OFFNLINE_IGNORE_IDS = Stream.of(101L, 102L)
|
||||
private static final Set<Long> OFFNLINE_IGNORE_IDS = Stream.of(101L, 102L,103L)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -316,6 +316,11 @@ Angle = Angle
|
|||
proportion=占比
|
||||
quantity=数量
|
||||
roll_angle_rate=Roll/Angle比例
|
||||
secondary_analysis_picture=二次分析图片
|
||||
master_drawing=原图
|
||||
view=查看
|
||||
deleteTime=删除时间
|
||||
|
||||
|
||||
# laydate 语言
|
||||
laydateEn =
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -321,3 +321,7 @@ roughening_num = 拉毛个数
|
|||
roughening_width = 毛边长度
|
||||
roughening_width_proportion = 拉毛长度比
|
||||
roll_angle_rate=Roll/Angle比例
|
||||
secondary_analysis_picture=二次分析图片
|
||||
master_drawing=原图
|
||||
view=查看
|
||||
deleteTime=删除时间
|
||||
|
|
@ -2,8 +2,15 @@
|
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bonus.boot.manager.defectStatistics.dao.IDayDefectRateDao">
|
||||
<!--记录删除的图片-->
|
||||
<insert id="addTowsDelData">
|
||||
INSERT INTO bm_tows_del(img_path,img_no,two_analysis_url)
|
||||
VALUES (
|
||||
#{imgInitialUrl},#{imgNum},#{imgTwoAnalysisUrl}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!--每日缺陷率统计-->
|
||||
<!--每日缺陷率统计-->
|
||||
<select id="getList" resultType="com.bonus.boot.manager.defectStatistics.entity.DayDefectRateVo">
|
||||
SELECT
|
||||
create_date AS createDate,
|
||||
|
|
@ -29,6 +36,13 @@
|
|||
AND is_active = '1'
|
||||
GROUP BY DATE_FORMAT(createTime,'%Y-%m-%d')
|
||||
</select>
|
||||
<!--获取缺陷详情-->
|
||||
<select id="getDefectDetail" resultType="com.bonus.boot.manager.defectStatistics.entity.ComprehensiveVo">
|
||||
SELECT img_initial_url AS imgInitialUrl,
|
||||
img_num AS imgNum,
|
||||
img_two_analysis_url AS imgTwoAnalysisUrl
|
||||
FROM bm_tows WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<!--删除缺陷图片-->
|
||||
<update id="delTowsData">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bonus.boot.manager.defectStatistics.dao.IDefectDelRecordDao">
|
||||
|
||||
<!--缺陷删除记录-->
|
||||
<select id="getList" resultType="com.bonus.boot.manager.defectStatistics.entity.TowsDelVo">
|
||||
SELECT
|
||||
id,
|
||||
img_path AS imgPath,
|
||||
del_time AS delTime,
|
||||
img_no AS imgNo,
|
||||
two_analysis_url AS twoAnalysisUrl
|
||||
FROM bm_tows_del
|
||||
<where>
|
||||
<if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
|
||||
AND del_time BETWEEN CONCAT(#{startDate}, ' 00:00:00') AND CONCAT(#{endDate},' 23:59:59')
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY del_time DESC
|
||||
</select>
|
||||
<!--导出缺陷数据查询-->
|
||||
<select id="getImageData" resultType="com.bonus.boot.manager.defectStatistics.entity.ImageRecord">
|
||||
SELECT
|
||||
img_path AS url,
|
||||
del_time AS createTime
|
||||
FROM bm_tows_del
|
||||
<where>
|
||||
<if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
|
||||
AND del_time BETWEEN CONCAT(#{startDate}, ' 00:00:00') AND CONCAT(#{endDate},' 23:59:59')
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY del_time DESC
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
@ -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 += "<a style='color: #3E689A;font-weight: bold;' onclick='getImgNum2(" + JSON.stringify(d) + ")'>" + d.id + "</a>";
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
width: '20%', align: "center", title: master_drawing,
|
||||
templet: function (d) {
|
||||
return "<a style='color: #3E689A;font-weight: bold;' onclick='getImgNum("+JSON.stringify(d)+")'>"+view+"</a>";
|
||||
}
|
||||
},
|
||||
{
|
||||
width: '20%', align: "center", title: secondary_analysis_picture,
|
||||
templet: function (d) {
|
||||
return "<a style='color: #3E689A;font-weight: bold;' onclick='getImgNum("+JSON.stringify(d)+")'>"+view+"</a>";
|
||||
}
|
||||
},
|
||||
{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`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Roll/Angle比例</title>
|
||||
<link rel="stylesheet" href="../../js/layui-v2.8.17/css/layui.css">
|
||||
<link rel="stylesheet" href="../../css/table-common2.css">
|
||||
<link rel="stylesheet" href="../../js/layui-v2.6.8/formSelects-v4.css">
|
||||
<script src="../../js/libs/jquery-3.6.0.min.js" charset="UTF-8" type="text/javascript"></script>
|
||||
<script src="../../js/layui-v2.8.17/layui.js" charset="UTF-8" type="text/javascript"></script>
|
||||
<script src="../../js/publicJs.js"></script>
|
||||
<script src="../../js/aes.js"></script>
|
||||
<script src="../../js/ajaxRequest.js"></script>
|
||||
<script src="../../js/select.js"></script>
|
||||
<script src="../../js/commonUtils.js"></script>
|
||||
<script src="../../js/openIframe.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<div class="basic-search-box layout">
|
||||
<form class="layui-form basic-form" onclick="return false;">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline" style="padding: 0 0 0 10px;">
|
||||
<div class="layui-input-inline baleNum" style="display: none">
|
||||
<select class="layui-select" id="baleNum" lay-search ></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" class="layui-input" id="operateTime" readonly th:placeholder="#{date_prompt}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline btns">
|
||||
<button type="button" class="layui-btn layui-btn-normal layui-btn-sm btn-1" onclick="query(1)">[[#{query_button}]]
|
||||
</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm btn-4" id="reset">[[#{resetting_button}]]</button>
|
||||
<button id="exportBt" class="layui-btn" style="width: 80px" onclick="exportData()">
|
||||
[[#{export_button}]]
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-box" table-responsive style="z-index: 1;">
|
||||
<table id="table_data" class="table" lay-filter="table_data">
|
||||
|
||||
</table>
|
||||
<div id="voi-page" class="layout"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../js/defectStatistics/defectDelRecord.js" charset="UTF-8" type="text/javascript"></script>
|
||||
<script type="text/javascript" th:inline="javascript">
|
||||
let package_number_select =[[#{package_number_select}]];
|
||||
let last_7_days =[[#{last_7_days}]];
|
||||
let last_30_days =[[#{last_30_days}]];
|
||||
let last_3_months =[[#{last_3_months}]];
|
||||
|
||||
let load_tip =[[#{load_tip}]];
|
||||
let package_details =[[#{package_details}]];
|
||||
let export_data_tip =[[#{export_data_tip}]];
|
||||
let export_error_tip =[[#{export_error_tip}]];
|
||||
|
||||
let order_number =[[#{order_number}]];//序号
|
||||
let roll_Angle_ratio_excel =[[#{roll_Angle_ratio_excel}]];//Roll/Angle比例.xlsx
|
||||
|
||||
|
||||
let photo_details=[[#{photo_details}]];//照片详情
|
||||
|
||||
let basic_information=[[#{basic_information}]];//基本信息
|
||||
let packet_number=[[#{packet_number}]];//包号
|
||||
let specifications=[[#{specifications}]];//规格
|
||||
let test_date=[[#{test_date}]];//检测日期
|
||||
let number_of_defects=[[#{number_of_defects}]];//缺陷数量
|
||||
let photo_number=[[#{photo_number}]];//照片编号
|
||||
let detection_area_size=[[#{detection_area_size}]];//检测面积大小
|
||||
|
||||
let mean_curl_ratio=[[#{mean_curl_ratio}]];//卷曲比例均值
|
||||
let maximum_curl_ratio=[[#{maximum_curl_ratio}]];//卷曲比例最大值
|
||||
let minimum_curl_ratio=[[#{minimum_curl_ratio}]];//卷曲比例最小值
|
||||
let number_of_bristles=[[#{number_of_bristles}]];//拉毛个数
|
||||
let burr_length=[[#{burr_length}]];//毛边长度
|
||||
let betection_length=[[#{betection_length}]];//检测长度
|
||||
let roughing_length_ratio=[[#{roughing_length_ratio}]];//拉毛长度比
|
||||
let polluted_colo=[[#{polluted_colo}]];//污染颜色
|
||||
let polluted_area=[[#{polluted_area}]];//污染面积
|
||||
let Proportion_of_pollution_area=[[#{Proportion_of_pollution_area}]];//污染面积占比
|
||||
let number_of_cuts=[[#{number_of_cuts}]];//切断个数
|
||||
let cut_off_area=[[#{cut_off_area}]];//切断面积
|
||||
let proportion_of_cutting_area=[[#{proportion_of_cutting_area}]];//切断面积占比
|
||||
let roll_angle_rate=[[#{roll_angle_rate}]];//rollAngle占比
|
||||
|
||||
|
||||
let operate =[[#{operate}]]; // 操作
|
||||
let delete_button =[[#{delete_button}]]; // 删除
|
||||
let delete_tip =[[#{delete_tip}]]; // 删除提示
|
||||
let message =[[#{message}]];
|
||||
let confirm =[[#{confirm}]];
|
||||
let cancel =[[#{cancel}]];
|
||||
let delete_tip2 =[[#{delete_tip2}]];
|
||||
|
||||
let secondary_analysis_picture =[[#{secondary_analysis_picture}]];
|
||||
let master_drawing =[[#{master_drawing}]];
|
||||
let view =[[#{view}]];
|
||||
let deleteTime =[[#{deleteTime}]];
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>导出进度</title>
|
||||
<script src="../../js/publicJs.js"></script>
|
||||
<script src="../../js/libs/jquery-3.6.0.min.js" charset="UTF-8" type="text/javascript"></script>
|
||||
<script src="../../js/aes.js"></script>
|
||||
<style>
|
||||
/* 基础样式模拟 */
|
||||
body { margin: 0; font-family: "Helvetica Neue", Helvetica, "PingFang SC", sans-serif; }
|
||||
.export-progress-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%);
|
||||
padding: 20px;
|
||||
}
|
||||
.progress-card {
|
||||
width: 500px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.card-header .title { font-size: 18px; font-weight: 600; color: #333; margin: 0; }
|
||||
.card-body { padding: 40px 24px; text-align: center; }
|
||||
|
||||
/* 进度条样式 */
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
background-color: #ebeef5;
|
||||
border-radius: 100px;
|
||||
margin: 20px 0;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
background-color: #1f72ea;
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #dcdfe6;
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #1f72ea;
|
||||
color: #fff;
|
||||
border: none;
|
||||
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
|
||||
}
|
||||
.btn-primary:hover { background: #4a8bff; }
|
||||
|
||||
/* 状态控制 */
|
||||
.status-section { display: none; }
|
||||
.active { display: block; }
|
||||
|
||||
/* 动画模拟 Element Icon */
|
||||
.loading-icon { display: inline-block; width: 20px; height: 20px; border: 2px solid #1f72ea; border-top-color: transparent; border-radius: 50%; animation: spin 1s linear infinite; vertical-align: middle; margin-right: 10px;}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.error-text { color: #f56c6c; margin-bottom: 20px; }
|
||||
.success-text { color: #333; margin-bottom: 20px; font-weight: 500; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="export-progress-container">
|
||||
<div class="progress-card">
|
||||
<div class="card-header">
|
||||
<h3 class="title">导出进度</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div id="section-progress" class="status-section active">
|
||||
<div class="progress-info">
|
||||
<span class="loading-icon"></span>
|
||||
<span id="progress-text">0%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div id="progress-fill" class="progress-bar-fill"></div>
|
||||
</div>
|
||||
<div class="progress-detail">正在生成压缩包,请稍候...</div>
|
||||
</div>
|
||||
|
||||
<div id="section-completed" class="status-section">
|
||||
<div style="font-size: 50px; color: #67c23a; margin-bottom: 15px;">✔</div>
|
||||
<div class="success-text">压缩包生成完成!</div>
|
||||
<button id="btn-download" class="btn btn-primary">下载文件</button>
|
||||
</div>
|
||||
|
||||
<div id="section-error" class="status-section">
|
||||
<div style="font-size: 50px; color: #f56c6c; margin-bottom: 15px;">✘</div>
|
||||
<div id="error-message" class="error-text">导出失败,请稍后重试</div>
|
||||
<button id="btn-close" class="btn">关闭窗口</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// --- 数据定义 ---
|
||||
let state = {
|
||||
taskId: null,
|
||||
downloadUrl: null,
|
||||
fileName: null,
|
||||
params: {},
|
||||
pollTimer: null
|
||||
};
|
||||
|
||||
// --- 初始化 ---
|
||||
init();
|
||||
|
||||
function init() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const paramsStr = urlParams.get('params');
|
||||
if (paramsStr) {
|
||||
try {
|
||||
state.params = JSON.parse(decodeURIComponent(paramsStr));
|
||||
startExport();
|
||||
} catch (e) {
|
||||
showError('参数解析失败');
|
||||
}
|
||||
} else {
|
||||
showError('缺少导出参数');
|
||||
}
|
||||
}
|
||||
|
||||
// --- 方法逻辑 ---
|
||||
|
||||
function startExport() {
|
||||
const params = {
|
||||
encryptedData: encryptCBC(JSON.stringify(state.params))
|
||||
}
|
||||
$.ajax({
|
||||
url: dataUrl + '/defectDelRecord/export',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"token": tokens
|
||||
},
|
||||
data: params,
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
if (res.code === 200) {
|
||||
// 兼容多种返回格式
|
||||
state.taskId = res.data?.taskId || res.data || res.taskId;
|
||||
if (state.taskId) {
|
||||
startPolling();
|
||||
} else if (res.data?.downloadUrl) {
|
||||
showCompleted(res.data.downloadUrl, res.data.fileName);
|
||||
} else {
|
||||
showError('未获取到任务ID');
|
||||
}
|
||||
} else {
|
||||
showError(res.msg || '启动导出任务失败');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
showError('网络请求失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
checkProgress(); // 立即执行一次
|
||||
state.pollTimer = setInterval(checkProgress, 2000);
|
||||
}
|
||||
|
||||
function checkProgress() {
|
||||
|
||||
if (!state.taskId) return;
|
||||
const params = {
|
||||
encryptedData: encryptCBC(JSON.stringify({
|
||||
taskId: state.taskId
|
||||
}))
|
||||
}
|
||||
$.ajax({
|
||||
url: dataUrl + '/defectDelRecord/progress', // 替换为真实URL
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"token": tokens
|
||||
},
|
||||
// data: { taskId: state.taskId },
|
||||
data: params,
|
||||
success: function(res) {
|
||||
if (res.code === 200) {
|
||||
const data = res.data;
|
||||
updateProgressBar(data.progress || 0);
|
||||
|
||||
if (data.progress >= 100 || data.status === 'completed' || data.status === 'success') {
|
||||
stopPolling();
|
||||
showCompleted(data.downloadUrl, data.fileName);
|
||||
} else if (data.status === 'failed' || data.status === 'error') {
|
||||
stopPolling();
|
||||
showError(data.message || '导出任务失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateProgressBar(percent) {
|
||||
$('#progress-text').text(percent + '%');
|
||||
$('#progress-fill').css('width', percent + '%');
|
||||
}
|
||||
|
||||
function showCompleted(url, name) {
|
||||
state.downloadUrl = url;
|
||||
state.fileName = name || `export_${Date.now()}.zip`;
|
||||
switchSection('completed');
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
$('#error-message').text(msg);
|
||||
switchSection('error');
|
||||
}
|
||||
|
||||
function switchSection(type) {
|
||||
$('.status-section').removeClass('active');
|
||||
$(`#section-${type}`).addClass('active');
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (state.pollTimer) {
|
||||
clearInterval(state.pollTimer);
|
||||
state.pollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 事件绑定 ---
|
||||
|
||||
$('#btn-download').on('click', function() {
|
||||
if (state.downloadUrl && state.downloadUrl.startsWith('http')) {
|
||||
window.open(state.downloadUrl, '_blank');
|
||||
} else {
|
||||
// 这里可以实现类似 Blob 下载逻辑,jQuery 需配合原生 fetch 或 xhr
|
||||
const params = encryptCBC(JSON.stringify({
|
||||
taskId: state.taskId
|
||||
}))
|
||||
window.location.href = dataUrl + `/defectDelRecord/download?encryptedData=${params}&token=${tokens}`;
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn-close').on('click', function() {
|
||||
window.close();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue