From b6efb383b8b20a012309b7b2d82a0299b24368cb Mon Sep 17 00:00:00 2001 From: "LUOJIE\\coolp" Date: Fri, 30 Jan 2026 19:31:31 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 11 ++-- .../src/main/resources/application-prod.yml | 46 +++++++++----- .../common/utils/file/FileUploadUtils.java | 2 +- .../FileComparisonStrategyFactory.java | 4 ++ .../strategy/WordComparisonStrategy.java | 8 +++ .../com/qingyun/service/utils/PdfUtil.java | 62 +++++++++++++++++-- 6 files changed, 107 insertions(+), 26 deletions(-) diff --git a/qingyun-admin/src/main/resources/application-dev.yml b/qingyun-admin/src/main/resources/application-dev.yml index 9aa602c..77e0e9f 100644 --- a/qingyun-admin/src/main/resources/application-dev.yml +++ b/qingyun-admin/src/main/resources/application-dev.yml @@ -1,7 +1,6 @@ --- # 项目相关配置 qingyun: -# 项目根目录 - profile: ${root.directory} + profile: /application/data/localUpload/carUpload # projectUrl: http://192.168.3.16:8081 projectUrl: http://localhost:8099 --- # 监控中心配置 @@ -145,10 +144,10 @@ security: # - /actuator/** # 验证码 - /**/captcha/** - - /system/oss/uploadLocal - - /api/service/reviewTask/** - - /api/pdf/** - - /api/service/compareTask/** + - /application/data/system/oss/uploadLocal + - /application/data/api/service/reviewTask/** + - /application/data/api/pdf/** + - /application/data/api/service/compareTask/** # orc识别服务 ocr: diff --git a/qingyun-admin/src/main/resources/application-prod.yml b/qingyun-admin/src/main/resources/application-prod.yml index d440fe3..77e0e9f 100644 --- a/qingyun-admin/src/main/resources/application-prod.yml +++ b/qingyun-admin/src/main/resources/application-prod.yml @@ -1,10 +1,19 @@ --- # 项目相关配置 qingyun: - profile: /opt/workspace/localUpload/carUpload + profile: /application/data/localUpload/carUpload + # projectUrl: http://192.168.3.16:8081 projectUrl: http://localhost:8099 -# projectUrl: ---- # 临时文件存储位置 避免临时文件被系统清理报错 -spring.servlet.multipart.location: /qingyun/server/temp +--- # 监控中心配置 +spring.boot.admin.client: + # 增加客户端开关 + enabled: false + url: http://localhost:9090/admin + instance: + service-host-type: IP + username: qingyun + password: 123456 + + --- # 数据源配置 spring: @@ -13,7 +22,7 @@ spring: # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content dynamic: # 性能分析插件(有性能损耗 不建议生产环境使用) - p6spy: false + p6spy: true # 设置默认的数据源或者数据源组,默认值即为 master primary: master # 严格模式 匹配不到数据源则报错 @@ -74,17 +83,17 @@ redisson: # redis key前缀 keyPrefix: # 线程池数量 - threads: 16 + threads: 4 # Netty线程池数量 - nettyThreads: 32 + nettyThreads: 8 # 单节点配置 singleServerConfig: # 客户端名称 clientName: ${qingyun.name} # 最小空闲连接数 - connectionMinimumIdleSize: 32 + connectionMinimumIdleSize: 8 # 连接池大小 - connectionPoolSize: 64 + connectionPoolSize: 32 # 连接空闲超时,单位:毫秒 idleConnectionTimeout: 10000 # 命令等待超时,单位:毫秒 @@ -95,7 +104,7 @@ redisson: springdoc: api-docs: # 是否开启接口文档 - enabled: false + enabled: true swagger-ui: path: /doc.html # 持久化认证数据 @@ -114,6 +123,7 @@ springdoc: - group: 4.合同业务管理 packages-to-scan: com.qingyun.web.controller.api.contract + # security配置 security: # 排除路径 @@ -126,13 +136,19 @@ security: # 公共路径 - /favicon.ico - /error + # swagger 文档配置 + - /*/api-docs + - /*/api-docs/** +# # actuator 监控配置 +# - /actuator +# - /actuator/** # 验证码 - /**/captcha/** - - /system/oss/uploadLocal - - /api/service/reviewTask/** - - /api/pdf/** - - /api/service/compareTask/** + - /application/data/system/oss/uploadLocal + - /application/data/api/service/reviewTask/** + - /application/data/api/pdf/** + - /application/data/api/service/compareTask/** # orc识别服务 ocr: - url: http://10.77.149.156:8188/layout-parsing + url: http://10.78.113.249:8080/layout-parsing diff --git a/qingyun-common/src/main/java/com/qingyun/common/utils/file/FileUploadUtils.java b/qingyun-common/src/main/java/com/qingyun/common/utils/file/FileUploadUtils.java index b35bfd3..dbf71aa 100644 --- a/qingyun-common/src/main/java/com/qingyun/common/utils/file/FileUploadUtils.java +++ b/qingyun-common/src/main/java/com/qingyun/common/utils/file/FileUploadUtils.java @@ -131,7 +131,7 @@ public class FileUploadUtils */ public static String extractFilename(MultipartFile file) { - String fileName = file.getOriginalFilename(); + String fileName = FilenameUtils.getName(file.getOriginalFilename()); String extension = getExtension(file); fileName = DateUtils.datePath() + "/" + IdUtil.fastUUID() + "/" + fileName; return fileName; diff --git a/qingyun-service/src/main/java/com/qingyun/service/compare/factory/FileComparisonStrategyFactory.java b/qingyun-service/src/main/java/com/qingyun/service/compare/factory/FileComparisonStrategyFactory.java index 53b4e10..1d1c669 100644 --- a/qingyun-service/src/main/java/com/qingyun/service/compare/factory/FileComparisonStrategyFactory.java +++ b/qingyun-service/src/main/java/com/qingyun/service/compare/factory/FileComparisonStrategyFactory.java @@ -10,10 +10,12 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; /** * 策略工厂(根据文件类型选择策略) */ @Component +@Slf4j public class FileComparisonStrategyFactory { private final List strategies; @@ -36,12 +38,14 @@ public class FileComparisonStrategyFactory { String ext1 = getFileExtension(task.getFirstContractUrl()); String ext2 = getFileExtension(task.getSecondContractUrl()); + log.info("选择比对策略,扩展名: first={}, second={}", ext1, ext2); if (!ext1.equalsIgnoreCase(ext2)) { throw new ServiceException("两份文件类型不一致,无法对比"); } for (FileComparisonStrategy strategy : strategies) { if (strategy.supports(ext1)) { + log.info("匹配到策略: {}", strategy.getClass().getSimpleName()); return strategy; } } diff --git a/qingyun-service/src/main/java/com/qingyun/service/compare/strategy/WordComparisonStrategy.java b/qingyun-service/src/main/java/com/qingyun/service/compare/strategy/WordComparisonStrategy.java index 5c2072e..0dfdacb 100644 --- a/qingyun-service/src/main/java/com/qingyun/service/compare/strategy/WordComparisonStrategy.java +++ b/qingyun-service/src/main/java/com/qingyun/service/compare/strategy/WordComparisonStrategy.java @@ -212,6 +212,14 @@ public class WordComparisonStrategy extends AbstractFileComparisonStrategy imple pdfFile = targetFile; } } + // 校验目标PDF是否可读,提前捕获异常并提升错误可见性 + try (PDDocument doc = Loader.loadPDF(pdfFile)) { + if (doc == null || doc.getNumberOfPages() <= 0) { + throw new ServiceException("生成的PDF无法解析或页数为0"); + } + } catch (IOException e) { + throw new ServiceException("生成的PDF无法被PDFBox加载: " + e.getMessage()); + } // 更新合同URL为新的PDF文件路径 String newUrl = "/profile/upload/" + needUploadName; diff --git a/qingyun-service/src/main/java/com/qingyun/service/utils/PdfUtil.java b/qingyun-service/src/main/java/com/qingyun/service/utils/PdfUtil.java index dc92af5..3881474 100644 --- a/qingyun-service/src/main/java/com/qingyun/service/utils/PdfUtil.java +++ b/qingyun-service/src/main/java/com/qingyun/service/utils/PdfUtil.java @@ -6,6 +6,7 @@ import com.documents4j.api.IConverter; import com.documents4j.job.LocalConverter; import com.qingyun.common.config.QingYunConfig; import com.qingyun.common.utils.file.FileUploadUtils; +import com.qingyun.common.constant.Constants; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.pdfbox.Loader; @@ -19,6 +20,8 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; @@ -99,9 +102,37 @@ public class PdfUtil { String newName = name.substring(0, name.lastIndexOf(".")); String projectUrl = QingYunConfig.getProjectUrl(); File tempFile = File.createTempFile(newName + System.currentTimeMillis(), getFileExtension(name)); - long file = HttpUtil.downloadFile(projectUrl + pdfUrl, tempFile); - if (file <= 0) { - throw new IOException("无法下载 PDF 文件"); + + /** + * 下载或本地复制文件到临时目录 + * - 当路径以 /profile 开头时,直接从容器本地文件系统复制,避免网关/反代造成的HTML错误页 + * - 否则通过 HTTP 下载 + */ + if (pdfUrl != null && pdfUrl.startsWith(Constants.RESOURCE_PREFIX)) { + String localPath = pdfUrl.replaceFirst(Constants.RESOURCE_PREFIX, QingYunConfig.getProfile()); + File source = new File(localPath); + if (!source.exists() || !source.isFile()) { + throw new IOException("本地文件不存在或无效: " + localPath); + } + Files.copy(source.toPath(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } else { + long size = HttpUtil.downloadFile(projectUrl + pdfUrl, tempFile); + if (size <= 0) { + throw new IOException("无法下载文件: " + projectUrl + pdfUrl); + } + } + + /** + * 若为PDF后缀,进行签名与解析预检测 + */ + if (".pdf".equalsIgnoreCase(getFileExtension(name))) { + try (PDDocument doc = Loader.loadPDF(tempFile)) { + if (doc == null || doc.getNumberOfPages() <= 0) { + throw new IOException("下载的PDF无法解析或页数为0"); + } + } catch (IOException e) { + throw new IOException("下载的PDF无法被PDFBox加载: " + e.getMessage(), e); + } } return tempFile; } @@ -126,6 +157,16 @@ public class PdfUtil { File outputFile = new File(wordFile.getParent(), FilenameUtils.getBaseName(wordFile.getName()) + ".pdf"); + /** + * 将 Word 文件转换为 PDF(Windows) + * - 使用 documents4j 本地转换器进行格式转换 + * - 转换完成后进行健全性校验:文件存在、非空、可被 PDFBox 正常加载 + * - 若校验失败则抛出 IOException,避免后续流程在解析阶段失败 + * + * @param wordFile 待转换的 Word 临时文件 + * @return 转换后的 PDF 文件对象 + * @throws IOException 当转换或校验失败时抛出 + */ try { IConverter converter = LocalConverter.builder().build(); @@ -134,7 +175,20 @@ public class PdfUtil { .to(outputFile).as(DocumentType.PDF) .prioritizeWith(1000) // optional .schedule(); - conversion.get(); // 这里会阻塞直到转换完成 + conversion.get(); // 阻塞直到转换完成 + + // 基础校验:文件存在且非空 + if (!outputFile.exists() || outputFile.length() == 0) { + throw new IOException("PDF文件未生成或为空"); + } + // 尝试用 PDFBox 打开以验证文件有效性 + try (PDDocument doc = Loader.loadPDF(outputFile)) { + if (doc == null || doc.getNumberOfPages() <= 0) { + throw new IOException("生成的PDF无法解析或页数为0"); + } + } catch (IOException e) { + throw new IOException("生成的PDF无法被PDFBox加载: " + e.getMessage(), e); + } return outputFile; } catch (Exception e) {