Przeglądaj źródła

fix(report): 上传非PDF文件时采集链路提前拦截,避免PyPDF2 EOF错误

舌头照片等图片被当成PDF走采集流程,指纹引擎/Opencode全部失败。
在collect()入口和detect()增加魔数预检,非PDF直接标记失败。
Sisyphus 6 dni temu
rodzic
commit
ee42fc5358

+ 24 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ReportCollectService.java

@@ -15,6 +15,7 @@ import org.springframework.web.client.RestTemplate;
 import javax.annotation.Resource;
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.InputStreamReader;
 import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
@@ -134,6 +135,14 @@ public class ReportCollectService {
                 healthReportDraftService.markCollectFailed(draftId, "无法获取报告文件路径");
                 return;
             }
+            // 文件类型预检:采集链路(指纹引擎/LangGraph/opencode)仅支持 PDF,
+            // 图片等非 PDF 文件直接标记失败,避免 PyPDF2 报 EOF marker not found 等错误
+            if (!isPdfFile(pdfPath)) {
+                log.warn("采集跳过非PDF文件 draftId={}, path={}, filename={}",
+                        draftId, pdfPath, draft.getOriginalFilename());
+                healthReportDraftService.markCollectFailed(draftId, "仅支持PDF报告文件,请上传PDF格式");
+                return;
+            }
 
             // 指纹判定
             Map<String, Object> fp = fingerprintService.detect(pdfPath, draft.getOriginalFilename());
@@ -201,6 +210,21 @@ public class ReportCollectService {
         }
     }
 
+    /**
+     * 检查文件是否为 PDF(通过魔数 %PDF- 判断)。
+     * 指纹引擎、LangGraph 解析、opencode 均要求 PDF 输入。
+     */
+    private boolean isPdfFile(String filePath) {
+        try (FileInputStream fis = new FileInputStream(filePath)) {
+            byte[] header = new byte[5];
+            int read = fis.read(header);
+            return read == 5 && new String(header, StandardCharsets.US_ASCII).startsWith("%PDF-");
+        } catch (Exception e) {
+            log.debug("PDF魔数检查失败 path={}: {}", filePath, e.getMessage());
+            return false;
+        }
+    }
+
     /** 未知类型:交本地 opencode 服务。因 headless serve 仅单轮文本生成可靠且输出有限,
      * 采用分块单轮解析:每一块独立 session、单轮返回该 section 的 JSON,块间互不依赖,
      * 最后合并为完整 Payload,避免单轮输出截断导致整单失败。

+ 20 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/ReportFingerprintService.java

@@ -42,6 +42,14 @@ public class ReportFingerprintService {
      * 检测 PDF 文件,返回匹配的报告类型
      */
     public Map<String, Object> detect(String pdfPath, String fileName) {
+        // 非 PDF 文件:指纹引擎不支持,直接返回 unknown
+        if (!isPdfFile(pdfPath)) {
+            log.debug("非PDF文件跳过指纹检测 path={}", pdfPath);
+            Map<String, Object> fallback = new LinkedHashMap<>();
+            fallback.put("typeId", "unknown");
+            fallback.put("reason", "not_pdf");
+            return fallback;
+        }
         List<ReportTypeRegistry> activeTypes = typeRegistryMapper.selectList(
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ReportTypeRegistry>()
                         .eq(ReportTypeRegistry::getIsActive, true));
@@ -127,8 +135,7 @@ public class ReportFingerprintService {
     /**
      * 获取脚本路径,支持多种搜索位置
      */
-    private String getScriptPath(String scriptName) {
-        String[] searchPaths = {
+    private String getScriptPath(String scriptName) {        String[] searchPaths = {
             "docs/scripts/parsers/" + scriptName,
             "docs/scripts/" + scriptName,
             "../docs/scripts/" + scriptName,
@@ -187,4 +194,15 @@ public class ReportFingerprintService {
         }
         return buf.toString("UTF-8");
     }
+
+    /** 通过魔数 %PDF- 判断文件是否为 PDF。 */
+    private boolean isPdfFile(String filePath) {
+        try (FileInputStream fis = new FileInputStream(filePath)) {
+            byte[] header = new byte[5];
+            int read = fis.read(header);
+            return read == 5 && new String(header, StandardCharsets.US_ASCII).startsWith("%PDF-");
+        } catch (Exception e) {
+            return false;
+        }
+    }
 }