From 318b4f7f9ace1185ecda912921ee9fcef8d8eeaa Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Mon, 2 Nov 2026 13:31:32 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E5=AE=A2=E5=95=86=E6=A1=A3?= =?UTF-8?q?=E6=A1=88=E8=90=A5=E4=B8=9A=E6=89=A7=E7=85=A7OCR=E6=94=B9?= =?UTF-8?q?=E6=8E=A5=E7=AC=AC=E4=B8=89=E6=96=B9businessLicense=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../pojo/vo/BusinessLicenseOcrVO.java | 54 +++++ .../controller/BaiduOcrController.java | 17 ++ .../service/IBusinessLicenseOcrService.java | 45 +++++ .../impl/BusinessLicenseOcrServiceImpl.java | 189 ++++++++++++++++++ doc/nacos/blade-dev.yaml | 2 + doc/nacos/blade-prod.yaml | 2 + doc/nacos/third-party-api.yaml | 2 + 7 files changed, 311 insertions(+) create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/BusinessLicenseOcrVO.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/ocr/service/IBusinessLicenseOcrService.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/ocr/service/impl/BusinessLicenseOcrServiceImpl.java diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/BusinessLicenseOcrVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/BusinessLicenseOcrVO.java new file mode 100644 index 00000000..2b7452e8 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/BusinessLicenseOcrVO.java @@ -0,0 +1,54 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 营业执照 OCR 识别结果。 + * + * @author Chill + */ +@Data +@Schema(description = "营业执照OCR识别结果") +public class BusinessLicenseOcrVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 按第三方返回 key 聚合的字段,例如 uscc、company_name、legal_name。 + */ + @Schema(description = "识别字段,键为第三方 OCR 的 key") + private Map fields = new LinkedHashMap<>(); + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BaiduOcrController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BaiduOcrController.java index c94cc068..56c682b0 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BaiduOcrController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BaiduOcrController.java @@ -36,7 +36,9 @@ import org.springblade.core.log.exception.ServiceException; import org.springblade.core.tool.api.R; import org.springblade.transport.ocr.constant.BaiduOcrType; import org.springblade.transport.ocr.service.IBaiduOcrService; +import org.springblade.transport.ocr.service.IBusinessLicenseOcrService; import org.springblade.transport.pojo.vo.BaiduOcrResultVO; +import org.springblade.transport.pojo.vo.BusinessLicenseOcrVO; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -60,6 +62,7 @@ import java.io.IOException; public class BaiduOcrController extends BladeController { private final IBaiduOcrService baiduOcrService; + private final IBusinessLicenseOcrService businessLicenseOcrService; /** * 识别上传的图片。 @@ -109,6 +112,20 @@ public class BaiduOcrController extends BladeController { return R.data(baiduOcrService.recognizeUrl(resolveType(type), side, imageUrl)); } + /** + * 营业执照图片地址识别(第三方 /businessLicense)。 + * + * @param imageUrl 图片地址 + * @return 识别结果 + */ + @PostMapping("/business-license") + @ApiOperationSupport(order = 3) + @Operation(summary = "营业执照OCR识别", description = "按图片地址调用第三方营业执照识别接口") + public R recognizeBusinessLicense( + @Parameter(description = "图片地址", required = true) @RequestParam String imageUrl) { + return R.data(businessLicenseOcrService.recognizeUrl(imageUrl)); + } + private BaiduOcrType resolveType(String type) { try { return BaiduOcrType.from(type); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/ocr/service/IBusinessLicenseOcrService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/ocr/service/IBusinessLicenseOcrService.java new file mode 100644 index 00000000..dcb8a4bf --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/ocr/service/IBusinessLicenseOcrService.java @@ -0,0 +1,45 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.ocr.service; + +import org.springblade.transport.pojo.vo.BusinessLicenseOcrVO; + +/** + * 营业执照 OCR 服务。 + * + * @author Chill + */ +public interface IBusinessLicenseOcrService { + + /** + * 根据图片地址识别营业执照。 + * + * @param imageUrl 图片地址 + * @return 识别结果 + */ + BusinessLicenseOcrVO recognizeUrl(String imageUrl); + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/ocr/service/impl/BusinessLicenseOcrServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/ocr/service/impl/BusinessLicenseOcrServiceImpl.java new file mode 100644 index 00000000..332efc39 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/ocr/service/impl/BusinessLicenseOcrServiceImpl.java @@ -0,0 +1,189 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.ocr.service.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.transport.ocr.service.IBusinessLicenseOcrService; +import org.springblade.transport.pojo.vo.BusinessLicenseOcrVO; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 营业执照 OCR 服务实现,调用第三方 /businessLicense 接口。 + * + * @author Chill + */ +@Slf4j +@Service +public class BusinessLicenseOcrServiceImpl implements IBusinessLicenseOcrService { + + private static final String PATH = "/businessLicense"; + private static final String DATA_TYPE_URL = "url"; + private static final int SUCCESS_CODE = 0; + + private final ObjectMapper objectMapper; + private final HttpClient httpClient; + + /** + * 营业执照 OCR 服务地址,Nacos:thirdParty.ocr.businessLicenseBaseUrl + */ + @Value("${thirdParty.ocr.businessLicenseBaseUrl:http://124.71.22.123:8888}") + private String businessLicenseBaseUrl; + + public BusinessLicenseOcrServiceImpl(ObjectMapper objectMapper, + @Qualifier("baiduOcrHttpClient") HttpClient httpClient) { + this.objectMapper = objectMapper; + this.httpClient = httpClient; + } + + @Override + public BusinessLicenseOcrVO recognizeUrl(String imageUrl) { + if (StringUtil.isBlank(imageUrl)) { + throw new ServiceException("OCR图片地址不能为空"); + } + String normalizedUrl = imageUrl.trim(); + validateImageUrl(normalizedUrl); + if (StringUtil.isBlank(businessLicenseBaseUrl)) { + throw new ServiceException("营业执照OCR服务地址未配置(thirdParty.ocr.businessLicenseBaseUrl)"); + } + String requestUrl = businessLicenseBaseUrl.replaceAll("/+$", "") + PATH; + Map body = new LinkedHashMap<>(); + body.put("img_path", normalizedUrl); + body.put("data_type", DATA_TYPE_URL); + JsonNode response = sendRequest(requestUrl, body); + int code = response.path("code").asInt(Integer.MIN_VALUE); + if (code != SUCCESS_CODE) { + String message = firstNonBlank(response.path("msg").asText(null), response.path("message").asText(null), "未知错误"); + log.warn("营业执照OCR识别失败,code={},msg={},imageUrl={}", code, message, normalizedUrl); + throw new ServiceException("营业执照OCR识别失败:" + message); + } + BusinessLicenseOcrVO result = new BusinessLicenseOcrVO(); + result.setFields(extractFields(response.path("data"))); + return result; + } + + private JsonNode sendRequest(String requestUrl, Map body) { + try { + String jsonBody = objectMapper.writeValueAsString(body); + HttpRequest request = HttpRequest.newBuilder(URI.create(requestUrl)) + .timeout(Duration.ofSeconds(60)) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8)) + .build(); + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + log.error("营业执照OCR接口响应异常,status={},url={}", response.statusCode(), requestUrl); + throw new ServiceException("营业执照OCR接口调用失败,HTTP状态码:" + response.statusCode()); + } + JsonNode result = objectMapper.readTree(response.body()); + if (result == null || !result.isObject()) { + throw new ServiceException("营业执照OCR响应格式异常"); + } + return result; + } catch (ServiceException exception) { + throw exception; + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + log.error("调用营业执照OCR接口被中断,url={}", requestUrl, exception); + throw new ServiceException("营业执照OCR接口调用被中断"); + } catch (IllegalArgumentException exception) { + log.error("营业执照OCR接口地址配置不正确,url={}", requestUrl); + throw new ServiceException("营业执照OCR接口地址配置不正确"); + } catch (IOException exception) { + log.error("调用营业执照OCR接口失败,url={}", requestUrl, exception); + throw new ServiceException("营业执照OCR接口调用失败"); + } + } + + private Map extractFields(JsonNode dataNode) { + Map fields = new LinkedHashMap<>(); + if (dataNode == null || !dataNode.isArray()) { + return fields; + } + for (JsonNode item : dataNode) { + if (item == null || !item.isObject()) { + continue; + } + String key = item.path("key").asText(null); + String value = item.path("value").asText(null); + if (StringUtil.isBlank(key) || value == null) { + continue; + } + String trimmed = value.trim(); + if (StringUtil.isBlank(trimmed)) { + continue; + } + fields.put(key.trim(), trimmed); + } + return fields; + } + + private void validateImageUrl(String imageUrl) { + if (imageUrl.getBytes(StandardCharsets.UTF_8).length > 1024) { + throw new ServiceException("OCR图片地址长度不能超过1024字节"); + } + try { + URI imageUri = URI.create(imageUrl); + String scheme = imageUri.getScheme(); + if (StringUtil.isBlank(scheme) || StringUtil.isBlank(imageUri.getHost()) + || imageUri.getUserInfo() != null + || !("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme))) { + throw new ServiceException("OCR图片地址必须是有效的HTTP或HTTPS地址"); + } + } catch (IllegalArgumentException exception) { + throw new ServiceException("OCR图片地址格式不正确"); + } + } + + private String firstNonBlank(String... values) { + if (values == null) { + return null; + } + for (String value : values) { + if (StringUtil.isNotBlank(value)) { + return value; + } + } + return null; + } + +} diff --git a/doc/nacos/blade-dev.yaml b/doc/nacos/blade-dev.yaml index c316ea70..d64189d5 100644 --- a/doc/nacos/blade-dev.yaml +++ b/doc/nacos/blade-dev.yaml @@ -96,6 +96,8 @@ thirdParty: baseOtherUrl: http://127.0.0.1:8080 # 身份证 OCR 服务地址(POST /ShenFenZheng,formData 字段 image_file) idCardBaseUrl: http://124.71.22.123:8500 + # 营业执照 OCR 服务地址(POST /businessLicense,JSON:img_path、data_type) + businessLicenseBaseUrl: http://124.71.22.123:8888 wps: # WPS开放接口地址 baseUrl: http://127.0.0.1:8080 diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index a1caecb8..08be3ef2 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -58,6 +58,8 @@ thirdParty: baseOtherUrl: http://127.0.0.1:8080 # 身份证 OCR 服务地址(POST /ShenFenZheng,formData 字段 image_file) idCardBaseUrl: http://124.71.22.123:8500 + # 营业执照 OCR 服务地址(POST /businessLicense,JSON:img_path、data_type) + businessLicenseBaseUrl: http://124.71.22.123:8888 wps: # WPS开放接口地址 baseUrl: http://127.0.0.1:8080 diff --git a/doc/nacos/third-party-api.yaml b/doc/nacos/third-party-api.yaml index b2218363..82ea4790 100644 --- a/doc/nacos/third-party-api.yaml +++ b/doc/nacos/third-party-api.yaml @@ -7,6 +7,8 @@ thirdParty: baseOtherUrl: ${OCR_BASE_OTHER_URL:http://127.0.0.1:8080} # 身份证 OCR 服务地址(POST /ShenFenZheng,formData 字段 image_file) idCardBaseUrl: ${OCR_ID_CARD_BASE_URL:http://124.71.22.123:8500} + # 营业执照 OCR 服务地址(POST /businessLicense,JSON:img_path、data_type) + businessLicenseBaseUrl: ${OCR_BUSINESS_LICENSE_BASE_URL:http://124.71.22.123:8888} wps: # WPS开放接口地址 baseUrl: ${WPS_BASE_URL:http://127.0.0.1:8080}