From a278a75f8d058a160d8df49d9e646621b3dc553b Mon Sep 17 00:00:00 2001 From: zhouhaibin Date: Thu, 22 May 2025 10:32:25 +0800 Subject: [PATCH] =?UTF-8?q?pdf=20=E4=B8=8B=E8=BD=BD=E5=B8=A6=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/ContractualTasksServiceImpl.java | 1 - .../impl/DocumentTaskResultsServiceImpl.java | 185 +++++++++++++----- .../impl/DocumentTasksServiceImpl.java | 28 +-- 3 files changed, 142 insertions(+), 72 deletions(-) diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java index e9b4d53..f3211b4 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java @@ -96,7 +96,6 @@ public class ContractualTasksServiceImpl extends AbstractTaskProcessor resultDetails = getDetailResultsByTaskId(String.valueOf(taskId)); if (resultDetails != null && !resultDetails.isEmpty()) { @@ -322,12 +322,12 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi if (allCategory.getResults() != null && !allCategory.getResults().isEmpty()) { // 获取任务类型对应的字段配置 List fieldConfigs = getFieldConfigsByTaskType(taskName); - + // 将结果转换为Markdown StringBuilder markdown = new StringBuilder(); for (DocumentTaskResultVO.ResultItem item : allCategory.getResults()) { markdown.append("## ").append(item.getSerialNumber()).append(". ").append(item.getExistingIssues()).append("\n\n"); - + // 根据配置添加各字段内容 for (FieldConfig config : fieldConfigs) { String fieldValue = getFieldValue(item, config.getField()); @@ -335,31 +335,31 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi markdown.append("### ").append(config.getTitle()).append("\n\n").append(fieldValue).append("\n\n"); } } - + // 添加分隔线 markdown.append("---\n\n"); } - + // 为每个结果创建PDF文件 String pdfFileName = label + "_" + documentName + "_" + ".pdf"; String pdfPath = tempDir + File.separator + pdfFileName; - + // 转换Markdown为PDF convertMarkdownToPdf(markdown.toString(), pdfPath); - + // 添加到ZIP addToZip(pdfPath, pdfFileName, zos); - + // 删除临时PDF文件 new File(pdfPath).delete(); } } } } - + // 下载ZIP文件 downloadFile(response, zipFilePath, zipFileName); - + // 删除临时ZIP文件 new File(zipFilePath).delete(); } else { @@ -369,30 +369,30 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi if (documentTasksVo == null) { throw new RuntimeException("未找到相关任务"); } - + String documentName = documentTasksVo.getDocumentName(); String taskName = documentTasksVo.getTaskName(); String taskType = documentTasksVo.getTaskType(); String label = dictTypeService.getDictLabel(taskType, taskName); - + // 获取详细的任务结果 List resultDetails = getDetailResultsByTaskId(String.valueOf(taskId)); - if (resultDetails == null || resultDetails.isEmpty() || + if (resultDetails == null || resultDetails.isEmpty() || resultDetails.get(0).getResults() == null || resultDetails.get(0).getResults().isEmpty()) { throw new RuntimeException("未找到相关结果"); } - + // 第一个是"全部"分类 DocumentTaskResultVO allCategory = resultDetails.get(0); - + // 获取任务类型对应的字段配置 List fieldConfigs = getFieldConfigsByTaskType(taskName); - + // 将结果转换为Markdown StringBuilder markdown = new StringBuilder(); for (DocumentTaskResultVO.ResultItem item : allCategory.getResults()) { markdown.append("## ").append(item.getSerialNumber()).append(". ").append(item.getExistingIssues()).append("\n\n"); - + // 根据配置添加各字段内容 for (FieldConfig config : fieldConfigs) { String fieldValue = getFieldValue(item, config.getField()); @@ -400,21 +400,21 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi markdown.append("### ").append(config.getTitle()).append("\n\n").append(fieldValue).append("\n\n"); } } - + // 添加分隔线 markdown.append("---\n\n"); } - + String pdfFileName = label + "_" + documentName + "_" + ".pdf"; String tempDir = System.getProperty("java.io.tmpdir"); String pdfPath = tempDir + File.separator + pdfFileName; - + // 转换Markdown为PDF convertMarkdownToPdf(markdown.toString(), pdfPath); - + // 下载PDF文件 downloadFile(response, pdfPath, pdfFileName); - + // 删除临时PDF文件 new File(pdfPath).delete(); } @@ -494,12 +494,6 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi // Markdown转PDF方法 private void convertMarkdownToPdf(String markdown, String outputPath) throws Exception { - // 使用flexmark-java解析Markdown -// MutableDataSet options = new MutableDataSet(); -// Parser parser = Parser.builder(options).build(); -// HtmlRenderer renderer = HtmlRenderer.builder(options).build(); -// Node document = parser.parse(markdown); -// String htmlContent = renderer.render(document); MutableDataSet options = new MutableDataSet(); // 启用表格解析 options.set(Parser.EXTENSIONS, Arrays.asList( @@ -523,14 +517,12 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi // 配置允许的标签 Safelist safelist = Safelist.relaxed() -// .addTags("em", "strong", "div", "span","br") -// .addAttributes(":all", "style", "class"); - .addTags("em", "strong", "div", "span", "br", "table", "thead", "tbody", "tr", "th", "td") + .addTags("em", "strong", "div", "span", "br", "table", "thead", "tbody", "tr", "th", "td", "img") .addAttributes(":all", "style", "class") - .addAttributes("table", "border", "cellpadding", "cellspacing"); + .addAttributes("table", "border", "cellpadding", "cellspacing") + .addAttributes("img", "src", "alt", "width", "height"); // 允许img标签及其属性 htmlContent = Jsoup.clean(htmlContent, "", safelist, settings); - // 使用 NekoHTML 进一步处理 // 创建完整的HTML文档,使用微软雅黑 String html = String.format(""" @@ -573,6 +565,14 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi tr { page-break-inside: avoid; } + img { + width: 600px; + max-width: 100%%; + height: auto; + display: block; + margin: 20px auto; + page-break-inside: avoid; + } @@ -581,9 +581,8 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi """, htmlContent.replace("
", "\n")); - - - + // 处理图片标签,将markdown的图片语法转换为HTML的img标签 + html = handleImageTags(html); // 配置Flying Saucer ITextRenderer pdfRenderer = new ITextRenderer(); @@ -610,6 +609,104 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi } } + /** + * 处理Markdown中的图片标签 + * @param content Markdown内容 + * @return 处理后的HTML内容 + */ + private String handleImageTags(String content) { + // 使用非贪婪模式匹配alt文本,使用平衡组模式匹配路径 + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("!\\[(.*?)\\]\\(([^()]*(?:\\([^()]*\\)[^()]*)*?)\\)"); + java.util.regex.Matcher matcher = pattern.matcher(content); + StringBuffer sb = new StringBuffer(); + + while (matcher.find()) { + String alt = matcher.group(1); + String imagePath = matcher.group(2); + + // 检查图片文件是否存在 + File imageFile = new File(imagePath); + if (!imageFile.exists() && imagePath.contains("(")) { + // 如果文件不存在且路径中包含括号,尝试修正路径 + String correctedPath = correctImagePath(imagePath); + imageFile = new File(correctedPath); + if (imageFile.exists()) { + imagePath = correctedPath; + } + } + + if (imageFile.exists()) { + try { + // 将图片转换为Base64编码 + byte[] imageBytes = Files.readAllBytes(imageFile.toPath()); + String base64Image = java.util.Base64.getEncoder().encodeToString(imageBytes); + String imageType = getImageMimeType(imagePath); + + // 创建data URL,使用固定的宽度来确保图片在页面内显示 + String imgTag = String.format("\"%s\"", + imageType, base64Image, alt); + + // 替换markdown图片语法为HTML img标签 + matcher.appendReplacement(sb, imgTag.replace("$", "\\$")); + } catch (IOException e) { + log.error("处理图片失败: " + imagePath, e); + // 如果处理失败,保留原始文本 + matcher.appendReplacement(sb, matcher.group(0).replace("$", "\\$")); + } + } else { + log.warn("图片文件不存在: " + imagePath); + // 如果图片不存在,保留原始文本 + matcher.appendReplacement(sb, matcher.group(0).replace("$", "\\$")); + } + } + matcher.appendTail(sb); + return sb.toString(); + } + + /** + * 修正包含括号的图片路径 + * @param originalPath 原始路径 + * @return 修正后的路径 + */ + private String correctImagePath(String originalPath) { + // 如果路径中包含多个括号,取最后一个右括号之前的内容 + int lastRightBracket = originalPath.lastIndexOf(")"); + if (lastRightBracket != -1) { + // 找到对应的左括号 + int count = 1; + int leftBracket = lastRightBracket - 1; + while (leftBracket >= 0 && count > 0) { + if (originalPath.charAt(leftBracket) == ')') { + count++; + } else if (originalPath.charAt(leftBracket) == '(') { + count--; + } + leftBracket--; + } + if (count == 0) { + // 找到匹配的括号对,返回不包含最外层括号的路径 + return originalPath.substring(0, leftBracket + 1) + originalPath.substring(lastRightBracket + 1); + } + } + return originalPath; + } + + /** + * 根据文件扩展名获取MIME类型 + * @param filePath 文件路径 + * @return MIME类型 + */ + private String getImageMimeType(String filePath) { + String extension = filePath.substring(filePath.lastIndexOf(".") + 1).toLowerCase(); + return switch (extension) { + case "png" -> "image/png"; + case "jpg", "jpeg" -> "image/jpeg"; + case "gif" -> "image/gif"; + case "bmp" -> "image/bmp"; + default -> "image/jpeg"; + }; + } + // 添加文件到ZIP private void addToZip(String filePath, String fileName, ZipOutputStream zos) throws IOException { byte[] buffer = new byte[1024]; @@ -770,21 +867,21 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi private static class FieldConfig { private String field; private String title; - + public FieldConfig(String field, String title) { this.field = field; this.title = title; } - + public String getField() { return field; } - + public String getTitle() { return title; } } - + /** * 根据任务类型获取对应的字段配置 * @@ -793,7 +890,7 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi */ private List getFieldConfigsByTaskType(String taskName) { List configs = new java.util.ArrayList<>(); - + // 根据任务类型返回不同的字段配置 switch (taskName) { case "checkCompanyName": @@ -824,10 +921,10 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi configs.add(new FieldConfig("modificationDisplay", "修改情况")); break; } - + return configs; } - + /** * 获取对象的字段值 * diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTasksServiceImpl.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTasksServiceImpl.java index 078a3e7..bdc0f7f 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTasksServiceImpl.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTasksServiceImpl.java @@ -1,11 +1,9 @@ package org.dromara.productManagement.service.impl; -import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.io.FileUtil; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import lombok.Data; -import lombok.RequiredArgsConstructor; + import okhttp3.*; import org.dromara.common.core.service.DictService; import org.dromara.common.core.utils.MapstructUtils; @@ -308,28 +306,4 @@ public class DocumentTasksServiceImpl extends AbstractTaskProcessor taskNames, BaseTaskBo bo, FileProcessResult fileResult, String taskType) { -// boolean flag = false; -// for (String taskName : taskNames) { -// DocumentTasks add = MapstructUtils.convert(bo, DocumentTasks.class); -// add.setTaskName(taskName); -// add.setDocumentName(fileResult.getFileName()); -// add.setProgressStatus("PENDING"); -// add.setTaskType(taskType); -// -// flag = baseMapper.insert(add) > 0; -// -// Long priority = calculatePriority(taskName); -// MyHttpUtils.sendTaskStartMessage(chatUrl + "/back/taskStart", -// add.getId(), taskName, fileResult.getFilePath(), priority); -// -// if (!flag) { -// throw new RuntimeException("新增文档任务失败"); -// } -// } -// return flag; -// -// } }