解决报错

This commit is contained in:
2026-01-30 19:31:31 +08:00
parent 8dd8d2668a
commit b6efb383b8
6 changed files with 107 additions and 26 deletions

View File

@@ -1,7 +1,6 @@
--- # 项目相关配置 --- # 项目相关配置
qingyun: qingyun:
# 项目根目录 profile: /application/data/localUpload/carUpload
profile: ${root.directory}
# projectUrl: http://192.168.3.16:8081 # projectUrl: http://192.168.3.16:8081
projectUrl: http://localhost:8099 projectUrl: http://localhost:8099
--- # 监控中心配置 --- # 监控中心配置
@@ -145,10 +144,10 @@ security:
# - /actuator/** # - /actuator/**
# 验证码 # 验证码
- /**/captcha/** - /**/captcha/**
- /system/oss/uploadLocal - /application/data/system/oss/uploadLocal
- /api/service/reviewTask/** - /application/data/api/service/reviewTask/**
- /api/pdf/** - /application/data/api/pdf/**
- /api/service/compareTask/** - /application/data/api/service/compareTask/**
# orc识别服务 # orc识别服务
ocr: ocr:

View File

@@ -1,10 +1,19 @@
--- # 项目相关配置 --- # 项目相关配置
qingyun: qingyun:
profile: /opt/workspace/localUpload/carUpload profile: /application/data/localUpload/carUpload
# projectUrl: http://192.168.3.16:8081
projectUrl: http://localhost:8099 projectUrl: http://localhost:8099
# projectUrl: --- # 监控中心配置
--- # 临时文件存储位置 避免临时文件被系统清理报错 spring.boot.admin.client:
spring.servlet.multipart.location: /qingyun/server/temp # 增加客户端开关
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: qingyun
password: 123456
--- # 数据源配置 --- # 数据源配置
spring: spring:
@@ -13,7 +22,7 @@ spring:
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
dynamic: dynamic:
# 性能分析插件(有性能损耗 不建议生产环境使用) # 性能分析插件(有性能损耗 不建议生产环境使用)
p6spy: false p6spy: true
# 设置默认的数据源或者数据源组,默认值即为 master # 设置默认的数据源或者数据源组,默认值即为 master
primary: master primary: master
# 严格模式 匹配不到数据源则报错 # 严格模式 匹配不到数据源则报错
@@ -74,17 +83,17 @@ redisson:
# redis key前缀 # redis key前缀
keyPrefix: keyPrefix:
# 线程池数量 # 线程池数量
threads: 16 threads: 4
# Netty线程池数量 # Netty线程池数量
nettyThreads: 32 nettyThreads: 8
# 单节点配置 # 单节点配置
singleServerConfig: singleServerConfig:
# 客户端名称 # 客户端名称
clientName: ${qingyun.name} clientName: ${qingyun.name}
# 最小空闲连接数 # 最小空闲连接数
connectionMinimumIdleSize: 32 connectionMinimumIdleSize: 8
# 连接池大小 # 连接池大小
connectionPoolSize: 64 connectionPoolSize: 32
# 连接空闲超时,单位:毫秒 # 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000 idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒 # 命令等待超时,单位:毫秒
@@ -95,7 +104,7 @@ redisson:
springdoc: springdoc:
api-docs: api-docs:
# 是否开启接口文档 # 是否开启接口文档
enabled: false enabled: true
swagger-ui: swagger-ui:
path: /doc.html path: /doc.html
# 持久化认证数据 # 持久化认证数据
@@ -114,6 +123,7 @@ springdoc:
- group: 4.合同业务管理 - group: 4.合同业务管理
packages-to-scan: com.qingyun.web.controller.api.contract packages-to-scan: com.qingyun.web.controller.api.contract
# security配置 # security配置
security: security:
# 排除路径 # 排除路径
@@ -126,13 +136,19 @@ security:
# 公共路径 # 公共路径
- /favicon.ico - /favicon.ico
- /error - /error
# swagger 文档配置
- /*/api-docs
- /*/api-docs/**
# # actuator 监控配置
# - /actuator
# - /actuator/**
# 验证码 # 验证码
- /**/captcha/** - /**/captcha/**
- /system/oss/uploadLocal - /application/data/system/oss/uploadLocal
- /api/service/reviewTask/** - /application/data/api/service/reviewTask/**
- /api/pdf/** - /application/data/api/pdf/**
- /api/service/compareTask/** - /application/data/api/service/compareTask/**
# orc识别服务 # orc识别服务
ocr: ocr:
url: http://10.77.149.156:8188/layout-parsing url: http://10.78.113.249:8080/layout-parsing

View File

@@ -131,7 +131,7 @@ public class FileUploadUtils
*/ */
public static String extractFilename(MultipartFile file) public static String extractFilename(MultipartFile file)
{ {
String fileName = file.getOriginalFilename(); String fileName = FilenameUtils.getName(file.getOriginalFilename());
String extension = getExtension(file); String extension = getExtension(file);
fileName = DateUtils.datePath() + "/" + IdUtil.fastUUID() + "/" + fileName; fileName = DateUtils.datePath() + "/" + IdUtil.fastUUID() + "/" + fileName;
return fileName; return fileName;

View File

@@ -10,10 +10,12 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
/** /**
* 策略工厂(根据文件类型选择策略) * 策略工厂(根据文件类型选择策略)
*/ */
@Component @Component
@Slf4j
public class FileComparisonStrategyFactory { public class FileComparisonStrategyFactory {
private final List<FileComparisonStrategy> strategies; private final List<FileComparisonStrategy> strategies;
@@ -36,12 +38,14 @@ public class FileComparisonStrategyFactory {
String ext1 = getFileExtension(task.getFirstContractUrl()); String ext1 = getFileExtension(task.getFirstContractUrl());
String ext2 = getFileExtension(task.getSecondContractUrl()); String ext2 = getFileExtension(task.getSecondContractUrl());
log.info("选择比对策略,扩展名: first={}, second={}", ext1, ext2);
if (!ext1.equalsIgnoreCase(ext2)) { if (!ext1.equalsIgnoreCase(ext2)) {
throw new ServiceException("两份文件类型不一致,无法对比"); throw new ServiceException("两份文件类型不一致,无法对比");
} }
for (FileComparisonStrategy strategy : strategies) { for (FileComparisonStrategy strategy : strategies) {
if (strategy.supports(ext1)) { if (strategy.supports(ext1)) {
log.info("匹配到策略: {}", strategy.getClass().getSimpleName());
return strategy; return strategy;
} }
} }

View File

@@ -212,6 +212,14 @@ public class WordComparisonStrategy extends AbstractFileComparisonStrategy imple
pdfFile = targetFile; 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文件路径 // 更新合同URL为新的PDF文件路径
String newUrl = "/profile/upload/" + needUploadName; String newUrl = "/profile/upload/" + needUploadName;

View File

@@ -6,6 +6,7 @@ import com.documents4j.api.IConverter;
import com.documents4j.job.LocalConverter; import com.documents4j.job.LocalConverter;
import com.qingyun.common.config.QingYunConfig; import com.qingyun.common.config.QingYunConfig;
import com.qingyun.common.utils.file.FileUploadUtils; import com.qingyun.common.utils.file.FileUploadUtils;
import com.qingyun.common.constant.Constants;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
@@ -19,6 +20,8 @@ import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@@ -99,9 +102,37 @@ public class PdfUtil {
String newName = name.substring(0, name.lastIndexOf(".")); String newName = name.substring(0, name.lastIndexOf("."));
String projectUrl = QingYunConfig.getProjectUrl(); String projectUrl = QingYunConfig.getProjectUrl();
File tempFile = File.createTempFile(newName + System.currentTimeMillis(), getFileExtension(name)); 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; return tempFile;
} }
@@ -126,6 +157,16 @@ public class PdfUtil {
File outputFile = new File(wordFile.getParent(), File outputFile = new File(wordFile.getParent(),
FilenameUtils.getBaseName(wordFile.getName()) + ".pdf"); FilenameUtils.getBaseName(wordFile.getName()) + ".pdf");
/**
* 将 Word 文件转换为 PDFWindows
* - 使用 documents4j 本地转换器进行格式转换
* - 转换完成后进行健全性校验:文件存在、非空、可被 PDFBox 正常加载
* - 若校验失败则抛出 IOException避免后续流程在解析阶段失败
*
* @param wordFile 待转换的 Word 临时文件
* @return 转换后的 PDF 文件对象
* @throws IOException 当转换或校验失败时抛出
*/
try { try {
IConverter converter = LocalConverter.builder().build(); IConverter converter = LocalConverter.builder().build();
@@ -134,7 +175,20 @@ public class PdfUtil {
.to(outputFile).as(DocumentType.PDF) .to(outputFile).as(DocumentType.PDF)
.prioritizeWith(1000) // optional .prioritizeWith(1000) // optional
.schedule(); .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; return outputFile;
} catch (Exception e) { } catch (Exception e) {