增加百度云api的人脸和OCR
This commit is contained in:
@ -319,3 +319,10 @@ warm-flow:
|
||||
- 255,205,23
|
||||
## 已办理
|
||||
- 157,255,0
|
||||
|
||||
|
||||
--- # 百度云配置
|
||||
baidu:
|
||||
client:
|
||||
id: zSB7KdLgY7a1tIEx3eTy65TE
|
||||
secret: 5nabjclW5BWGV8UwEueDgBDmOveRVkmD
|
||||
|
@ -17,7 +17,12 @@
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- JSON解析(FastJSON) -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>2.0.32</version>
|
||||
</dependency>
|
||||
<!-- word转pdf转图片 -->
|
||||
<!-- docx4j 核心 -->
|
||||
<dependency>
|
||||
@ -189,6 +194,22 @@
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-workflow</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.scala-lang</groupId>
|
||||
<artifactId>scala-library</artifactId>
|
||||
<version>2.13.9</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>5.15.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
package org.dromara.common.constant;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:38
|
||||
* @Version 1.0
|
||||
*/public interface businessConstant {
|
||||
String REDIS_BAIDU_KEY = "baidu:access_token"; //百度token存储在redis中的key
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package org.dromara.common.utils;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:16
|
||||
* @Version 1.0
|
||||
*
|
||||
* HttpClient 设计为可重用、线程安全的组件,其内部维护了连接池等资源,适合在多个接口调用中共享使用,所以交给Spring Bean 管理
|
||||
*/
|
||||
@Configuration
|
||||
public class HttpClientConfig {
|
||||
@Bean
|
||||
public HttpClient httpClient() {
|
||||
return HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package org.dromara.common.utils.baiduUtil;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.dromara.common.utils.baiduUtil.entity.AccessTokenResponse;
|
||||
import org.glassfish.jaxb.runtime.v2.runtime.reflect.opt.Const;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
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.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.dromara.common.constant.businessConstant.REDIS_BAIDU_KEY;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 9:46
|
||||
* @Version 1.0
|
||||
*
|
||||
* 获取百度AccessToken
|
||||
*/
|
||||
@Service
|
||||
public class BaiDuCommon {
|
||||
@Autowired
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
@Value("${baidu.client.id}")
|
||||
private String clientId;
|
||||
|
||||
@Value("${baidu.client.secret}")
|
||||
private String clientSecret;
|
||||
|
||||
private static final String TOKEN_URL = "https://aip.baidubce.com/oauth/2.0/token?client_id=%s&client_secret=%s&grant_type=client_credentials";
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* 获取百度AccessToken,优先从Redis缓存中获取
|
||||
*/
|
||||
public String getAccessToken() {
|
||||
// 1. 尝试从Redis中获取
|
||||
String cachedToken = redisTemplate.opsForValue().get(REDIS_BAIDU_KEY);
|
||||
if (cachedToken != null) {
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
// 2. Redis中没有,加锁防止并发请求刷新Token
|
||||
synchronized (this) {
|
||||
// 再次检查,避免其他线程已经刷新
|
||||
cachedToken = redisTemplate.opsForValue().get(REDIS_BAIDU_KEY);
|
||||
if (cachedToken != null) {
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
// 3. 真正调用API获取新Token
|
||||
String newToken = fetchAccessTokenFromBaidu();
|
||||
if (newToken != null) {
|
||||
// 4. 将新Token存入Redis,并设置过期时间(比实际有效期短一些)
|
||||
redisTemplate.opsForValue().set(REDIS_BAIDU_KEY, newToken, 29, TimeUnit.DAYS);
|
||||
return newToken;
|
||||
}
|
||||
}
|
||||
|
||||
return null; // 获取失败
|
||||
}
|
||||
|
||||
/**
|
||||
* 从百度API获取AccessToken
|
||||
*/
|
||||
private String fetchAccessTokenFromBaidu() {
|
||||
String url = String.format(TOKEN_URL, clientId, clientSecret);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.noBody())
|
||||
.build();
|
||||
|
||||
try {
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 200) {
|
||||
AccessTokenResponse tokenResponse = gson.fromJson(response.body(), AccessTokenResponse.class);
|
||||
return tokenResponse.getAccessToken();
|
||||
} else {
|
||||
throw new IOException("获取AccessToken失败,状态码: " + response.statusCode());
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("调用百度API获取AccessToken失败", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
package org.dromara.common.utils.baiduUtil;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.common.utils.baiduUtil.entity.face.ComparisonRes;
|
||||
import org.dromara.common.utils.baiduUtil.entity.face.HumanFaceReq;
|
||||
import org.dromara.common.utils.baiduUtil.entity.face.HumanFaceRes;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 17:09
|
||||
* @Version 1.0
|
||||
* 处理百度人脸相关逻辑:人脸检测、人脸对比
|
||||
*/
|
||||
@Service
|
||||
public class BaiDuFace {
|
||||
|
||||
@Autowired
|
||||
private BaiDuCommon baiDuCommon;
|
||||
@Autowired
|
||||
private HttpClient httpClient;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
// 人脸识别API地址
|
||||
private static final String FACE_DETECT_URL = "https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=%s";
|
||||
// 人脸对比API地址
|
||||
private static final String FACE_COMPARE_URL = "https://aip.baidubce.com/rest/2.0/face/v3/match?access_token=%s";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 人脸识别+人脸检测
|
||||
* @param request 人脸请求参数
|
||||
* @throws RuntimeException 检测失败时抛出异常(由Spring统一处理)
|
||||
*/
|
||||
public void humanFace(HumanFaceReq request) {
|
||||
// 1. 获取AccessToken
|
||||
String accessToken = baiDuCommon.getAccessToken();
|
||||
if (accessToken == null || accessToken.trim().isEmpty()) {
|
||||
throw new RuntimeException("获取访问令牌失败:token为空");
|
||||
}
|
||||
|
||||
// 2. 构建请求URL
|
||||
String requestUrl = String.format(FACE_DETECT_URL, URLEncoder.encode(accessToken, StandardCharsets.UTF_8));
|
||||
|
||||
try {
|
||||
// 3. 序列化请求参数
|
||||
String requestBody = objectMapper.writeValueAsString(request);
|
||||
|
||||
// 4. 构建HTTP请求
|
||||
HttpRequest httpRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create(requestUrl))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
// 5. 发送请求并获取响应
|
||||
HttpResponse<String> response = httpClient.send(
|
||||
httpRequest,
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
|
||||
// 6. 解析响应结果
|
||||
HumanFaceRes faceRep = objectMapper.readValue(response.body(), HumanFaceRes.class);
|
||||
|
||||
// 7. 处理API返回错误
|
||||
if (faceRep.getErrorCode() != 0) {
|
||||
throw new RuntimeException("错误码说明:" + faceRep.getErrorMsg());
|
||||
}
|
||||
|
||||
// 8. 验证人脸信息(无人脸时抛出异常)
|
||||
if (faceRep.getResult() == null
|
||||
|| faceRep.getResult().getFaceList() == null
|
||||
|| faceRep.getResult().getFaceList().isEmpty()) {
|
||||
throw new RuntimeException("未检测到有效人脸信息");
|
||||
}
|
||||
|
||||
// 9. 人脸质量校验
|
||||
HumanFaceRes.FaceList faceInfo = faceRep.getResult().getFaceList().get(0);
|
||||
|
||||
// 9.1 人脸置信度校验(必须为1.0)
|
||||
if (faceInfo.getFaceProbability() != 1.0) {
|
||||
throw new RuntimeException("人脸置信度过低,请重新拍摄!");
|
||||
}
|
||||
|
||||
// 9.2 真实人脸校验(排除卡通人脸)
|
||||
HumanFaceRes.FaceType faceType = faceInfo.getFaceType();
|
||||
if (faceType == null) {
|
||||
throw new RuntimeException("无法判断人脸类型");
|
||||
}
|
||||
if ("cartoon".equals(faceType.getType())) {
|
||||
throw new RuntimeException("请传入真实人脸,勿使用卡通图像");
|
||||
}
|
||||
// 校验真实人脸置信度(需≥0.8)
|
||||
if (faceType.getProbability() < 0.8) {
|
||||
throw new RuntimeException("人脸识别不清晰,请换个角度拍摄");
|
||||
}
|
||||
|
||||
// 9.3 人脸模糊度校验(模糊度≥0.1视为模糊)
|
||||
HumanFaceRes.Quality quality = faceInfo.getQuality();
|
||||
if (quality == null) {
|
||||
throw new RuntimeException("无法获取人脸质量信息");
|
||||
}
|
||||
if (quality.getBlur() != 0) {
|
||||
throw new RuntimeException("人脸过于模糊,请保持镜头稳定");
|
||||
}
|
||||
|
||||
// 9.4 光线校验(光线值<100视为过暗)
|
||||
if (quality.getIllumination() < 100.0) {
|
||||
throw new RuntimeException("光线太暗,请在光线充足的环境下拍摄");
|
||||
}
|
||||
|
||||
// 9.5 人脸完整性校验(1为完整,0为不完整)
|
||||
if (quality.getCompleteness() != 1) {
|
||||
throw new RuntimeException("人脸未完全显示,请确保面部在框内");
|
||||
}
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("请求参数序列化失败:" + e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IO处理异常:" + e.getMessage(), e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 人脸对比(计算人脸相似度)
|
||||
* @param requestList 人脸请求参数列表(至少包含2个人脸)
|
||||
* @return 人脸相似度得分
|
||||
* @throws RuntimeException 对比失败时抛出异常
|
||||
*/
|
||||
public double comparison(List<HumanFaceReq> requestList) {
|
||||
// 1. 校验请求参数
|
||||
if (requestList == null || requestList.size() < 2) {
|
||||
throw new RuntimeException("人脸对比至少需要2张人脸图片");
|
||||
}
|
||||
|
||||
// 2. 获取AccessToken
|
||||
String accessToken = baiDuCommon.getAccessToken();
|
||||
if (accessToken == null || accessToken.trim().isEmpty()) {
|
||||
throw new RuntimeException("获取访问令牌失败:token为空");
|
||||
}
|
||||
|
||||
// 3. 构建请求URL
|
||||
String requestUrl = String.format(FACE_COMPARE_URL, URLEncoder.encode(accessToken, StandardCharsets.UTF_8));
|
||||
|
||||
try {
|
||||
// 4. 序列化请求参数
|
||||
String requestBody = objectMapper.writeValueAsString(requestList);
|
||||
|
||||
// 5. 构建HTTP请求
|
||||
HttpRequest httpRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create(requestUrl))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
// 6. 发送请求并获取响应
|
||||
HttpResponse<String> response = httpClient.send(
|
||||
httpRequest,
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
|
||||
// 7. 解析响应结果
|
||||
ComparisonRes comparisonRep = objectMapper.readValue(response.body(), ComparisonRes.class);
|
||||
|
||||
// 8. 处理API返回错误
|
||||
if (comparisonRep.getErrorCode() != 0) {
|
||||
throw new RuntimeException("人脸对比失败:" + comparisonRep.getErrorMsg()
|
||||
+ "(错误码:" + comparisonRep.getErrorCode() + ")");
|
||||
}
|
||||
|
||||
// 9. 校验对比结果
|
||||
if (comparisonRep.getResult() == null) {
|
||||
throw new RuntimeException("人脸对比结果为空");
|
||||
}
|
||||
|
||||
return comparisonRep.getResult().getScore();
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("请求参数序列化失败:" + e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IO处理异常:" + e.getMessage(), e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
package org.dromara.common.utils.baiduUtil;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.IDCardInfo;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.OcrReq;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.Result;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.WordsResult;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:45
|
||||
* @Version 1.0
|
||||
*
|
||||
* 处理百度OCR相关逻辑:身份证识别、银行卡识别
|
||||
*/
|
||||
@Service
|
||||
public class BaiDuOCR {
|
||||
@Autowired
|
||||
private BaiDuCommon baiDuCommon;
|
||||
@Autowired
|
||||
private HttpClient httpClient;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper; //ObjectMapper 是 Java 处理 JSON 的核心工具,项目中使用它进行 JSON 与 Java 对象的相互转换。
|
||||
|
||||
|
||||
String baseUrlTemplate = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard?access_token=%s";
|
||||
String BASE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard?access_token=%s";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @description 银行卡OCR识别
|
||||
* @author 铁憨憨
|
||||
* @date 2025/7/18 11:50
|
||||
* @param vr 请求参数
|
||||
* @return 识别结果Result
|
||||
**/
|
||||
public Result bankCardOCRRecognition(OcrReq vr) {
|
||||
// 先从缓存里面捞取token,若为空直接抛出异常
|
||||
String atStr = baiDuCommon.getAccessToken();
|
||||
if (atStr == null || atStr.trim().isEmpty()) {
|
||||
throw new RuntimeException("获取访问令牌失败:token为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求URL(包含token参数)
|
||||
String requestUrl = String.format(baseUrlTemplate, URLEncoder.encode(atStr, StandardCharsets.UTF_8));
|
||||
|
||||
// 准备请求体(将请求参数转为JSON)
|
||||
String requestBody = objectMapper.writeValueAsString(vr);
|
||||
if (requestBody == null) {
|
||||
throw new RuntimeException("请求参数序列化失败");
|
||||
}
|
||||
|
||||
// 构建HTTP请求
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(requestUrl))
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
// 发送请求并获取响应
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// 处理响应状态码(非200直接抛出异常)
|
||||
if (response.statusCode() != 200) {
|
||||
throw new RuntimeException("接口请求失败,状态码:" + response.statusCode() + ",响应信息:" + response.body());
|
||||
}
|
||||
|
||||
// 解析响应结果(若解析失败抛出异常)
|
||||
Result result = objectMapper.readValue(response.body(), Result.class);
|
||||
if (result == null) {
|
||||
throw new RuntimeException("响应结果解析失败");
|
||||
}
|
||||
|
||||
// // 若接口返回错误码(如百度API的error_code),抛出异常
|
||||
// if (result.getErrorCode() != null && result.getErrorCode() != 0) {
|
||||
// throw new RuntimeException("接口返回错误:" + result.getErrorMessage() + "(错误码:" + result.getErrorCode() + ")");
|
||||
// }
|
||||
|
||||
return result;
|
||||
|
||||
} catch (IOException e) {
|
||||
// IO异常(如序列化失败、网络IO错误)
|
||||
throw new RuntimeException("IO处理异常:" + e.getMessage(), e);
|
||||
} catch (InterruptedException e) {
|
||||
// 线程中断异常
|
||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @description 身份证OCR识别
|
||||
* @author 铁憨憨
|
||||
* @date 2025/7/18 16:53
|
||||
* @param request 请求参数
|
||||
* @return 识别结果WordsResult
|
||||
**/
|
||||
public WordsResult idCardOCR(OcrReq request) {
|
||||
// 获取AccessToken,为空直接抛异常
|
||||
String accessToken = baiDuCommon.getAccessToken();
|
||||
if (accessToken == null || accessToken.trim().isEmpty()) {
|
||||
throw new RuntimeException("获取访问令牌失败:token为空");
|
||||
}
|
||||
|
||||
// 构建请求URL
|
||||
String requestUrl = String.format(BASE_URL, accessToken);
|
||||
|
||||
try {
|
||||
// 准备请求体(序列化失败抛异常)
|
||||
String requestBody = objectMapper.writeValueAsString(request);
|
||||
if (requestBody == null) {
|
||||
throw new RuntimeException("请求参数序列化失败");
|
||||
}
|
||||
|
||||
// 构建HTTP请求
|
||||
HttpRequest httpRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create(requestUrl))
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
// 发送请求并获取响应
|
||||
HttpResponse<String> response = httpClient.send(
|
||||
httpRequest,
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
|
||||
// 处理响应状态码(非200直接抛异常)
|
||||
if (response.statusCode() != 200) {
|
||||
throw new RuntimeException("接口请求失败,状态码:" + response.statusCode() + ",响应信息:" + response.body());
|
||||
}
|
||||
|
||||
// 处理响应内容(去除空格并解析)
|
||||
String responseBody = response.body().replaceAll("\\s+", "");
|
||||
IDCardInfo idCardInfo = objectMapper.readValue(responseBody, IDCardInfo.class);
|
||||
if (idCardInfo == null) {
|
||||
throw new RuntimeException("响应结果解析失败");
|
||||
}
|
||||
|
||||
// 检查身份证状态(状态异常由validateImageStatus抛异常)
|
||||
validateImageStatus(idCardInfo.getImageStatus());
|
||||
|
||||
// 提取识别结果(结果为空抛异常)
|
||||
WordsResult wordsResult = idCardInfo.getWordsResult();
|
||||
if (wordsResult == null) {
|
||||
throw new RuntimeException("未识别到身份证信息");
|
||||
}
|
||||
|
||||
return wordsResult;
|
||||
|
||||
} catch (IOException e) {
|
||||
// IO异常(序列化、网络IO等)
|
||||
throw new RuntimeException("IO处理异常:" + e.getMessage(), e);
|
||||
} catch (InterruptedException e) {
|
||||
// 线程中断异常
|
||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 验证身份证图片状态
|
||||
* @author 铁憨憨
|
||||
* @date 2025/7/18 17:08
|
||||
* @param imageStatus 图片状态
|
||||
* @throws RuntimeException 当状态不正常时抛出运行时异常
|
||||
**/
|
||||
private void validateImageStatus(String imageStatus) {
|
||||
if (imageStatus == null || "normal".equals(imageStatus)) {
|
||||
return; // 正常状态,直接返回
|
||||
}
|
||||
|
||||
// 根据不同状态码抛出不同的异常信息
|
||||
String errorMessage = switch (imageStatus) {
|
||||
case "reversed_side" -> "身份证正反面颠倒,请调整后重新上传";
|
||||
case "non_idcard" -> "上传的图片中不包含身份证,请重新上传";
|
||||
case "blurred" -> "身份证图片模糊,请重新上传清晰的图片";
|
||||
case "other_type_card" -> "上传的不是身份证,请上传有效的身份证图片";
|
||||
case "over_exposure" -> "身份证关键字段反光或过曝,请调整光线后重新上传";
|
||||
case "over_dark" -> "身份证图片欠曝(亮度过低),请调整光线后重新上传";
|
||||
default -> "未知错误:" + imageStatus;
|
||||
};
|
||||
|
||||
throw new RuntimeException(errorMessage);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 9:56
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AccessTokenResponse {
|
||||
private String accessToken;
|
||||
private int expiresIn;
|
||||
private String scope;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.face;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 17:15
|
||||
* @Version 1.0
|
||||
*
|
||||
* 人脸对比响应结果
|
||||
*/
|
||||
@Data
|
||||
public class ComparisonRes {
|
||||
@JsonProperty("error_code")
|
||||
private int errorCode;
|
||||
|
||||
@JsonProperty("error_msg")
|
||||
private String errorMsg;
|
||||
|
||||
@JsonProperty("log_id")
|
||||
private long logId;
|
||||
|
||||
@JsonProperty("timestamp")
|
||||
private long timestamp;
|
||||
|
||||
@JsonProperty("cached")
|
||||
private int cached;
|
||||
|
||||
@JsonProperty("result")
|
||||
private CompareResult result;
|
||||
|
||||
@Data
|
||||
public static class CompareResult {
|
||||
@JsonProperty("score")
|
||||
private double score; // 人脸相似度得分
|
||||
|
||||
@JsonProperty("face_list")
|
||||
private List<FaceToken> faceList;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FaceToken {
|
||||
@JsonProperty("face_token")
|
||||
private String faceToken;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.face;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 17:12
|
||||
* @Version 1.0
|
||||
*
|
||||
* 人脸识别请求参数
|
||||
*/
|
||||
@Data
|
||||
public class HumanFaceReq {
|
||||
@JsonProperty("image")
|
||||
private String image; // 图片base64字符串
|
||||
|
||||
@JsonProperty("image_type")
|
||||
private String imageType = "BASE64"; // 图片类型(固定为BASE64)
|
||||
|
||||
@JsonProperty("face_field")
|
||||
private String faceField = "face_type,quality"; // 需要返回的人脸字段
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.face;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 17:13
|
||||
* @Version 1.0
|
||||
*
|
||||
* 人脸识别响应结果
|
||||
*/
|
||||
@Data
|
||||
public class HumanFaceRes {
|
||||
@JsonProperty("error_code")
|
||||
private int errorCode;
|
||||
|
||||
@JsonProperty("error_msg")
|
||||
private String errorMsg;
|
||||
|
||||
@JsonProperty("log_id")
|
||||
private long logId;
|
||||
|
||||
@JsonProperty("timestamp")
|
||||
private long timestamp;
|
||||
|
||||
@JsonProperty("cached")
|
||||
private int cached;
|
||||
|
||||
@JsonProperty("result")
|
||||
private Result result;
|
||||
|
||||
@Data
|
||||
public static class Result {
|
||||
@JsonProperty("face_num")
|
||||
private int faceNum;
|
||||
|
||||
@JsonProperty("face_list")
|
||||
private List<FaceList> faceList;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FaceList {
|
||||
@JsonProperty("face_token")
|
||||
private String faceToken;
|
||||
|
||||
@JsonProperty("location")
|
||||
private Location location;
|
||||
|
||||
@JsonProperty("face_probability")
|
||||
private double faceProbability;
|
||||
|
||||
@JsonProperty("angle")
|
||||
private Angle angle;
|
||||
|
||||
@JsonProperty("face_type")
|
||||
private FaceType faceType;
|
||||
|
||||
@JsonProperty("quality")
|
||||
private Quality quality;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Location {
|
||||
@JsonProperty("left")
|
||||
private double left;
|
||||
|
||||
@JsonProperty("top")
|
||||
private double top;
|
||||
|
||||
@JsonProperty("width")
|
||||
private int width;
|
||||
|
||||
@JsonProperty("height")
|
||||
private int height;
|
||||
|
||||
@JsonProperty("rotation")
|
||||
private int rotation;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Angle {
|
||||
@JsonProperty("yaw")
|
||||
private double yaw;
|
||||
|
||||
@JsonProperty("pitch")
|
||||
private double pitch;
|
||||
|
||||
@JsonProperty("roll")
|
||||
private double roll;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FaceType {
|
||||
@JsonProperty("type")
|
||||
private String type;
|
||||
|
||||
@JsonProperty("probability")
|
||||
private double probability;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Quality {
|
||||
@JsonProperty("occlusion")
|
||||
private Occlusion occlusion;
|
||||
|
||||
@JsonProperty("blur")
|
||||
private double blur;
|
||||
|
||||
@JsonProperty("illumination")
|
||||
private double illumination;
|
||||
|
||||
@JsonProperty("completeness")
|
||||
private long completeness;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Occlusion {
|
||||
@JsonProperty("left_eye")
|
||||
private double leftEye;
|
||||
|
||||
@JsonProperty("right_eye")
|
||||
private double rightEye;
|
||||
|
||||
@JsonProperty("nose")
|
||||
private double nose;
|
||||
|
||||
@JsonProperty("mouth")
|
||||
private double mouth;
|
||||
|
||||
@JsonProperty("left_cheek")
|
||||
private double leftCheek;
|
||||
|
||||
@JsonProperty("right_cheek")
|
||||
private double rightCheek;
|
||||
|
||||
@JsonProperty("chin_contour")
|
||||
private double chinContour;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:58
|
||||
* @Version 1.0
|
||||
*
|
||||
* 银行卡具体信息
|
||||
*/
|
||||
@Data
|
||||
//public class BankData {
|
||||
// private String valid_date; // 有效期
|
||||
// private String bank_card_number; // 卡号
|
||||
// private String bank_name; // 银行名称
|
||||
// private int bank_card_type; // 卡类型(0未知,1借记卡,2贷记卡等)
|
||||
// private String holder_name; // 持卡人姓名(可能为空)
|
||||
//}
|
||||
|
||||
|
||||
public class BankData {
|
||||
|
||||
@JSONField(name = "bank_card_number",label = "银行卡卡号")
|
||||
private String bankCardNumber;
|
||||
|
||||
@JSONField(name = "valid_date",label = "有效期")
|
||||
private String validDate;
|
||||
|
||||
@JSONField(name = "bank_card_type",label = "银行卡类型,0:不能识别; 1:借记卡; 2:贷记卡(原信用卡大部分为贷记卡); 3:准贷记卡; 4:预付费卡")
|
||||
private int bankCardType;
|
||||
|
||||
@JSONField(name = "bank_name",label = "银行名,不能识别时为空")
|
||||
private String bankName;
|
||||
|
||||
@JSONField(name = "holder_name",label = "持卡人姓名,不能识别时为空")
|
||||
private String holderName;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:55
|
||||
* @Version 1.0
|
||||
*
|
||||
* 单个识别字段(含文字和位置)
|
||||
*/
|
||||
@Data
|
||||
public class Field {
|
||||
private Location location; // 位置信息
|
||||
private String words; // 识别文字
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:50
|
||||
* @Version 1.0
|
||||
*
|
||||
* 身份证识别结果
|
||||
*/
|
||||
@Data
|
||||
public class IDCardInfo {
|
||||
@JSONField(name = "words_result")
|
||||
private WordsResult wordsResult; // 识别字段集合(姓名、身份证号等)
|
||||
|
||||
@JSONField(name = "words_result_num")
|
||||
private int wordsResultNum; // 识别字段数量
|
||||
|
||||
@JSONField(name = "idcard_number_type")
|
||||
private int idCardNumberType; // 身份证类型(如18位/15位)
|
||||
|
||||
private String imageStatus; // 图像状态(如是否颠倒)
|
||||
|
||||
@JSONField(name = "log_id")
|
||||
private long logId; // 请求唯一标识(日志ID)
|
||||
|
||||
private String photo; // 身份证照片base64(若检测)
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:56
|
||||
* @Version 1.0
|
||||
*
|
||||
* 位置信息(坐标)
|
||||
*/
|
||||
@Data
|
||||
public class Location {
|
||||
private int top; // 顶部坐标
|
||||
private int left; // 左侧坐标
|
||||
private int width; // 宽度
|
||||
private int height; // 高度
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:47
|
||||
* @Version 1.0
|
||||
*
|
||||
* OCR请求参数(身份证/银行卡通用)
|
||||
*/
|
||||
@Data
|
||||
public class OcrReq {
|
||||
private String image; // 图像base64(与url二选一)
|
||||
|
||||
private String url; // 图像URL(与image二选一)
|
||||
|
||||
@JSONField(name = "id_card_side")
|
||||
private String idCardSide; // 身份证正反面(front/back,银行卡无需)
|
||||
|
||||
@JSONField(name = "detect_photo")
|
||||
private boolean detectPhoto;// 是是否开启银行卡质量类型(清晰模糊、边框/四角不完整)检测功能,默认不开启,即:false。
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 11:20
|
||||
* @Version 1.0
|
||||
*
|
||||
* 银行卡识别的完整返回结果
|
||||
*/
|
||||
@Data
|
||||
public class Result {
|
||||
@JSONField(name = "result")
|
||||
private BankData res;
|
||||
|
||||
private int direction;
|
||||
|
||||
@JSONField(name = "log_id")
|
||||
private long logId;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:50
|
||||
* @Version 1.0
|
||||
*
|
||||
* 身份证识别字段集合
|
||||
*/
|
||||
@Data
|
||||
public class WordsResult {
|
||||
@JSONField(name = "姓名")
|
||||
private Field name; // 姓名
|
||||
@JSONField(name = "民族")
|
||||
private Field nation; // 民族
|
||||
@JSONField(name = "住址")
|
||||
private Field address; // 住址
|
||||
@JSONField(name = "公民身份号码")
|
||||
private Field citizenIdentification; // 身份证号
|
||||
@JSONField(name = "出生")
|
||||
private Field birth; // 出生日期
|
||||
@JSONField(name = "性别")
|
||||
private Field gender; // 性别
|
||||
@JSONField(name = "失效日期")
|
||||
private Field expirationDate; // 失效日期
|
||||
@JSONField(name = "签发机关")
|
||||
private Field issuingAuthority; // 签发机关
|
||||
@JSONField(name = "签发日期")
|
||||
private Field issueDate; // 签发日期
|
||||
}
|
Reference in New Issue
Block a user