基于GLM-OCR的Java后端服务开发:SpringBoot集成指南
最近在做一个内部文档管理系统,需要批量处理上传的合同、票据图片,把里面的文字信息提取出来。手动录入?效率太低,还容易出错。用现成的OCR服务?要么贵,要么识别率不理想,要么对中文支持不好。
后来,我们把目光投向了GLM-OCR。它在大模型加持下,对复杂版面和中文的识别效果确实让人眼前一亮。但问题来了,怎么把它“搬”进我们自己的Java后端系统里,让它变成一个稳定、高效、能扛住并发请求的服务呢?
这篇文章,我就来分享一下我们团队的做法:如何用SpringBoot,把部署在星图GPU上的GLM-OCR,封装成一个企业级可用的RESTful API服务。整个过程,从环境搭建、接口调用,到图片处理、结果解析,再到性能优化,我都会用代码和实际踩过的坑来给你讲清楚。如果你也在为Java项目集成智能OCR能力而头疼,这篇实战指南应该能帮到你。
1. 项目初始化与环境准备
在开始敲代码之前,我们得先把“舞台”搭好。这里主要分两步:一是创建一个标准的SpringBoot项目,二是确保我们能访问到部署好的GLM-OCR服务。
1.1 创建SpringBoot项目
现在创建SpringBoot项目非常方便,我习惯用 Spring Initializr。根据我们的需求,需要选择以下几个核心依赖:
- Spring Web: 用于构建RESTful API。
- Lombok: 减少Java Bean的模板代码,让代码更简洁。
- Spring Boot DevTools: 开发工具,支持热加载。
- Apache HttpClient或OkHttp3: 用于向后端GLM-OCR服务发起HTTP调用。这里我选择更通用的
HttpClient。
你的pom.xml依赖部分看起来会是这样:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- Apache HttpClient --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> </dependency> <!-- 用于图片处理,如获取图片尺寸 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>1.2 配置GLM-OCR服务连接
假设你的GLM-OCR已经成功部署在星图GPU服务器上,并获得了访问地址,比如http://your-gpu-server-ip:port/v1/ocr。我们需要在SpringBoot的配置文件中管理这个地址,以及可能需要的认证密钥。
在application.yml中配置:
app: glm-ocr: # GLM-OCR服务的API端点 api-url: ${GLM_OCR_API_URL:http://localhost:8000/v1/ocr} # 如果服务端启用了API Key认证,在此配置 api-key: ${GLM_OCR_API_KEY:} # 连接超时和读取超时时间(毫秒) connect-timeout: 5000 read-timeout: 30000 spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB这里用了环境变量${...:default}的写法,好处是安全且灵活。敏感信息如api-key和服务器地址可以通过生产环境的环境变量注入,避免硬编码在代码里。
接下来,我们创建一个配置类来读取这些属性,并提供一个配置好的HttpClientBean。
import lombok.Data; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.util.Timeout; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Data @Configuration @ConfigurationProperties(prefix = "app.glm-ocr") public class GlmOcrConfig { private String apiUrl; private String apiKey; private Integer connectTimeout; private Integer readTimeout; @Bean public CloseableHttpClient glmOcrHttpClient() { // 使用连接池管理HTTP连接,提升性能 var connectionManager = PoolingHttpClientConnectionManagerBuilder.create() .setDefaultSocketConfig(SocketConfig.custom() .setSoTimeout(Timeout.of(readTimeout, TimeUnit.MILLISECONDS)) .build()) .build(); return HttpClients.custom() .setConnectionManager(connectionManager) .setDefaultRequestConfig(org.apache.hc.client5.http.config.RequestConfig.custom() .setConnectTimeout(Timeout.of(connectTimeout, TimeUnit.MILLISECONDS)) .setResponseTimeout(Timeout.of(readTimeout, TimeUnit.MILLISECONDS)) .build()) .build(); } }2. 核心服务层:封装GLM-OCR调用
环境搭好,配置就绪,现在我们来写最核心的部分:一个服务类,专门负责和远端的GLM-OCR API“对话”。
GLM-OCR的接口通常需要以multipart/form-data格式上传图片文件,并接收一个结构化的JSON响应。我们的服务类要完成三件事:构建请求、发送请求、解析响应。
首先,定义两个内部类用来映射请求和响应。这能让我们用对象的方式操作数据,更清晰。
import lombok.Data; @Data public class OcrRequest { // 这里可以根据GLM-OCR API的实际需求添加其他参数 // 例如 language(语言)、detect_direction(检测方向)等 // private String language = "ch"; }import lombok.Data; import java.util.List; @Data public class OcrResponse { private Integer code; // 响应码,通常0表示成功 private String msg; // 响应信息 private Data data; // 识别结果数据 @lombok.Data public static class Data { private List<TextRegion> regions; // 识别出的文本区域列表 } @lombok.Data public static class TextRegion { private String text; // 识别出的文本 private List<List<Integer>> bbox; // 文本区域边界框坐标 [左上x, 左上y, 右下x, 右下y] private Double score; // 识别置信度 } }然后,实现服务类。这里的关键是使用HttpClient构建一个包含图片文件的multipart请求。
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @Slf4j @Service @RequiredArgsConstructor public class GlmOcrService { private final GlmOcrConfig glmOcrConfig; private final CloseableHttpClient glmOcrHttpClient; public OcrResponse recognizeText(MultipartFile imageFile) throws IOException { String apiUrl = glmOcrConfig.getApiUrl(); HttpPost httpPost = new HttpPost(apiUrl); // 1. 构建 multipart 请求体 MultipartEntityBuilder builder = MultipartEntityBuilder.create(); // 添加图片文件部分,字段名通常是 'image' 或 'file',需根据GLM-OCR API文档调整 builder.addBinaryBody("image", imageFile.getInputStream(), ContentType.create(imageFile.getContentType()), imageFile.getOriginalFilename()); // 如果有其他参数,可以添加表单字段 // builder.addTextBody("language", "ch", ContentType.TEXT_PLAIN); httpPost.setEntity(builder.build()); // 2. 添加认证头(如果需要) if (glmOcrConfig.getApiKey() != null && !glmOcrConfig.getApiKey().isEmpty()) { httpPost.setHeader("Authorization", "Bearer " + glmOcrConfig.getApiKey()); } // 3. 发送请求并处理响应 try (var response = glmOcrHttpClient.execute(httpPost)) { String responseBody = EntityUtils.toString(response.getEntity()); log.debug("GLM-OCR API响应: {}", responseBody); // 使用Jackson等JSON工具反序列化,这里简化为示例 // 实际项目中应注入 ObjectMapper com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); OcrResponse ocrResponse = mapper.readValue(responseBody, OcrResponse.class); if (ocrResponse.getCode() != null && ocrResponse.getCode() == 0) { return ocrResponse; } else { log.error("GLM-OCR识别失败: {} - {}", ocrResponse.getCode(), ocrResponse.getMsg()); throw new RuntimeException(String.format("OCR识别失败: %s", ocrResponse.getMsg())); } } catch (Exception e) { log.error("调用GLM-OCR服务异常", e); throw new IOException("OCR服务调用失败", e); } } }这个GlmOcrService就是我们后端的“OCR引擎”。它接收Spring MVC传来的MultipartFile,处理好所有HTTP通信细节,最后返回结构化的识别结果。
3. 控制器层:设计RESTful API
服务层做好了“苦力活”,控制器层的工作就轻松了。它主要负责定义API端点,接收前端或客户端的请求,调用服务,并返回格式友好的响应。
我们设计两个简单的API:
POST /api/ocr/sync: 同步识别接口,上传图片后立即等待并返回结果。适用于轻量、即时场景。POST /api/ocr/async: 异步识别接口,上传图片后立即返回一个任务ID,结果通过其他方式(如WebSocket、回调或查询接口)获取。适用于处理耗时较长或批量任务。
首先,定义统一的API响应格式。
import lombok.Data; @Data public class ApiResponse<T> { private boolean success; private String message; private T data; public static <T> ApiResponse<T> success(T data) { ApiResponse<T> response = new ApiResponse<>(); response.setSuccess(true); response.setMessage("success"); response.setData(data); return response; } public static <T> ApiResponse<T> error(String message) { ApiResponse<T> response = new ApiResponse<>(); response.setSuccess(false); response.setMessage(message); return response; } }然后,实现同步识别控制器。
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @Slf4j @RestController @RequestMapping("/api/ocr") @RequiredArgsConstructor public class OcrController { private final GlmOcrService glmOcrService; @PostMapping(value = "/sync", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ApiResponse<OcrResponse> syncRecognize(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return ApiResponse.error("请上传图片文件"); } try { log.info("同步处理OCR请求,文件名: {}", file.getOriginalFilename()); OcrResponse result = glmOcrService.recognizeText(file); return ApiResponse.success(result); } catch (Exception e) { log.error("同步OCR处理失败", e); return ApiResponse.error("图片识别失败: " + e.getMessage()); } } }现在,你已经可以通过curl或 Postman 测试这个接口了。
curl -X POST -F "file=@/path/to/your/invoice.jpg" http://localhost:8080/api/ocr/sync4. 进阶:实现异步处理与任务队列
同步接口虽然简单,但在处理大量图片或图片较大时,会长时间占用HTTP连接,影响用户体验和服务器并发能力。异步处理是更专业的做法。
我们可以利用Spring提供的@Async注解和线程池,实现一个简单的异步任务队列。更复杂的生产环境可以考虑集成RabbitMQ或Kafka。
4.1 配置异步任务执行器
在配置类中启用异步支持并配置线程池。
import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; @Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(5); // 最大线程数 executor.setMaxPoolSize(10); // 队列容量 executor.setQueueCapacity(100); // 线程名前缀 executor.setThreadNamePrefix("OCR-Async-"); executor.initialize(); return executor; } }4.2 创建异步服务与任务管理
我们需要一个地方来存储异步任务的状态和结果。这里用一个简单的内存ConcurrentHashMap来模拟,生产环境应使用Redis或数据库。
import lombok.Data; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @Service public class AsyncOcrService { private final GlmOcrService glmOcrService; // 内存存储任务状态,Key: taskId, Value: 任务结果Future private final Map<String, CompletableFuture<OcrResponse>> taskStore = new ConcurrentHashMap<>(); public AsyncOcrService(GlmOcrService glmOcrService) { this.glmOcrService = glmOcrService; } /** * 提交一个异步OCR任务 * @param taskId 任务ID * @param imageFile 图片文件 * @return 返回该任务的Future */ @Async public CompletableFuture<OcrResponse> submitTask(String taskId, MultipartFile imageFile) { CompletableFuture<OcrResponse> future = CompletableFuture.supplyAsync(() -> { try { return glmOcrService.recognizeText(imageFile); } catch (Exception e) { throw new RuntimeException(e); } }); taskStore.put(taskId, future); // 任务完成后,可以设置一段时间后自动清理,这里省略 return future; } /** * 根据任务ID查询结果 */ public OcrResponse getTaskResult(String taskId) { CompletableFuture<OcrResponse> future = taskStore.get(taskId); if (future == null) { return null; // 或抛出任务不存在的异常 } if (future.isDone()) { try { return future.get(); } catch (Exception e) { throw new RuntimeException("获取任务结果失败", e); } } else { return null; // 任务仍在处理中 } } }4.3 完善异步控制器
最后,在控制器中添加异步接口。
private final AsyncOcrService asyncOcrService; @PostMapping(value = "/async", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ApiResponse<Map<String, String>> asyncRecognize(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return ApiResponse.error("请上传图片文件"); } // 生成一个唯一任务ID,可以用UUID String taskId = java.util.UUID.randomUUID().toString(); log.info("提交异步OCR任务,任务ID: {}, 文件名: {}", taskId, file.getOriginalFilename()); // 提交异步任务,不等待结果 asyncOcrService.submitTask(taskId, file); Map<String, String> response = new HashMap<>(); response.put("taskId", taskId); response.put("status", "submitted"); response.put("message", "任务已提交,请使用taskId查询结果"); return ApiResponse.success(response); } @GetMapping("/async/result/{taskId}") public ApiResponse<Object> getAsyncResult(@PathVariable String taskId) { OcrResponse result = asyncOcrService.getTaskResult(taskId); if (result == null) { Map<String, String> status = new HashMap<>(); status.put("taskId", taskId); status.put("status", "processing"); status.put("message", "任务正在处理中,请稍后重试"); return ApiResponse.success(status); } return ApiResponse.success(result); }现在,你的服务就拥有了同步和异步两种处理模式。前端可以先调用/api/ocr/async快速拿到taskId,然后轮询/api/ocr/async/result/{taskId}来获取最终识别结果,用户体验会好很多。
5. 总结
走完这一趟,一个基于SpringBoot的GLM-OCR后端服务就基本成型了。从最基础的HTTP客户端调用,到RESTful API的设计,再到引入异步任务队列来提升并发处理能力,我们一步步把一个AI能力封装成了适合Java企业应用集成的服务模块。
实际用下来,这套架构在应对中小规模的文档识别场景时表现很稳定。异步设计有效地避免了长时间的网络请求阻塞,线程池的配置也让资源利用更合理。当然,在真正的高并发生产环境,你可能还需要考虑更多,比如用Redis来持久化任务状态、增加更完善的熔断降级机制(例如使用Resilience4j),以及对GLM-OCR服务端进行集群化部署来分摊压力。
集成过程中,最关键的是理解GLM-OCR API的详细规格(请求格式、响应结构、错误码),并设计好与之匹配的数据模型和异常处理逻辑。希望这篇结合了具体代码的指南,能让你在为自己的Java项目添加OCR智能时,少走些弯路。接下来,你可以尝试在此基础上,增加批量处理、结果后处理(如关键信息结构化提取)等功能,让它更加强大。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。