✨ 从业资格证识别改为调用标准保单接口

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-05 15:09:16 +08:00
parent e79c74fd30
commit 896564d911
7 changed files with 309 additions and 0 deletions
@@ -0,0 +1,54 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* 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 QualificationCertificateOcrVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 按第三方返回 key 聚合的字段,例如 qualification_category、certificate_no、valid_from、valid_until。
*/
@Schema(description = "识别字段,键为第三方 OCR 的 key")
private Map<String, String> fields = new LinkedHashMap<>();
}
@@ -37,8 +37,10 @@ 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.ocr.service.IQualificationCertificateOcrService;
import org.springblade.transport.pojo.vo.BaiduOcrResultVO;
import org.springblade.transport.pojo.vo.BusinessLicenseOcrVO;
import org.springblade.transport.pojo.vo.QualificationCertificateOcrVO;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -63,6 +65,7 @@ public class BaiduOcrController extends BladeController {
private final IBaiduOcrService baiduOcrService;
private final IBusinessLicenseOcrService businessLicenseOcrService;
private final IQualificationCertificateOcrService qualificationCertificateOcrService;
/**
* 识别上传的图片。
@@ -126,6 +129,20 @@ public class BaiduOcrController extends BladeController {
return R.data(businessLicenseOcrService.recognizeUrl(imageUrl));
}
/**
* 从业资格证图片地址识别(第三方 /Biaozhunbaodan)。
*
* @param imageUrl 图片地址
* @return 识别结果
*/
@PostMapping("/qualification-certificate")
@ApiOperationSupport(order = 4)
@Operation(summary = "从业资格证OCR识别", description = "按图片地址调用第三方道路运输从业人员从业资格证识别接口")
public R<QualificationCertificateOcrVO> recognizeQualificationCertificate(
@Parameter(description = "图片地址", required = true) @RequestParam String imageUrl) {
return R.data(qualificationCertificateOcrService.recognizeUrl(imageUrl));
}
private BaiduOcrType resolveType(String type) {
try {
return BaiduOcrType.from(type);
@@ -0,0 +1,45 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.ocr.service;
import org.springblade.transport.pojo.vo.QualificationCertificateOcrVO;
/**
* 从业资格证 OCR 服务。
*
* @author Chill
*/
public interface IQualificationCertificateOcrService {
/**
* 根据图片地址识别道路运输从业人员从业资格证。
*
* @param imageUrl 图片地址
* @return 识别结果
*/
QualificationCertificateOcrVO recognizeUrl(String imageUrl);
}
@@ -0,0 +1,187 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* 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.IQualificationCertificateOcrService;
import org.springblade.transport.pojo.vo.QualificationCertificateOcrVO;
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 服务实现,调用第三方 /Biaozhunbaodan 接口。
*
* @author Chill
*/
@Slf4j
@Service
public class QualificationCertificateOcrServiceImpl implements IQualificationCertificateOcrService {
private static final String PATH = "/Biaozhunbaodan";
private static final int SUCCESS_CODE = 200;
private final ObjectMapper objectMapper;
private final HttpClient httpClient;
/**
* 从业资格证 OCR 服务地址,Nacos:thirdParty.ocr.qualificationCertificateBaseUrl
*/
@Value("${thirdParty.ocr.qualificationCertificateBaseUrl:http://124.71.22.123:8400}")
private String qualificationCertificateBaseUrl;
public QualificationCertificateOcrServiceImpl(ObjectMapper objectMapper,
@Qualifier("baiduOcrHttpClient") HttpClient httpClient) {
this.objectMapper = objectMapper;
this.httpClient = httpClient;
}
@Override
public QualificationCertificateOcrVO recognizeUrl(String imageUrl) {
if (StringUtil.isBlank(imageUrl)) {
throw new ServiceException("OCR图片地址不能为空");
}
String normalizedUrl = imageUrl.trim();
validateImageUrl(normalizedUrl);
if (StringUtil.isBlank(qualificationCertificateBaseUrl)) {
throw new ServiceException("从业资格证OCR服务地址未配置(thirdParty.ocr.qualificationCertificateBaseUrl)");
}
String requestUrl = qualificationCertificateBaseUrl.replaceAll("/+$", "") + PATH;
Map<String, String> body = new LinkedHashMap<>();
body.put("img_path", normalizedUrl);
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);
}
QualificationCertificateOcrVO result = new QualificationCertificateOcrVO();
result.setFields(extractFields(response.path("result").path("key_info")));
return result;
}
private JsonNode sendRequest(String requestUrl, Map<String, String> 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<String> 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<String, String> extractFields(JsonNode keyInfoNode) {
Map<String, String> fields = new LinkedHashMap<>();
if (keyInfoNode == null || !keyInfoNode.isArray()) {
return fields;
}
for (JsonNode item : keyInfoNode) {
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;
}
}
+2
View File
@@ -98,6 +98,8 @@ thirdParty:
idCardBaseUrl: http://124.71.22.123:8500
# 营业执照 OCR 服务地址(POST /businessLicense,JSON:img_path、data_type)
businessLicenseBaseUrl: http://124.71.22.123:8888
# 从业资格证 OCR 服务地址(POST /Biaozhunbaodan,JSON:img_path)
qualificationCertificateBaseUrl: http://124.71.22.123:8400
wps:
# WPS开放接口地址
baseUrl: http://127.0.0.1:8080
+2
View File
@@ -60,6 +60,8 @@ thirdParty:
idCardBaseUrl: http://124.71.22.123:8500
# 营业执照 OCR 服务地址(POST /businessLicense,JSON:img_path、data_type)
businessLicenseBaseUrl: http://124.71.22.123:8888
# 从业资格证 OCR 服务地址(POST /Biaozhunbaodan,JSON:img_path)
qualificationCertificateBaseUrl: http://124.71.22.123:8400
wps:
# WPS开放接口地址
baseUrl: http://127.0.0.1:8080
+2
View File
@@ -9,6 +9,8 @@ thirdParty:
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}
# 从业资格证 OCR 服务地址(POST /Biaozhunbaodan,JSON:img_path)
qualificationCertificateBaseUrl: ${OCR_QUALIFICATION_CERTIFICATE_BASE_URL:http://124.71.22.123:8400}
wps:
# WPS开放接口地址
baseUrl: ${WPS_BASE_URL:http://127.0.0.1:8080}