From aade0779777fbe34b12a13cad861db6b7639cc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=88=90?= <2847920761@qq.com> Date: Fri, 18 Jul 2025 18:37:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=99=BE=E5=BA=A6=E4=BA=91ap?= =?UTF-8?q?i=E7=9A=84=E4=BA=BA=E8=84=B8=E5=92=8COCR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 7 + .../ruoyi-modules/ruoyi-system/pom.xml | 23 +- .../common/constant/businessConstant.java | 9 + .../common/utils/HttpClientConfig.java | 24 ++ .../common/utils/baiduUtil/BaiDuCommon.java | 101 +++++++++ .../common/utils/baiduUtil/BaiDuFace.java | 205 ++++++++++++++++++ .../common/utils/baiduUtil/BaiDuOCR.java | 204 +++++++++++++++++ .../baiduUtil/entity/AccessTokenResponse.java | 19 ++ .../baiduUtil/entity/face/ComparisonRes.java | 49 +++++ .../baiduUtil/entity/face/HumanFaceReq.java | 23 ++ .../baiduUtil/entity/face/HumanFaceRes.java | 142 ++++++++++++ .../utils/baiduUtil/entity/ocr/BankData.java | 39 ++++ .../utils/baiduUtil/entity/ocr/Field.java | 16 ++ .../baiduUtil/entity/ocr/IDCardInfo.java | 30 +++ .../utils/baiduUtil/entity/ocr/Location.java | 18 ++ .../utils/baiduUtil/entity/ocr/OcrReq.java | 24 ++ .../utils/baiduUtil/entity/ocr/Result.java | 22 ++ .../baiduUtil/entity/ocr/WordsResult.java | 33 +++ 18 files changed, 987 insertions(+), 1 deletion(-) create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/constant/businessConstant.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/HttpClientConfig.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuCommon.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuFace.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuOCR.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/AccessTokenResponse.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/ComparisonRes.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/HumanFaceReq.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/HumanFaceRes.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/BankData.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Field.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/IDCardInfo.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Location.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/OcrReq.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Result.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/WordsResult.java diff --git a/xinnengyuan/ruoyi-admin/src/main/resources/application.yml b/xinnengyuan/ruoyi-admin/src/main/resources/application.yml index a873a5c6..558f54b1 100644 --- a/xinnengyuan/ruoyi-admin/src/main/resources/application.yml +++ b/xinnengyuan/ruoyi-admin/src/main/resources/application.yml @@ -319,3 +319,10 @@ warm-flow: - 255,205,23 ## 已办理 - 157,255,0 + + +--- # 百度云配置 +baidu: + client: + id: zSB7KdLgY7a1tIEx3eTy65TE + secret: 5nabjclW5BWGV8UwEueDgBDmOveRVkmD diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml b/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml index f94e10e4..d1bb30ee 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/pom.xml @@ -17,7 +17,12 @@ - + + + com.alibaba + fastjson + 2.0.32 + @@ -189,6 +194,22 @@ org.dromara ruoyi-workflow + + com.google.code.gson + gson + + + org.scala-lang + scala-library + 2.13.9 + compile + + + net.java.dev.jna + jna-platform + 5.15.0 + compile + diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/constant/businessConstant.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/constant/businessConstant.java new file mode 100644 index 00000000..3086e68c --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/constant/businessConstant.java @@ -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 +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/HttpClientConfig.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/HttpClientConfig.java new file mode 100644 index 00000000..613b696f --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/HttpClientConfig.java @@ -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(); + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuCommon.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuCommon.java new file mode 100644 index 00000000..d2e41c54 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuCommon.java @@ -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 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); + } + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuFace.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuFace.java new file mode 100644 index 00000000..733f0c17 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuFace.java @@ -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 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 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 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); + } + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuOCR.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuOCR.java new file mode 100644 index 00000000..65a45da6 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/BaiDuOCR.java @@ -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 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 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); + } + + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/AccessTokenResponse.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/AccessTokenResponse.java new file mode 100644 index 00000000..d0fec39d --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/AccessTokenResponse.java @@ -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; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/ComparisonRes.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/ComparisonRes.java new file mode 100644 index 00000000..08735652 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/ComparisonRes.java @@ -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 faceList; + } + + @Data + public static class FaceToken { + @JsonProperty("face_token") + private String faceToken; + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/HumanFaceReq.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/HumanFaceReq.java new file mode 100644 index 00000000..e3383f17 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/HumanFaceReq.java @@ -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"; // 需要返回的人脸字段 +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/HumanFaceRes.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/HumanFaceRes.java new file mode 100644 index 00000000..2e4c5742 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/face/HumanFaceRes.java @@ -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; + } + + @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; + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/BankData.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/BankData.java new file mode 100644 index 00000000..e74f8461 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/BankData.java @@ -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; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Field.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Field.java new file mode 100644 index 00000000..ebc77a35 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Field.java @@ -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; // 识别文字 +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/IDCardInfo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/IDCardInfo.java new file mode 100644 index 00000000..fbd30a2d --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/IDCardInfo.java @@ -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(若检测) +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Location.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Location.java new file mode 100644 index 00000000..fc611511 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Location.java @@ -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; // 高度 +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/OcrReq.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/OcrReq.java new file mode 100644 index 00000000..a785503c --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/OcrReq.java @@ -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。 +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Result.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Result.java new file mode 100644 index 00000000..b92420c1 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/Result.java @@ -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; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/WordsResult.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/WordsResult.java new file mode 100644 index 00000000..43de74b4 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/baiduUtil/entity/ocr/WordsResult.java @@ -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; // 签发日期 +}