20260607211212
This commit is contained in:
1 parent
31dff90725
commit
4da3c88400
42 files changed
+795
No files matched your search
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,39 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
.kotlin
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
|
||||
#Sun Jun 07 19:41:40 CST 2026
|
||||
@default-aliyunmaven-https\://maven.aliyun.com/repository/central/.lastUpdated=1780832500060
|
||||
https\://maven.aliyun.com/repository/central/.error=Could not transfer artifact org.springframework.boot\:spring-boot-starter-parent\:pom\:4.0.6 from/to aliyunmaven (https\://maven.aliyun.com/repository/central)\: Permission denied\: getsockopt
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>4.0.6</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>springboot4-api</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>springboot4-api</name>
|
||||
<description>Spring Boot 4 REST API project</description>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Spring MVC -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webmvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 参数校验 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- OkHttp -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>5.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson(如果IDE识别不到可显式添加) -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<argLine>-Xshare:off -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.example;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Base64;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
String apiUrl = "http://localhost:8866/layout-parsing";
|
||||
String pdfPath = "D:/workspace/bgd/bgd-01.pdf";
|
||||
|
||||
// 读取PDF
|
||||
File file = new File(pdfPath);
|
||||
byte[] bytes = Files.readAllBytes(file.toPath());
|
||||
|
||||
// Base64编码
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
|
||||
// JSON请求体
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
ObjectNode payload = mapper.createObjectNode();
|
||||
payload.put("file", base64);
|
||||
payload.put("fileType", 0);
|
||||
|
||||
// 打印请求大小
|
||||
System.out.println("Base64 Length: " + base64.length());
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
HttpEntity<String> request =
|
||||
new HttpEntity<>(payload.toString(), headers);
|
||||
|
||||
ResponseEntity<String> response =
|
||||
restTemplate.exchange(
|
||||
apiUrl,
|
||||
HttpMethod.POST,
|
||||
request,
|
||||
String.class
|
||||
);
|
||||
|
||||
System.out.println("HTTP Status: "
|
||||
+ response.getStatusCode());
|
||||
|
||||
String responseBody = response.getBody();
|
||||
|
||||
System.out.println("Response:");
|
||||
System.out.println(responseBody);
|
||||
|
||||
// 解析返回结果
|
||||
JsonNode root = mapper.readTree(responseBody);
|
||||
|
||||
JsonNode result = root.path("result");
|
||||
|
||||
JsonNode layoutParsingResults =
|
||||
result.path("layoutParsingResults");
|
||||
|
||||
for (int i = 0; i < layoutParsingResults.size(); i++) {
|
||||
|
||||
JsonNode item = layoutParsingResults.get(i);
|
||||
|
||||
System.out.println(
|
||||
"\n========== Page " + i + " =========="
|
||||
);
|
||||
|
||||
JsonNode prunedResult =
|
||||
item.path("prunedResult");
|
||||
|
||||
System.out.println(
|
||||
mapper.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(prunedResult)
|
||||
);
|
||||
|
||||
JsonNode outputImages =
|
||||
item.path("outputImages");
|
||||
|
||||
outputImages.fieldNames()
|
||||
.forEachRemaining(imgName -> {
|
||||
|
||||
try {
|
||||
|
||||
String imgBase64 =
|
||||
outputImages.get(imgName)
|
||||
.asText();
|
||||
|
||||
byte[] imgBytes =
|
||||
Base64.getDecoder()
|
||||
.decode(imgBase64);
|
||||
|
||||
String imgPath =
|
||||
imgName + "_" + i + ".jpg";
|
||||
|
||||
try (FileOutputStream fos =
|
||||
new FileOutputStream(imgPath)) {
|
||||
|
||||
fos.write(imgBytes);
|
||||
}
|
||||
|
||||
System.out.println(
|
||||
"Saved image: " + imgPath
|
||||
);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
System.err.println(
|
||||
"Save image failed: "
|
||||
+ e.getMessage()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.example;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Springboot4ApiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Springboot4ApiApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.example.api;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class HealthController {
|
||||
|
||||
@GetMapping("/health")
|
||||
public HealthResponse health() {
|
||||
return new HealthResponse("UP", Instant.now());
|
||||
}
|
||||
|
||||
public record HealthResponse(String status, Instant timestamp) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.example.api.layout;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/layout-parsing")
|
||||
public class LayoutParsingController {
|
||||
|
||||
private final LayoutParsingUseCase layoutParsingUseCase;
|
||||
private final String defaultFilePath;
|
||||
|
||||
public LayoutParsingController(
|
||||
LayoutParsingUseCase layoutParsingUseCase,
|
||||
@Value("${layout.parsing.default-file-path}") String defaultFilePath
|
||||
) {
|
||||
this.layoutParsingUseCase = layoutParsingUseCase;
|
||||
this.defaultFilePath = defaultFilePath;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public LayoutParsingResponse parseDefault() {
|
||||
LayoutParsingRequest request = LayoutParsingRequest.pdf(defaultFilePath);
|
||||
return layoutParsingUseCase.parse(request);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public LayoutParsingResponse parse(@Valid @RequestBody LayoutParsingRequest request) {
|
||||
return layoutParsingUseCase.parse(request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.example.api.layout;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public record LayoutParsingRequest(
|
||||
String filePath,
|
||||
String imagePath,
|
||||
Integer fileType,
|
||||
String outputDirectory,
|
||||
Boolean useDocOrientationClassify,
|
||||
Boolean useDocUnwarping,
|
||||
Boolean useTextlineOrientation,
|
||||
Boolean useSealRecognition,
|
||||
Boolean useTableRecognition,
|
||||
Boolean useFormulaRecognition,
|
||||
Boolean useChartRecognition,
|
||||
Boolean useRegionDetection,
|
||||
Boolean formatBlockContent,
|
||||
Boolean returnMarkdownImages,
|
||||
Boolean visualize,
|
||||
List<String> outputFormats
|
||||
) {
|
||||
|
||||
public static LayoutParsingRequest pdf(String filePath) {
|
||||
return new LayoutParsingRequest(
|
||||
filePath,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
public String resolvedFilePath() {
|
||||
if (hasText(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
if (hasText(imagePath)) {
|
||||
return imagePath;
|
||||
}
|
||||
throw new IllegalArgumentException("filePath is required");
|
||||
}
|
||||
|
||||
public int resolvedFileType() {
|
||||
if (fileType != null) {
|
||||
return fileType;
|
||||
}
|
||||
|
||||
String fileName = Path.of(resolvedFilePath()).getFileName().toString().toLowerCase();
|
||||
return fileName.endsWith(".pdf") ? 0 : 1;
|
||||
}
|
||||
|
||||
private boolean hasText(String value) {
|
||||
return value != null && !value.isBlank();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.example.api.layout;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import tools.jackson.databind.JsonNode;
|
||||
|
||||
public record LayoutParsingResponse(
|
||||
String filePath,
|
||||
int fileType,
|
||||
List<Item> items
|
||||
) {
|
||||
|
||||
public record Item(
|
||||
JsonNode prunedResult,
|
||||
String recognizedText,
|
||||
String markdownText,
|
||||
String savedPrunedResult,
|
||||
String savedMarkdown,
|
||||
String savedText,
|
||||
List<String> savedImages
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package org.example.api.layout;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
@Service
|
||||
public class LayoutParsingService implements LayoutParsingUseCase {
|
||||
|
||||
private final RestClient restClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final String apiUrl;
|
||||
private final Path defaultOutputDirectory;
|
||||
|
||||
public LayoutParsingService(
|
||||
ObjectMapper objectMapper,
|
||||
@Value("${layout.parsing.api-url}") String apiUrl,
|
||||
@Value("${layout.parsing.output-directory:output}") String defaultOutputDirectory
|
||||
) {
|
||||
this.restClient = RestClient.create();
|
||||
this.objectMapper = objectMapper;
|
||||
this.apiUrl = apiUrl;
|
||||
this.defaultOutputDirectory = Path.of(defaultOutputDirectory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutParsingResponse parse(LayoutParsingRequest request) {
|
||||
Path filePath = Path.of(request.resolvedFilePath()).toAbsolutePath().normalize();
|
||||
if (!Files.isRegularFile(filePath)) {
|
||||
throw new IllegalArgumentException("File does not exist: " + filePath);
|
||||
}
|
||||
|
||||
JsonNode root = callLayoutParsingApi(filePath, request);
|
||||
JsonNode layoutParsingResults = root.path("result").path("layoutParsingResults");
|
||||
if (!layoutParsingResults.isArray()) {
|
||||
throw new IllegalStateException("Invalid layout parsing response: result.layoutParsingResults is missing");
|
||||
}
|
||||
|
||||
Path outputDirectory = resolveOutputDirectory(request.outputDirectory());
|
||||
List<LayoutParsingResponse.Item> items = new ArrayList<>();
|
||||
System.out.println("Layout parsing file: " + filePath);
|
||||
System.out.println("Layout parsing result count: " + layoutParsingResults.size());
|
||||
for (int i = 0; i < layoutParsingResults.size(); i++) {
|
||||
JsonNode item = layoutParsingResults.get(i);
|
||||
JsonNode prunedResult = item.path("prunedResult");
|
||||
String markdownText = item.path("markdown").path("text").asText();
|
||||
String recognizedText = extractRecognizedText(prunedResult);
|
||||
String savedPrunedResult = savePrunedResult(prunedResult, outputDirectory, i);
|
||||
String savedMarkdown = saveTextFile(markdownText, outputDirectory, "markdown_" + i + ".md");
|
||||
String savedText = saveTextFile(recognizedText, outputDirectory, "recognized_text_" + i + ".txt");
|
||||
List<String> savedImages = saveOutputImages(item.path("outputImages"), outputDirectory, i);
|
||||
System.out.println("Pruned Result [" + i + "]: " + prunedResult);
|
||||
System.out.println("Recognized Text [" + i + "]:");
|
||||
System.out.println(recognizedText);
|
||||
System.out.println("Markdown Text [" + i + "]:");
|
||||
System.out.println(markdownText);
|
||||
System.out.println("Saved pruned result: " + savedPrunedResult);
|
||||
System.out.println("Saved markdown: " + savedMarkdown);
|
||||
System.out.println("Saved text: " + savedText);
|
||||
savedImages.forEach(savedImage -> System.out.println("Saved image: " + savedImage));
|
||||
items.add(new LayoutParsingResponse.Item(
|
||||
prunedResult,
|
||||
recognizedText,
|
||||
markdownText,
|
||||
savedPrunedResult,
|
||||
savedMarkdown,
|
||||
savedText,
|
||||
savedImages
|
||||
));
|
||||
}
|
||||
|
||||
return new LayoutParsingResponse(filePath.toString(), request.resolvedFileType(), items);
|
||||
}
|
||||
|
||||
private JsonNode callLayoutParsingApi(Path filePath, LayoutParsingRequest request) {
|
||||
try {
|
||||
String base64File = Base64.getEncoder().encodeToString(Files.readAllBytes(filePath));
|
||||
|
||||
ObjectNode payload = objectMapper.createObjectNode();
|
||||
payload.put("file", base64File);
|
||||
payload.put("fileType", request.resolvedFileType());
|
||||
putIfNotNull(payload, "useDocOrientationClassify", request.useDocOrientationClassify());
|
||||
putIfNotNull(payload, "useDocUnwarping", request.useDocUnwarping());
|
||||
putIfNotNull(payload, "useTextlineOrientation", request.useTextlineOrientation());
|
||||
putIfNotNull(payload, "useSealRecognition", request.useSealRecognition());
|
||||
putIfNotNull(payload, "useTableRecognition", request.useTableRecognition());
|
||||
putIfNotNull(payload, "useFormulaRecognition", request.useFormulaRecognition());
|
||||
putIfNotNull(payload, "useChartRecognition", request.useChartRecognition());
|
||||
putIfNotNull(payload, "useRegionDetection", request.useRegionDetection());
|
||||
putIfNotNull(payload, "formatBlockContent", request.formatBlockContent());
|
||||
putIfNotNull(payload, "returnMarkdownImages", request.returnMarkdownImages());
|
||||
putIfNotNull(payload, "visualize", request.visualize());
|
||||
if (request.outputFormats() != null && !request.outputFormats().isEmpty()) {
|
||||
payload.set("outputFormats", objectMapper.valueToTree(request.outputFormats()));
|
||||
}
|
||||
|
||||
return restClient.post()
|
||||
.uri(apiUrl)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(payload)
|
||||
.retrieve()
|
||||
.body(JsonNode.class);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to read file: " + filePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void putIfNotNull(ObjectNode payload, String name, Boolean value) {
|
||||
if (value != null) {
|
||||
payload.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private Path resolveOutputDirectory(String outputDirectory) {
|
||||
Path directory = StringUtils.hasText(outputDirectory)
|
||||
? Path.of(outputDirectory)
|
||||
: defaultOutputDirectory;
|
||||
try {
|
||||
Files.createDirectories(directory);
|
||||
return directory.toAbsolutePath().normalize();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to create output directory: " + directory, e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> saveOutputImages(JsonNode outputImages, Path outputDirectory, int resultIndex) {
|
||||
List<String> savedImages = new ArrayList<>();
|
||||
if (!outputImages.isObject()) {
|
||||
return savedImages;
|
||||
}
|
||||
|
||||
Iterator<Entry<String, JsonNode>> fields = outputImages.properties().iterator();
|
||||
while (fields.hasNext()) {
|
||||
Entry<String, JsonNode> field = fields.next();
|
||||
String imageName = sanitizeFileName(field.getKey());
|
||||
Path outputPath = outputDirectory.resolve(imageName + "_" + resultIndex + ".jpg");
|
||||
try {
|
||||
byte[] imageBytes = Base64.getDecoder().decode(field.getValue().asText());
|
||||
Files.write(outputPath, imageBytes);
|
||||
savedImages.add(outputPath.toString());
|
||||
} catch (IllegalArgumentException | IOException e) {
|
||||
throw new IllegalStateException("Failed to save output image: " + outputPath, e);
|
||||
}
|
||||
}
|
||||
return savedImages;
|
||||
}
|
||||
|
||||
private String savePrunedResult(JsonNode prunedResult, Path outputDirectory, int resultIndex) {
|
||||
Path outputPath = outputDirectory.resolve("pruned_result_" + resultIndex + ".json");
|
||||
try {
|
||||
Files.writeString(outputPath, prunedResult.toString());
|
||||
return outputPath.toString();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to save pruned result: " + outputPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String saveTextFile(String content, Path outputDirectory, String fileName) {
|
||||
if (!StringUtils.hasText(content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Path outputPath = outputDirectory.resolve(fileName);
|
||||
try {
|
||||
Files.writeString(outputPath, content);
|
||||
return outputPath.toString();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to save text file: " + outputPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String extractRecognizedText(JsonNode prunedResult) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
|
||||
JsonNode parsingResults = prunedResult.path("parsing_res_list");
|
||||
if (parsingResults.isArray()) {
|
||||
for (JsonNode block : parsingResults) {
|
||||
String blockContent = block.path("block_content").asText();
|
||||
if (StringUtils.hasText(blockContent)) {
|
||||
lines.add(blockContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode recognizedTexts = prunedResult.path("overall_ocr_res").path("rec_texts");
|
||||
if (lines.isEmpty() && recognizedTexts.isArray()) {
|
||||
for (JsonNode text : recognizedTexts) {
|
||||
String value = text.asText();
|
||||
if (StringUtils.hasText(value)) {
|
||||
lines.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return String.join(System.lineSeparator(), lines);
|
||||
}
|
||||
|
||||
private String sanitizeFileName(String value) {
|
||||
String sanitized = value.replaceAll("[^a-zA-Z0-9._-]", "_");
|
||||
return StringUtils.hasText(sanitized) ? sanitized : "image";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.example.api.layout;
|
||||
|
||||
public interface LayoutParsingUseCase {
|
||||
|
||||
LayoutParsingResponse parse(LayoutParsingRequest request);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
### Send POST request with json body
|
||||
POST http://localhost:8866/layout-parsing
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"file": "D:\\workspace\\bgd\\bgd-01.pdf",
|
||||
"fileType": 0
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
spring:
|
||||
application:
|
||||
name: springboot4-api
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
layout:
|
||||
parsing:
|
||||
api-url: http://localhost:8866/layout-parsing
|
||||
default-file-path: D:/workspace/bgd/bgd-01.pdf
|
||||
output-directory: output
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.example;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class Springboot4ApiApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.example.api;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@WebMvcTest(HealthController.class)
|
||||
class HealthControllerTests {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void healthReturnsUp() throws Exception {
|
||||
mockMvc.perform(get("/api/health"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status", is("UP")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.example.api.layout;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@WebMvcTest(LayoutParsingController.class)
|
||||
class LayoutParsingControllerTests {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Test
|
||||
void parseAcceptsLocalPdfPath() throws Exception {
|
||||
String request = """
|
||||
{
|
||||
"filePath": "D:/workspace/bgd/bgd-01.pdf",
|
||||
"outputDirectory": "output",
|
||||
"returnMarkdownImages": false,
|
||||
"visualize": false
|
||||
}
|
||||
""";
|
||||
|
||||
mockMvc.perform(post("/api/layout-parsing")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(request))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.filePath", is("D:/workspace/bgd/bgd-01.pdf")))
|
||||
.andExpect(jsonPath("$.fileType", is(0)))
|
||||
.andExpect(jsonPath("$.items[0].recognizedText", is("demo text")))
|
||||
.andExpect(jsonPath("$.items[0].markdownText", is("# demo")))
|
||||
.andExpect(jsonPath("$.items[0].savedPrunedResult", is("output/pruned_result_0.json")))
|
||||
.andExpect(jsonPath("$.items[0].savedMarkdown", is("output/markdown_0.md")))
|
||||
.andExpect(jsonPath("$.items[0].savedText", is("output/recognized_text_0.txt")))
|
||||
.andExpect(jsonPath("$.items[0].savedImages[0]", is("output/table_0.jpg")));
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
static class TestConfig {
|
||||
|
||||
@Bean
|
||||
LayoutParsingUseCase layoutParsingUseCase(ObjectMapper objectMapper) {
|
||||
return request -> new LayoutParsingResponse(
|
||||
request.resolvedFilePath(),
|
||||
request.resolvedFileType(),
|
||||
List.of(new LayoutParsingResponse.Item(
|
||||
objectMapper.createObjectNode().put("text", "demo"),
|
||||
"demo text",
|
||||
"# demo",
|
||||
"output/pruned_result_0.json",
|
||||
"output/markdown_0.md",
|
||||
"output/recognized_text_0.txt",
|
||||
List.of("output/table_0.jpg")
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user