From 16f385fb5bd03c42ea40ad80ab679b41fa06106b Mon Sep 17 00:00:00 2001 From: hayu <1604366271@qq.com> Date: Wed, 10 Dec 2025 19:20:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=A5=E5=91=8A=E6=9F=A5=E8=AF=A2=E4=B8=80?= =?UTF-8?q?=E9=94=AE=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../basic/controller/BmReportController.java | 433 ++++-------------- .../bonus/material/common/utils/DocxUtil.java | 410 +++++------------ 2 files changed, 195 insertions(+), 648 deletions(-) diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/basic/controller/BmReportController.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/basic/controller/BmReportController.java index 4a0d3d13..cef9c6f5 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/basic/controller/BmReportController.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/basic/controller/BmReportController.java @@ -4,11 +4,11 @@ import cn.hutool.core.convert.Convert; import com.alibaba.nacos.common.utils.CollectionUtils; import com.bonus.common.biz.config.ListPagingUtil; import com.bonus.common.biz.utils.StringHelper; +import com.bonus.common.core.utils.DateUtils; import com.bonus.common.core.utils.ServletUtils; import com.bonus.common.core.utils.poi.ExcelUtil; import com.bonus.common.core.web.controller.BaseController; import com.bonus.common.core.web.domain.AjaxResult; -import com.bonus.material.basic.domain.dto.DownloadProgress; import com.bonus.material.basic.domain.report.*; import com.bonus.material.basic.service.BmReportService; import com.bonus.material.common.utils.DocxUtil; @@ -17,15 +17,8 @@ import com.bonus.material.part.domain.PartTypeCheckInfo; import com.bonus.material.part.domain.PartTypeQueryDto; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import org.apache.commons.compress.archivers.zip.Zip64Mode; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; -import org.apache.http.HttpEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.util.EntityUtils; import org.apache.poi.util.IOUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -34,18 +27,15 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; import java.net.URLEncoder; import java.time.LocalDate; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; -import java.util.zip.Deflater; +import java.util.LinkedHashMap; import static com.bonus.common.core.web.page.TableSupport.PAGE_NUM; import static com.bonus.common.core.web.page.TableSupport.PAGE_SIZE; @@ -62,9 +52,6 @@ public class BmReportController extends BaseController { @Resource private BmReportService bmReportService; - // 存储下载进度 - private static final Map downloadProgressMap = new ConcurrentHashMap<>(); - /** * 新购入库报表查询 * @param bean @@ -621,144 +608,67 @@ public class BmReportController extends BaseController { handleDownload(request, response); } - - - /** - * 流式批量下载接口 - */ - @PostMapping("/downloadBulkStream") - public void downloadBulkStream(@RequestBody DownloadRequest request, - HttpServletResponse response) throws IOException { - -// String taskId = UUID.randomUUID().toString(); - String taskId = request.getTaskId() != null ? request.getTaskId() : UUID.randomUUID().toString(); + @ApiOperation("报告一键下载") + @PostMapping("/downloadBulk") + public void downloadBulk(@RequestBody DownloadRequest request, HttpServletResponse response) throws IOException { String zipName = request.getZipName() != null ? request.getZipName() : "报告下载_" + LocalDate.now(); + String encoded = URLEncoder.encode(zipName + ".zip", "UTF-8"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); - // 初始化进度 - DownloadProgress progress = new DownloadProgress(); - progress.setTotalFiles(calculateTotalFiles(request.getItems())); - progress.setStatus("processing"); - progress.setStartTime(System.currentTimeMillis()); - downloadProgressMap.put(taskId, progress); + try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(response.getOutputStream())) { - try { - String encoded = java.net.URLEncoder.encode(zipName + ".zip", "UTF-8"); - response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded); - response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + // 按工程 + 领用日期分组 + Map>> grouped = request.getItems().stream() + .collect(Collectors.groupingBy( + item -> sanitize(item.getProName()), // 工程 + LinkedHashMap::new, + Collectors.groupingBy(item -> sanitize(item.getTestTime()), LinkedHashMap::new, Collectors.toList()) // 日期 + )); - // 设置流式传输相关头部 - response.setHeader("Transfer-Encoding", "chunked"); - response.setHeader("X-Content-Type-Options", "nosniff"); + for (Map.Entry>> projectEntry : grouped.entrySet()) { + String projectFolder = projectEntry.getKey(); + Map> dateMap = projectEntry.getValue(); - // 使用低压缩级别提高速度 - try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(response.getOutputStream())) { - zipOut.setLevel(Deflater.BEST_SPEED); - zipOut.setEncoding("UTF-8"); - zipOut.setUseZip64(Zip64Mode.AsNeeded); + for (Map.Entry> dateEntry : dateMap.entrySet()) { + String dateFolder = dateEntry.getKey(); + List items = dateEntry.getValue(); + String baseDatePath = projectFolder + "/" + dateFolder + "/"; - // 按工程 + 领用日期分组 - Map>> grouped = - request.getItems().stream() - .collect(Collectors.groupingBy( - item -> sanitize(item.getProName()), - LinkedHashMap::new, - Collectors.groupingBy( - item -> sanitize(item.getTestTime()), - LinkedHashMap::new, - Collectors.toList() - ) - )); + // 1. 生成合并的出库检验报告 + byte[] mergedReport = DocxUtil.generateReportByList(items); + addToZip(zipOut, baseDatePath + "出库检验报告.pdf", mergedReport); - AtomicInteger fileCount = new AtomicInteger(0); + // 2. 遍历每个类型-规格,创建文件夹并添加附加文件 + Map> typeMap = items.stream() + .collect(Collectors.groupingBy(item -> sanitize(item.getTypeName() + "-" + item.getTypeModelName()))); - for (Map.Entry>> projectEntry : grouped.entrySet()) { - String projectFolder = projectEntry.getKey(); - Map> dateMap = projectEntry.getValue(); + for (Map.Entry> typeEntry : typeMap.entrySet()) { + String typeFolder = typeEntry.getKey(); + String typePath = baseDatePath + typeFolder + "/"; - for (Map.Entry> dateEntry : dateMap.entrySet()) { - String dateFolder = dateEntry.getKey(); - List items = dateEntry.getValue(); - String baseDatePath = projectFolder + "/" + dateFolder + "/"; + // **先创建空文件夹** + ZipArchiveEntry folderEntry = new ZipArchiveEntry(typePath); + zipOut.putArchiveEntry(folderEntry); + zipOut.closeArchiveEntry(); - // 1. 生成合并的出库检验报告(使用新的分页方法) - progress.setCurrentFileName("正在生成出库检验报告..."); - progress.setCurrentFile(fileCount.incrementAndGet()); - updateProgress(taskId, progress); - - byte[] mergedReport = DocxUtil.generateReportByList(items); - if (mergedReport != null && mergedReport.length > 0) { - addToZipStream(zipOut, baseDatePath + "出库检验报告.pdf", mergedReport); - zipOut.flush(); - response.flushBuffer(); - } - - // 2. 按类型-规格分组处理附加文件 - Map> typeMap = items.stream() - .collect(Collectors.groupingBy( - item -> sanitize(item.getTypeName() + "-" + item.getTypeModelName()), - LinkedHashMap::new, - Collectors.toList() - )); - - for (Map.Entry> typeEntry : typeMap.entrySet()) { - String typeFolder = typeEntry.getKey(); - String typePath = baseDatePath + typeFolder + "/"; - - // 创建文件夹 - ZipArchiveEntry folderEntry = new ZipArchiveEntry(typePath); - zipOut.putArchiveEntry(folderEntry); - zipOut.closeArchiveEntry(); - - // 处理每个项目的附加文件 - for (DownloadRequest.ItemInfo item : typeEntry.getValue()) { - String maCode = StringHelper.isEmpty(item.getMaCode()) ? "" : "_" + item.getMaCode(); - - // 使用流式方式下载和添加文件 - processAndAddFile(zipOut, typePath, "合格证" + maCode, - item.getQualifiedUrl(), taskId, progress, fileCount); - processAndAddFile(zipOut, typePath, "型式试验报告" + maCode, - item.getTestReportUrl(), taskId, progress, fileCount); - processAndAddFile(zipOut, typePath, "第三方检测报告" + maCode, - item.getThirdReportUrl(), taskId, progress, fileCount); - processAndAddFile(zipOut, typePath, "出厂检测报告" + maCode, - item.getFactoryReportUrl(), taskId, progress, fileCount); - processAndAddFile(zipOut, typePath, "其他文件" + maCode, - item.getOtherReportUrl(), taskId, progress, fileCount); - - // 定期刷新缓冲区,避免内存占用过高 - if (fileCount.get() % 10 == 0) { - zipOut.flush(); - response.flushBuffer(); - } + // 再添加附加文件(如果存在) + for (DownloadRequest.ItemInfo item : typeEntry.getValue()) { + String maCode=""; + if (!StringHelper.isEmpty(item.getMaCode())){ + maCode="_"+item.getMaCode(); } + addFileIfExists(zipOut, typePath, "合格证"+maCode, item.getQualifiedUrl()); + addFileIfExists(zipOut, typePath, "型式试验报告"+maCode, item.getTestReportUrl()); + addFileIfExists(zipOut, typePath, "第三方检测报告"+maCode, item.getThirdReportUrl()); + addFileIfExists(zipOut, typePath, "出厂检测报告"+maCode, item.getFactoryReportUrl()); + addFileIfExists(zipOut, typePath, "其他文件"+maCode, item.getOtherReportUrl()); } } } - - // 更新进度为完成 - progress.setStatus("completed"); - progress.setPercentage(100); - progress.setEndTime(System.currentTimeMillis()); - progress.setCurrentFileName("下载完成"); - updateProgress(taskId, progress); - - zipOut.finish(); - - } catch (IOException e) { - progress.setStatus("error"); - progress.setErrorMessage(e.getMessage()); - updateProgress(taskId, progress); - throw e; } - } finally { - // 清理进度信息(延迟清理,以便前端可以获取最终状态) - new Timer().schedule(new TimerTask() { - @Override - public void run() { - downloadProgressMap.remove(taskId); - } - }, 300000); // 5分钟后清理 + zipOut.finish(); } } @@ -804,7 +714,7 @@ public class BmReportController extends BaseController { return; } try { - byte[] bytes = downloadFile(url); + byte[] bytes = HttpFileUtil.downloadFile(url); if (bytes != null) { addToZip(zipOut, base + name + getFileExtension(url), bytes); } @@ -813,23 +723,7 @@ public class BmReportController extends BaseController { } } - public static byte[] downloadFile(String url) throws IOException { - try (CloseableHttpClient client = HttpClients.createDefault()) { - HttpGet get = new HttpGet(url); - try (CloseableHttpResponse response = client.execute(get)) { - HttpEntity entity = response.getEntity(); - return entity != null ? EntityUtils.toByteArray(entity) : null; - } - } - } - - /** - * 辅助方法 - */ private void addToZip(ZipArchiveOutputStream zipOut, String path, byte[] bytes) throws IOException { - if (bytes == null || bytes.length == 0) { - return; - } ZipArchiveEntry entry = new ZipArchiveEntry(path); zipOut.putArchiveEntry(entry); try (ByteArrayInputStream in = new ByteArrayInputStream(bytes)) { @@ -838,194 +732,49 @@ public class BmReportController extends BaseController { zipOut.closeArchiveEntry(); } - - /** - * 处理并添加文件到ZIP(流式方式) - */ - private void processAndAddFile(ZipArchiveOutputStream zipOut, String path, - String name, String url, String taskId, - DownloadProgress progress, AtomicInteger fileCount) throws IOException { - - if (url == null || url.isEmpty()) { - return; + private String sanitize(String name) { + if (name == null) { + return "未知"; } - - try { - // 更新进度 - progress.setCurrentFileName("正在下载: " + name); - progress.setCurrentFile(fileCount.incrementAndGet()); - updateProgress(taskId, progress); - - // 使用流式下载 - byte[] fileData = downloadFileWithProgress(url, progress, taskId); - if (fileData != null && fileData.length > 0) { - String fileName = name + getFileExtension(url); - addToZipStream(zipOut, path + fileName, fileData); - - // 更新进度 - progress.setProcessedBytes(progress.getProcessedBytes() + fileData.length); - updateProgress(taskId, progress); - } - - } catch (Exception e) { - System.err.println("跳过文件:" + url + " -> " + e.getMessage()); - // 记录错误但不中断整个下载过程 - progress.addError(url, e.getMessage()); - } - } - - /** - * 带进度的文件下载 - */ - private byte[] downloadFileWithProgress(String url, DownloadProgress progress, String taskId) throws IOException { - HttpURLConnection connection = null; - InputStream inputStream = null; - try { - connection = (HttpURLConnection) new URL(url).openConnection(); - connection.setRequestMethod("GET"); - // 30秒连接超时 - connection.setConnectTimeout(30000); - // 120秒读取超时 - connection.setReadTimeout(120000); - connection.setRequestProperty("Accept", "*/*"); - - int responseCode = connection.getResponseCode(); - if (responseCode != 200) { - throw new IOException("HTTP " + responseCode + " for URL: " + url); - } - - int contentLength = connection.getContentLength(); - progress.setCurrentFileSize(contentLength); - updateProgress(taskId, progress); - - inputStream = connection.getInputStream(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - // 8KB缓冲区 - byte[] data = new byte[8192]; - int bytesRead; - long totalRead = 0; - - while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, bytesRead); - totalRead += bytesRead; - - // 更新下载进度 - if (contentLength > 0) { - int percentage = (int) ((totalRead * 100) / contentLength); - progress.setCurrentFilePercentage(percentage); - updateProgress(taskId, progress); - } - } - - return buffer.toByteArray(); - - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - // Ignore - } - } - if (connection != null) { - connection.disconnect(); - } - } - } - - /** - * 流式添加到ZIP - */ - private void addToZipStream(ZipArchiveOutputStream zipOut, String path, byte[] bytes) throws IOException { - if (bytes == null || bytes.length == 0) { - return; - } - - ZipArchiveEntry entry = new ZipArchiveEntry(path); - entry.setSize(bytes.length); - zipOut.putArchiveEntry(entry); - - try (ByteArrayInputStream in = new ByteArrayInputStream(bytes)) { - IOUtils.copy(in, zipOut); - } - - zipOut.closeArchiveEntry(); - zipOut.flush(); - } - - /** - * 获取下载进度接口 - */ - @PostMapping("/downloadProgress") - @ResponseBody - public DownloadProgress getDownloadProgress(@RequestBody Map request) { - String taskId = request.get("taskId"); - if (taskId == null || !downloadProgressMap.containsKey(taskId)) { - DownloadProgress progress = new DownloadProgress(); - progress.setStatus("not_found"); - return progress; - } - - DownloadProgress progress = downloadProgressMap.get(taskId); - if (progress.getTotalFiles() > 0) { - int percentage = (int) ((progress.getCurrentFile() * 100.0) / progress.getTotalFiles()); - progress.setPercentage(Math.min(percentage, 100)); - } - - return progress; - } - - /** - * 计算总文件数 - */ - private int calculateTotalFiles(List items) { - int total = 0; - for (DownloadRequest.ItemInfo item : items) { - // 每个项目至少有一个出库检验报告 - total += 1; - - // 加上各个附件 - if (item.getQualifiedUrl() != null && !item.getQualifiedUrl().isEmpty()) { - total += 1; - } - if (item.getTestReportUrl() != null && !item.getTestReportUrl().isEmpty()) { - total += 1; - } - if (item.getThirdReportUrl() != null && !item.getThirdReportUrl().isEmpty()) { - total += 1; - } - if (item.getFactoryReportUrl() != null && !item.getFactoryReportUrl().isEmpty()) { - total += 1; - } - if (item.getOtherReportUrl() != null && !item.getOtherReportUrl().isEmpty()) { - total += 1; - } - } - return total; - } - - /** - * 更新进度信息 - */ - private void updateProgress(String taskId, DownloadProgress progress) { - if (taskId != null && progress != null) { - downloadProgressMap.put(taskId, progress); - } - } - - // 辅助方法 - private String sanitize(String str) { - return str != null ? str.replaceAll("[\\\\/:*?\"<>|]", "_") : ""; + return name.replaceAll("[\\\\/:*?\"<>|]", "_"); } private String getFileExtension(String url) { - if (url == null || url.isEmpty()) { - return ""; + int idx = url.lastIndexOf('.'); + return (idx > 0 && idx < url.length() - 1) ? url.substring(idx) : ""; + } + + /** + * 资产占有月度报表查询 + * @param bean + * @return + */ + @ApiOperation(value = "资产占有月度报表查询") + @GetMapping("/getAssetReportList") + public AjaxResult getAssetReportList(AssetReportInfo bean) { + startPage(); + List pageList = bmReportService.getAssetReportList(bean); + return AjaxResult.success(getDataTable(pageList)); + } + + /** + * 导出资产占有月度报表 + * @param response + * @param bean + */ + @ApiOperation("导出资产占有月度报表") + @PostMapping("/exportAssetReportList") + public void exportAssetReportList(HttpServletResponse response, AssetReportInfo bean) + { + String fileName = "资产占有月度报表"; + List list = bmReportService.getAssetReportList(bean); + // 根据list集合数,去填充序号 + for (int i = 0; i < list.size(); i++) { + list.get(i).setSeq(i + 1); } - int dotIndex = url.lastIndexOf('.'); - if (dotIndex > 0 && dotIndex < url.length() - 1) { - return url.substring(dotIndex); - } - return ""; + ExcelUtil util = new ExcelUtil<>(AssetReportInfo.class); + // 获取当前年月日时分秒导出时间,用括号拼接在后面 + String title = "资产占有月度报表" + "(" + "导出时间:" + DateUtils.getTime() + ")"; + util.exportExcel(response, list, fileName, title); } } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/utils/DocxUtil.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/utils/DocxUtil.java index dfcad9a6..d75e79a0 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/utils/DocxUtil.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/utils/DocxUtil.java @@ -12,8 +12,6 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import java.io.*; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; /** @@ -22,114 +20,38 @@ import java.util.List; */ public class DocxUtil { - /** - * 生成单个报告 - */ public static byte[] generateReport(DownloadRequest.ItemInfo item) { - return generateReportByList(Collections.singletonList(item)); - } - - /** - * 生成多个报告(带分页) - */ - public static byte[] generateReportByList(List items) { - return generateReportByListWithPagination(items); - } - - /** - * 生成PDF报告(带自动分页功能) - */ - public static byte[] generateReportByListWithPagination(List items) { - if (items == null || items.isEmpty()) { - return new byte[0]; - } - try (PDDocument document = new PDDocument(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) { PDType0Font font = PDType0Font.load(document, PdfUtil.class.getClassLoader().getResourceAsStream("fonts/syht.ttf")); - // 页面尺寸(横向A4) PDRectangle pageSize = new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth()); + float margin = 50; + float yStart = pageSize.getHeight() - margin; + float yPosition = yStart; - // 数据转为二维数组 - String[][] rows = convertItemsToRows(items); - - // 生成PDF - generatePdfWithPagination(document, font, pageSize, items.get(0), rows); - - document.save(bos); - return bos.toByteArray(); - - } catch (Exception e) { - e.printStackTrace(); - // 返回一个简单的错误报告,而不是空数组 - return generateErrorReport(e.getMessage()); - } - } - - /** - * 将Item列表转为二维数组 - */ - private static String[][] convertItemsToRows(List items) { - String[] headers = { - "机具名称", "规格型号", "单位", "数量", "设备编码", - "额定载荷KN", "试验载荷KN", "持荷时间min", - "试验日期", "下次试验日期", "检验结论", "备注" - }; - - String[][] rows = new String[items.size()][headers.length]; - for (int i = 0; i < items.size(); i++) { - DownloadRequest.ItemInfo item = items.get(i); - rows[i] = new String[]{ - safe(item.getTypeName()), - safe(item.getTypeModelName()), - safe(item.getUnit()), - safe(String.valueOf(item.getNum())), - safe(item.getMaCode()), - safe(item.getRatedLoad()), - safe(item.getTestLoad()), - safe(item.getHoldingTime()), - safe(item.getTestTime()), - safe(item.getNextTestTime()), - safe(item.getCheckResult()), - safe(item.getRemark()) + float[] colWidths = {80, 80, 40, 40, 80, 70, 70, 75, 65, 80, 60, 40}; + String[] headers = { + "机具名称", "规格型号", "单位", "数量", "设备编码", + "额定载荷KN", "试验载荷KN", "持荷时间min", + "试验日期", "下次试验日期", "检验结论", "备注" + }; + String[][] rows = { + { + safe(item.getTypeName()), safe(item.getTypeModelName()), safe(item.getUnit()), + safe(String.valueOf(item.getNum())), safe(item.getMaCode()), + safe(item.getRatedLoad()), safe(item.getTestLoad()), safe(item.getHoldingTime()), + safe(item.getTestTime()), safe(item.getNextTestTime()), + safe(item.getCheckResult()), safe(item.getRemark()) + } }; - } - return rows; - } - /** - * 生成带分页的PDF - */ - private static void generatePdfWithPagination(PDDocument document, PDType0Font font, - PDRectangle pageSize, DownloadRequest.ItemInfo firstItem, - String[][] rows) throws IOException { - float margin = 50; - float yStart = pageSize.getHeight() - margin; - float bottomMargin = 120; // 底部留出足够空间给印章 - - float[] colWidths = {80, 80, 40, 40, 80, 70, 70, 75, 65, 80, 60, 40}; - String[] headers = { - "机具名称", "规格型号", "单位", "数量", "设备编码", - "额定载荷KN", "试验载荷KN", "持荷时间min", - "试验日期", "下次试验日期", "检验结论", "备注" - }; - - // 分页处理 - int totalRows = rows.length; - int currentRow = 0; - int pageNum = 0; - - while (currentRow < totalRows) { - // 创建新页面 PDPage page = new PDPage(pageSize); document.addPage(page); PDPageContentStream content = new PDPageContentStream(document, page); - float yPosition = yStart; - // ===== 标题 ===== String titleText = "施工机具设备出库检验记录表"; content.beginText(); @@ -140,109 +62,103 @@ public class DocxUtil { yPosition -= 50; - // ===== 工程与单位(第一页显示,后续页面只显示页码) ===== - if (pageNum == 0) { - content.beginText(); - content.setFont(font, 12); - content.newLineAtOffset(margin, yPosition); - content.showText("领用工程:" + safe(firstItem.getProName())); - content.newLineAtOffset(0, -20); - content.showText("使用单位:" + safe(firstItem.getDepartName())); - content.endText(); - yPosition -= 40; - } else { - // 后续页面显示页码 - content.beginText(); - content.setFont(font, 10); - content.newLineAtOffset(pageSize.getWidth() - margin - 50, yStart - 30); - content.showText("第 " + (pageNum + 1) + " 页"); - content.endText(); - } + // ===== 工程与单位 ===== + content.beginText(); + content.setFont(font, 12); + content.newLineAtOffset(margin, yPosition); + content.showText("领用工程:" + safe(item.getProName())); + content.newLineAtOffset(0, -20); + content.showText("使用单位:" + safe(item.getDepartName())); + content.endText(); - // ===== 计算本页的行数 ===== - String[][] pageRows = getRowsForCurrentPage(rows, currentRow, margin, yPosition, bottomMargin, - colWidths, headers, font); - int rowsThisPage = pageRows.length; + yPosition -= 40; - // ===== 表格绘制 ===== + // ===== 表格绘制方法 ===== float tableWidth = 0; for (float w : colWidths) { tableWidth += w; } + float tableBottomMargin = 100; float rowMargin = 5; float fontSize = 10; - yPosition = drawTableWithPagination(document, page, content, font, margin, yPosition, tableWidth, - colWidths, headers, pageRows, fontSize, rowMargin, pageSize, yStart, bottomMargin); + // 绘制表头 + 数据 + yPosition = drawTable(document, page, content, font, margin, yPosition, tableWidth, colWidths, + headers, rows, fontSize, rowMargin, pageSize, yStart, tableBottomMargin); - // ===== 检验单位与印章(最后一页显示) ===== - if (currentRow + rowsThisPage >= totalRows) { - yPosition -= 60; - content.beginText(); - content.setFont(font, 12); - content.newLineAtOffset(margin, yPosition); - content.showText("检验单位:"); - content.endText(); + // ===== 检验单位与印章 ===== + yPosition -= 60; + content.beginText(); + content.setFont(font, 12); + content.newLineAtOffset(margin, yPosition); + content.showText("检验单位:"); + content.endText(); - // 添加印章图片 - addStamp(document, content, margin, yPosition); + InputStream is = PdfUtil.class.getClassLoader().getResourceAsStream("template/gaizhang.png"); + if (is != null) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] data = new byte[1024]; + int nRead; + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + byte[] imageBytes = buffer.toByteArray(); + + PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "gaizhang"); + content.drawImage(image, margin + 70, yPosition - 60, 100, 100); + is.close(); } + content.close(); - currentRow += rowsThisPage; - pageNum++; + document.save(bos); + return bos.toByteArray(); + + } catch (Exception e) { + e.printStackTrace(); + return new byte[0]; } } - /** - * 获取当前页的行数据 - */ - private static String[][] getRowsForCurrentPage(String[][] rows, int startRow, - float margin, float startY, float bottomMargin, - float[] colWidths, String[] headers, PDFont font) throws IOException { - List pageRows = new ArrayList<>(); - float currentY = startY; - float fontSize = 10; - float cellMargin = 5; - // 绘制表头所需高度 - float headerHeight = getRowHeight(headers, font, fontSize, colWidths, cellMargin); - currentY -= headerHeight; + private static String safe(String v) { + return v == null ? "" : v; + } - for (int i = startRow; i < rows.length; i++) { - float rowHeight = getRowHeight(rows[i], font, fontSize, colWidths, cellMargin); - - // 检查是否有足够空间绘制这一行 - if (currentY - rowHeight < bottomMargin) { - break; - } - - pageRows.add(rows[i]); - currentY -= rowHeight; - } - - // 转换为数组 - return pageRows.toArray(new String[0][]); + private static float getStringWidth(String text, PDFont font, float fontSize) throws IOException { + return font.getStringWidth(text) / 1000 * fontSize; } /** - * 绘制表格(分页版本) + * 自动换行表格绘制 */ - private static float drawTableWithPagination(PDDocument doc, PDPage page, PDPageContentStream content, - PDFont font, float startX, float startY, float tableWidth, - float[] colWidths, String[] headers, String[][] rows, - float fontSize, float cellMargin, PDRectangle pageSize, - float yStart, float bottomMargin) throws IOException { + private static float drawTable(PDDocument doc, PDPage page, PDPageContentStream content, + PDFont font, float startX, float startY, float tableWidth, + float[] colWidths, String[] headers, String[][] rows, + float fontSize, float cellMargin, PDRectangle pageSize, + float yStart, float bottomMargin) throws IOException { float y = startY; float pageHeight = pageSize.getHeight(); + float usableHeight = pageHeight - bottomMargin; // 绘制表头 y = drawRow(doc, page, content, font, startX, y, colWidths, headers, fontSize, cellMargin, true); - // 绘制数据行 for (String[] row : rows) { float rowHeight = getRowHeight(row, font, fontSize, colWidths, cellMargin); + + if (y - rowHeight < bottomMargin) { + content.close(); + PDPage newPage = new PDPage(pageSize); + doc.addPage(newPage); + content = new PDPageContentStream(doc, newPage); + y = yStart; + + // 重绘表头 + y = drawRow(doc, newPage, content, font, startX, y, colWidths, headers, fontSize, cellMargin, true); + } + y = drawRow(doc, page, content, font, startX, y, colWidths, row, fontSize, cellMargin, false); } @@ -272,8 +188,8 @@ public class DocxUtil { } // 绘制单元格边框 - content.setStrokingColor(0, 0, 0); - content.setLineWidth(1f); + // 黑色 + content.setNonStrokingColor(0, 0, 0); content.addRect(x, y - maxHeight, cellWidth, maxHeight); content.stroke(); @@ -302,6 +218,8 @@ public class DocxUtil { return y - maxHeight; } + + /** * 计算行高(取最长文本行数) */ @@ -338,141 +256,7 @@ public class DocxUtil { lines.add(line.toString()); return lines; } - - /** - * 添加印章 - */ - private static void addStamp(PDDocument document, PDPageContentStream content, - float margin, float yPosition) throws IOException { - InputStream is = PdfUtil.class.getClassLoader().getResourceAsStream("template/gaizhang.png"); - if (is != null) { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - byte[] data = new byte[1024]; - int nRead; - while ((nRead = is.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - byte[] imageBytes = buffer.toByteArray(); - - PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "gaizhang"); - content.drawImage(image, margin + 70, yPosition - 60, 100, 100); - is.close(); - } - } - - /** - * 安全获取字符串(处理null) - */ - private static String safe(String str) { - return str != null ? str : ""; - } - - /** - * 获取字符串宽度 - */ - private static float getStringWidth(String text, PDFont font, float fontSize) throws IOException { - if (text == null || text.isEmpty()) { - return 0; - } - return font.getStringWidth(text) / 1000 * fontSize; - } - - /** - * 生成错误报告 - */ - private static byte[] generateErrorReport(String errorMessage) { - try (PDDocument document = new PDDocument(); - ByteArrayOutputStream bos = new ByteArrayOutputStream()) { - - PDType0Font font = PDType0Font.load(document, - PdfUtil.class.getClassLoader().getResourceAsStream("fonts/syht.ttf")); - - PDPage page = new PDPage(PDRectangle.A4); - document.addPage(page); - - try (PDPageContentStream content = new PDPageContentStream(document, page)) { - content.beginText(); - content.setFont(font, 16); - content.newLineAtOffset(100, 700); - content.showText("出库检验报告生成失败"); - content.endText(); - - content.beginText(); - content.setFont(font, 12); - content.newLineAtOffset(100, 650); - content.showText("错误信息: " + (errorMessage != null ? - errorMessage.substring(0, Math.min(100, errorMessage.length())) : "未知错误")); - content.endText(); - - content.beginText(); - content.setFont(font, 12); - content.newLineAtOffset(100, 600); - content.showText("生成时间: " + new java.util.Date()); - content.endText(); - - content.beginText(); - content.setFont(font, 10); - content.newLineAtOffset(100, 550); - content.showText("提示: 请减少一次下载的数据量,或联系系统管理员"); - content.endText(); - } - - document.save(bos); - return bos.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - return new byte[0]; - } - } - - /** - * 原有的drawTable方法(保持兼容) - */ - public static float drawTable(PDDocument doc, PDPage page, PDPageContentStream content, - PDFont font, float startX, float startY, float tableWidth, - float[] colWidths, String[] headers, String[][] rows, - float fontSize, float cellMargin, PDRectangle pageSize, - float yStart, float bottomMargin) throws IOException { - - float y = startY; - float pageHeight = pageSize.getHeight(); - - // 绘制表头 - y = drawRow(doc, page, content, font, startX, y, colWidths, headers, fontSize, cellMargin, true); - - for (String[] row : rows) { - float rowHeight = getRowHeight(row, font, fontSize, colWidths, cellMargin); - - if (y - rowHeight < bottomMargin) { - // 关闭当前页面内容流 - content.close(); - - // 创建新页面 - PDPage newPage = new PDPage(pageSize); - doc.addPage(newPage); - content = new PDPageContentStream(doc, newPage); - - // 重置y坐标 - y = yStart; - - // 重绘表头 - y = drawRow(doc, newPage, content, font, startX, y, colWidths, headers, fontSize, cellMargin, true); - - // 绘制当前行 - y = drawRow(doc, newPage, content, font, startX, y, colWidths, row, fontSize, cellMargin, false); - } else { - // 在当前页面绘制行 - y = drawRow(doc, page, content, font, startX, y, colWidths, row, fontSize, cellMargin, false); - } - } - - return y; - } - - /** - * 旧版生成多个报告方法(无分页,兼容旧代码) - */ - public static byte[] generateReportByListOld(List items) { + public static byte[] generateReportByList(List items) { if (items == null || items.isEmpty()) { return new byte[0]; } @@ -555,8 +339,20 @@ public class DocxUtil { content.showText("检验单位:"); content.endText(); - // 添加印章图片 - addStamp(document, content, margin, yPosition); + InputStream is = PdfUtil.class.getClassLoader().getResourceAsStream("template/gaizhang.png"); + if (is != null) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] data = new byte[1024]; + int nRead; + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + byte[] imageBytes = buffer.toByteArray(); + + PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "gaizhang"); + content.drawImage(image, margin + 70, yPosition - 60, 100, 100); + is.close(); + } content.close(); document.save(bos); @@ -567,4 +363,6 @@ public class DocxUtil { return new byte[0]; } } -} \ No newline at end of file + + +}