|
|
@@ -2,11 +2,13 @@ package com.lingyue.parse.service;
|
|
|
|
|
|
import com.lingyue.parse.config.FileStorageProperties;
|
|
|
import com.lingyue.parse.entity.ParseTask;
|
|
|
+import com.lingyue.parse.enums.FileType;
|
|
|
import com.lingyue.parse.repository.ParseTaskRepository;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
+import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.nio.file.Files;
|
|
|
@@ -24,7 +26,13 @@ public class ParseService {
|
|
|
|
|
|
private final ParseTaskRepository parseTaskRepository;
|
|
|
private final PaddleOcrClient paddleOcrClient;
|
|
|
+ private final PdfTextExtractionService pdfTextExtractionService;
|
|
|
+ private final WordTextExtractionService wordTextExtractionService;
|
|
|
+ private final ExcelTextExtractionService excelTextExtractionService;
|
|
|
+ private final OcrResultParser ocrResultParser;
|
|
|
+ private final LayoutAnalysisService layoutAnalysisService;
|
|
|
private final FileStorageProperties fileStorageProperties;
|
|
|
+ private final com.lingyue.parse.client.GraphServiceClient graphServiceClient;
|
|
|
|
|
|
/**
|
|
|
* 根据ID获取解析任务
|
|
|
@@ -57,49 +65,128 @@ public class ParseService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 对指定文档执行 OCR 并将结果写入 TXT 文件
|
|
|
- *
|
|
|
- * 当前实现为同步调用,后续可以改为异步任务。
|
|
|
+ * 对指定文档执行解析并将结果写入 TXT 文件
|
|
|
+ * 根据文件类型选择不同的处理方式:
|
|
|
+ * - PDF: 使用分页判断逻辑(有文本层直接提取,无文本层使用OCR)
|
|
|
+ * - 图片: 使用OCR
|
|
|
+ * - 其他: 使用OCR
|
|
|
*
|
|
|
* @param documentId 文档ID
|
|
|
* @param sourceFilePath 原始文件路径
|
|
|
+ * @param fileType 文件类型
|
|
|
* @return 更新后的解析任务
|
|
|
*/
|
|
|
- public ParseTask runOcrAndSaveText(String documentId, String sourceFilePath) {
|
|
|
+ public ParseTask parseAndSaveText(String documentId, String sourceFilePath, FileType fileType) {
|
|
|
// 1. 初始化或更新解析任务
|
|
|
ParseTask task = getOrCreateTask(documentId);
|
|
|
task.setStatus("processing");
|
|
|
- task.setCurrentStep("ocr");
|
|
|
+ task.setCurrentStep("parsing");
|
|
|
task.setProgress(10);
|
|
|
saveParseTask(task);
|
|
|
|
|
|
try {
|
|
|
- // 2. 调用 OCR 服务
|
|
|
- String ocrResult = paddleOcrClient.ocrFile(sourceFilePath);
|
|
|
-
|
|
|
- // TODO: 根据实际返回结构,将 ocrResult 解析为纯文本
|
|
|
- String plainText = extractPlainTextFromOcrResult(ocrResult);
|
|
|
+ String plainText;
|
|
|
+
|
|
|
+ // 2. 根据文件类型选择处理方式
|
|
|
+ if (fileType == FileType.PDF) {
|
|
|
+ log.info("处理PDF文件: {}", sourceFilePath);
|
|
|
+ task.setCurrentStep("pdf_extraction");
|
|
|
+ task.setProgress(20);
|
|
|
+ saveParseTask(task);
|
|
|
+
|
|
|
+ // PDF使用分页判断逻辑
|
|
|
+ plainText = pdfTextExtractionService.extractText(sourceFilePath);
|
|
|
+ } else if (fileType == FileType.WORD || fileType == FileType.WORD_OLD) {
|
|
|
+ log.info("处理Word文件: {}", sourceFilePath);
|
|
|
+ task.setCurrentStep("word_extraction");
|
|
|
+ task.setProgress(20);
|
|
|
+ saveParseTask(task);
|
|
|
+
|
|
|
+ // Word文档直接提取文本
|
|
|
+ plainText = wordTextExtractionService.extractText(sourceFilePath);
|
|
|
+ } else if (fileType == FileType.EXCEL || fileType == FileType.EXCEL_OLD) {
|
|
|
+ log.info("处理Excel文件: {}", sourceFilePath);
|
|
|
+ task.setCurrentStep("excel_extraction");
|
|
|
+ task.setProgress(20);
|
|
|
+ saveParseTask(task);
|
|
|
+
|
|
|
+ // Excel表格直接提取文本
|
|
|
+ plainText = excelTextExtractionService.extractText(sourceFilePath);
|
|
|
+ } else if (fileType.isImage()) {
|
|
|
+ log.info("处理图片文件: {}", sourceFilePath);
|
|
|
+ task.setCurrentStep("ocr");
|
|
|
+ task.setProgress(20);
|
|
|
+ saveParseTask(task);
|
|
|
+
|
|
|
+ // 图片使用OCR
|
|
|
+ String ocrResult = paddleOcrClient.ocrFile(sourceFilePath);
|
|
|
+ plainText = ocrResultParser.parseText(ocrResult);
|
|
|
+ } else {
|
|
|
+ log.info("处理其他文件类型: {}, 使用OCR", fileType);
|
|
|
+ task.setCurrentStep("ocr");
|
|
|
+ task.setProgress(20);
|
|
|
+ saveParseTask(task);
|
|
|
+
|
|
|
+ // 其他文件类型使用OCR
|
|
|
+ String ocrResult = paddleOcrClient.ocrFile(sourceFilePath);
|
|
|
+ plainText = ocrResultParser.parseText(ocrResult);
|
|
|
+ }
|
|
|
|
|
|
// 3. 将纯文本写入 TXT 文件
|
|
|
+ task.setCurrentStep("saving");
|
|
|
+ task.setProgress(80);
|
|
|
+ saveParseTask(task);
|
|
|
+
|
|
|
String textFilePath = buildTextFilePath(documentId);
|
|
|
try {
|
|
|
writeTextToFile(textFilePath, plainText);
|
|
|
} catch (IOException ioException) {
|
|
|
- log.error("写入 OCR 文本到 TXT 文件失败, path={}", textFilePath, ioException);
|
|
|
- throw new RuntimeException("写入 OCR 文本失败: " + ioException.getMessage(), ioException);
|
|
|
+ log.error("写入文本到 TXT 文件失败, path={}", textFilePath, ioException);
|
|
|
+ throw new RuntimeException("写入文本失败: " + ioException.getMessage(), ioException);
|
|
|
+ }
|
|
|
+ log.info("文本已写入: {}", textFilePath);
|
|
|
+
|
|
|
+ // 4. 版面分析
|
|
|
+ task.setCurrentStep("layout_analysis");
|
|
|
+ task.setProgress(85);
|
|
|
+ saveParseTask(task);
|
|
|
+
|
|
|
+ try {
|
|
|
+ LayoutAnalysisService.LayoutAnalysisResult layoutResult =
|
|
|
+ layoutAnalysisService.analyzeLayout(sourceFilePath, fileType, plainText);
|
|
|
+ log.info("版面分析完成: 识别到 {} 个元素", layoutResult.getElementCount());
|
|
|
+
|
|
|
+ // 将版面分析结果保存到任务选项(可选,用于后续图节点构建)
|
|
|
+ if (task.getOptions() == null) {
|
|
|
+ task.setOptions(new java.util.HashMap<>());
|
|
|
+ }
|
|
|
+ java.util.Map<String, Object> options = (java.util.Map<String, Object>) task.getOptions();
|
|
|
+ options.put("layoutAnalysis", layoutResult);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("版面分析失败,但不影响主流程: documentId={}", documentId, e);
|
|
|
+ // 版面分析失败不影响主流程,只记录警告日志
|
|
|
}
|
|
|
- log.info("OCR 文本已写入: {}", textFilePath);
|
|
|
|
|
|
- // TODO: 在此处调用 graph-service 或 document-service 记录 text_storage 信息
|
|
|
+ // 5. 记录文本存储路径到数据库
|
|
|
+ task.setCurrentStep("recording");
|
|
|
+ task.setProgress(90);
|
|
|
+ saveParseTask(task);
|
|
|
+
|
|
|
+ try {
|
|
|
+ recordTextStorage(documentId, textFilePath);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("记录文本存储路径失败,但不影响主流程: documentId={}, filePath={}", documentId, textFilePath, e);
|
|
|
+ // 记录失败不影响主流程,只记录警告日志
|
|
|
+ }
|
|
|
|
|
|
- // 4. 更新任务状态为完成
|
|
|
+ // 6. 更新任务状态为完成
|
|
|
task.setStatus("completed");
|
|
|
task.setCurrentStep("completed");
|
|
|
task.setProgress(100);
|
|
|
task.setCompletedAt(new java.util.Date());
|
|
|
saveParseTask(task);
|
|
|
} catch (Exception e) {
|
|
|
- log.error("执行 OCR 解析任务失败, documentId={}", documentId, e);
|
|
|
+ log.error("执行解析任务失败, documentId={}", documentId, e);
|
|
|
task.setStatus("failed");
|
|
|
task.setCurrentStep("failed");
|
|
|
task.setErrorMessage(e.getMessage());
|
|
|
@@ -109,6 +196,33 @@ public class ParseService {
|
|
|
|
|
|
return task;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对指定文档执行 OCR 并将结果写入 TXT 文件(兼容旧接口)
|
|
|
+ *
|
|
|
+ * @param documentId 文档ID
|
|
|
+ * @param sourceFilePath 原始文件路径
|
|
|
+ * @return 更新后的解析任务
|
|
|
+ */
|
|
|
+ @Deprecated
|
|
|
+ public ParseTask runOcrAndSaveText(String documentId, String sourceFilePath) {
|
|
|
+ // 自动检测文件类型
|
|
|
+ FileType fileType = detectFileType(sourceFilePath);
|
|
|
+ return parseAndSaveText(documentId, sourceFilePath, fileType);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检测文件类型
|
|
|
+ */
|
|
|
+ private FileType detectFileType(String filePath) {
|
|
|
+ File file = new File(filePath);
|
|
|
+ String fileName = file.getName();
|
|
|
+ String extension = "";
|
|
|
+ if (fileName.contains(".")) {
|
|
|
+ extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
|
|
|
+ }
|
|
|
+ return FileType.fromExtension(extension);
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* 获取或创建解析任务
|
|
|
@@ -147,17 +261,41 @@ public class ParseService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 从 OCR 返回结果中提取纯文本
|
|
|
- *
|
|
|
- * 这里先做一个简单占位实现:直接返回原始 JSON,
|
|
|
- * 后续可以根据你本地 GPU 服务的返回结构进行 JSON 解析与拼接。
|
|
|
+ * 从 OCR 返回结果中提取纯文本(兼容旧接口)
|
|
|
+ *
|
|
|
+ * @deprecated 使用 OcrResultParser.parseText() 替代
|
|
|
*/
|
|
|
+ @Deprecated
|
|
|
private String extractPlainTextFromOcrResult(String ocrResult) {
|
|
|
- // TODO: 根据实际返回结构提取文字内容
|
|
|
- return ocrResult == null ? "" : ocrResult;
|
|
|
+ return ocrResultParser.parseText(ocrResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录文本存储路径到数据库
|
|
|
+ *
|
|
|
+ * @param documentId 文档ID
|
|
|
+ * @param textFilePath 文本文件路径
|
|
|
+ */
|
|
|
+ private void recordTextStorage(String documentId, String textFilePath) {
|
|
|
+ try {
|
|
|
+ java.util.Map<String, Object> request = new java.util.HashMap<>();
|
|
|
+ request.put("documentId", documentId);
|
|
|
+ request.put("filePath", textFilePath);
|
|
|
+
|
|
|
+ com.lingyue.common.domain.AjaxResult<?> result = graphServiceClient.saveTextStorage(request);
|
|
|
+
|
|
|
+ if (result != null && result.getCode() == 200) {
|
|
|
+ log.info("文本存储路径记录成功: documentId={}, filePath={}", documentId, textFilePath);
|
|
|
+ } else {
|
|
|
+ log.warn("文本存储路径记录失败: documentId={}, filePath={}, result={}",
|
|
|
+ documentId, textFilePath, result);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用graph-service记录文本存储路径异常", e);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // TODO: 实现版面分析
|
|
|
// TODO: 实现异步任务处理(MQ / 线程池等)
|
|
|
}
|
|
|
|