diff --git a/src/main/java/com/xujie/sys/modules/pms/controller/QcReportController.java b/src/main/java/com/xujie/sys/modules/pms/controller/QcReportController.java index 80bfbc3..2a2d7f3 100644 --- a/src/main/java/com/xujie/sys/modules/pms/controller/QcReportController.java +++ b/src/main/java/com/xujie/sys/modules/pms/controller/QcReportController.java @@ -6,10 +6,7 @@ import com.xujie.sys.modules.pms.data.QcMethodData; import com.xujie.sys.modules.pms.data.QcReportData; import com.xujie.sys.modules.pms.service.QcReportService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletResponse; import java.io.IOException; diff --git a/src/main/java/com/xujie/sys/modules/pms/mapper/QcReportMapper.java b/src/main/java/com/xujie/sys/modules/pms/mapper/QcReportMapper.java index ac4781c..322a31b 100644 --- a/src/main/java/com/xujie/sys/modules/pms/mapper/QcReportMapper.java +++ b/src/main/java/com/xujie/sys/modules/pms/mapper/QcReportMapper.java @@ -36,4 +36,10 @@ public interface QcReportMapper { IPage getOQCReportCount(Page qcReportDataPage, @Param("query") QcReportData data); List downloadOQCRecord(QcReportData data); + + // 优化方法:使用OFFSET分页,避免PageHelper开销 + List downloadIPQCRecordWithOffset(Page qcReportDataPage,@Param("query")QcReportData data); + + // 优化方法:仅查询总数,不返回具体数据 + int getIPQCReportCountOptimized(@Param("query") QcReportData data); } diff --git a/src/main/java/com/xujie/sys/modules/pms/service/Impl/QcReportServiceImpl.java b/src/main/java/com/xujie/sys/modules/pms/service/Impl/QcReportServiceImpl.java index c25ef93..9ec02fe 100644 --- a/src/main/java/com/xujie/sys/modules/pms/service/Impl/QcReportServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/pms/service/Impl/QcReportServiceImpl.java @@ -20,10 +20,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.*; @Service @@ -124,59 +121,108 @@ public class QcReportServiceImpl implements QcReportService { @Override public void downloadQcRecordMillion(HttpServletResponse response, QcReportData data) throws Exception { ServletOutputStream out = null; + ExcelWriter writer = null; + ExecutorService executor = null; + try { + // 1. 提前设置响应头(必须在获取输出流前!) + String fileName = URLEncoder.encode("QC报表", "UTF-8"); + response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx"); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + + // 2. 获取输出流 out = response.getOutputStream(); - // 设置EXCEL名称 - String fileName = URLEncoder.encode("QC报表", "UTF-8"); + // 3. 校验下载类型 + if (!"ipqc".equals(data.getDownloadType())) { + throw new IllegalArgumentException("不支持的下载类型: " + data.getDownloadType()); + } - // 设置SHEET名称 - WriteSheet sheet = new WriteSheet(); - sheet.setSheetName("明细列表sheet1"); + // 4. 查询总数 + int totalRowCount = this.qcReportMapper.getIPQCReportCountOptimized(data); + if (totalRowCount <= 0) { + // 处理空数据:写入空Excel(避免客户端收到损坏文件) + writer = EasyExcel.write(out, QcReportIPQCData.class).autoCloseStream(false).build(); + WriteSheet sheet = EasyExcel.writerSheet("检验单明细").build(); + writer.write(Collections.emptyList(), sheet); + writer.finish(); + out.flush(); + return; + } - int totalRowCount = 0; - // 查询总数并封装相关变量 - if ("ipqc".equals(data.getDownloadType())) { - IPage list = this.qcReportMapper.getIPQCReportCount(new Page(data.getPage(), data.getLimit()), data); - totalRowCount = (int)list.getTotal(); - int pageSize = ExcelConstant.PER_WRITE_ROW_COUNT; - int writeCount = totalRowCount % pageSize == 0 ? (totalRowCount / pageSize) : (totalRowCount / pageSize + 1); - // 1. 循环外创建线程池 - ExecutorService executor = Executors.newFixedThreadPool(Math.min(writeCount, 10)); - List>> futureTaskList = new ArrayList<>(); - - for (int i = 0; i < writeCount; i++) { - int pageNum = i + 1; // 子线程中使用的页码 - FutureTask> futureTask = new FutureTask<>(() -> { - PageHelper.startPage(pageNum, pageSize); - return this.qcReportMapper.downloadIPQCRecord(data); - }); - - executor.execute(futureTask); - futureTaskList.add(futureTask); - } + // 5. 分页参数 + int pageSize = ExcelConstant.PER_WRITE_ROW_COUNT; + int writeCount = (totalRowCount + pageSize - 1) / pageSize; // 优化分页计算 - executor.shutdown(); - executor.awaitTermination(1, TimeUnit.HOURS); + // 6. 线程池(明确配置,避免资源泄漏) + executor = new ThreadPoolExecutor( + Math.min(10, Runtime.getRuntime().availableProcessors() + 1), + 10, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(100), + new ThreadPoolExecutor.CallerRunsPolicy() + ); - ExcelWriter writer = EasyExcel.write(out, QcReportIPQCData.class).build(); - sheet = EasyExcel.writerSheet("检验单明细").build(); - for (FutureTask> task : futureTaskList) { - List dataList = task.get(); // 获取单批数据 - writer.write(dataList, sheet); // 分批写入 - } - writer.finish(); + List>> futureList = new ArrayList<>(); + for (int i = 0; i < writeCount; i++) { + int pageNum = i + 1; + futureList.add(executor.submit(() -> + this.qcReportMapper.downloadIPQCRecordWithOffset(new Page<>(pageNum, pageSize), data) + )); + } + // 7. 关闭线程池并等待 + executor.shutdown(); + if (!executor.awaitTermination(1, TimeUnit.HOURS)) { + executor.shutdownNow(); // 超时强制关闭 } - // 下载EXCEL - response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx"); - response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - response.setCharacterEncoding("utf-8"); + // 8. 写入Excel(禁用自动关闭流) + writer = EasyExcel.write(out, QcReportIPQCData.class) + .autoCloseStream(false) // 手动控制流关闭 + .build(); + WriteSheet sheet = EasyExcel.writerSheet("检验单明细").build(); + + // 9. 处理子线程结果(捕获异常) + for (Future> future : futureList) { + try { + List dataList = future.get(30, TimeUnit.SECONDS); // 单个任务超时 + if (dataList != null) { + writer.write(dataList, sheet); + } + } catch (ExecutionException e) { + // 子线程异常(如SQL错误) + throw new RuntimeException("分页查询失败: " + e.getCause().getMessage(), e.getCause()); + } catch (TimeoutException e) { + throw new RuntimeException("分页查询超时", e); + } + } + + writer.finish(); out.flush(); + + } catch (Exception e) { + // 增强异常信息,便于排查 + throw new RuntimeException("下载QC报表失败: " + e.getMessage(), e); } finally { + // 10. 按顺序关闭资源(先关闭writer,再关闭流) + if (writer != null) { + try { + writer.finish(); // 确保最后关闭writer + } catch (Exception e) { + e.printStackTrace(); + } + } if (out != null) { - out.close(); + try { + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (executor != null && !executor.isTerminated()) { + executor.shutdownNow(); } } } diff --git a/src/main/resources/mapper/pms/QcReportMapper.xml b/src/main/resources/mapper/pms/QcReportMapper.xml index fb2ddc9..b28aae5 100644 --- a/src/main/resources/mapper/pms/QcReportMapper.xml +++ b/src/main/resources/mapper/pms/QcReportMapper.xml @@ -1347,4 +1347,189 @@ END, a.create_date desc + + + + + +