diff --git a/pom.xml b/pom.xml
index ccc6b21da..31b7f8c4b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,6 +44,7 @@
4.40.54.ALL
4.2.2
4.0.1
+ 3.1.2
@@ -273,6 +274,12 @@
${alibabacloud-dysmsapi.version}
+
+ com.aliyun
+ ocr_api20210707
+ ${aliyun-ocr.version}
+
+
co.elastic.clients
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
index 97598e0ae..2430af4f6 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
@@ -1,5 +1,6 @@
package com.ruoyi.web.controller.common;
+import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.aliyun.oss.common.auth.Credentials;
@@ -8,16 +9,20 @@ import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.config.properties.OSSProperties;
+import com.ruoyi.framework.ocr.BusinessLicense;
+import com.ruoyi.framework.ocr.IdCard;
+import com.ruoyi.framework.ocr.OcrClientWrapper;
import com.ruoyi.framework.oss.OSSClientWrapper;
+import com.ruoyi.web.controller.common.vo.BusinessLicenseVO;
+import com.ruoyi.web.controller.common.vo.IdCardVO;
import com.ruoyi.web.controller.common.vo.STSCredentialsVO;
+import com.ruoyi.web.controller.common.vo.UrlVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit;
@@ -37,6 +42,8 @@ public class CommonController {
private OSSProperties ossProperties;
@Autowired
private RedisCache redisCache;
+ @Autowired
+ private OcrClientWrapper ocrClient;
@ApiOperation("获取OSS临时访问凭证")
@GetMapping("/oss/getCredentials")
@@ -67,4 +74,34 @@ public class CommonController {
return R.ok(vo);
}
+ @ApiOperation("身份证OCR")
+ @PostMapping("/ocr/idCard")
+ public R recognizeIdCard(@Validated @RequestBody UrlVO vo) {
+ String url = vo.getUrl();
+ String cacheKey = CacheConstants.OCR_CACHE + url;
+ String cacheStr = redisCache.getCacheObject(cacheKey);
+ if (StrUtil.isNotEmpty(cacheStr)) {
+ return R.ok(JSONUtil.toBean(cacheStr, IdCardVO.class));
+ }
+ IdCard dto = ocrClient.recognizeIdCard(url);
+ // 缓存
+ redisCache.setCacheObject(cacheKey, JSONUtil.toJsonStr(dto), 10, TimeUnit.MINUTES);
+ return R.ok(BeanUtil.toBean(dto, IdCardVO.class));
+ }
+
+ @ApiOperation("营业执照OCR")
+ @PostMapping("/ocr/businessLicense")
+ public R recognizeBusinessLicense(@Validated @RequestBody UrlVO vo) {
+ String url = vo.getUrl();
+ String cacheKey = CacheConstants.OCR_CACHE + url;
+ String cacheStr = redisCache.getCacheObject(cacheKey);
+ if (StrUtil.isNotEmpty(cacheStr)) {
+ return R.ok(JSONUtil.toBean(cacheStr, BusinessLicenseVO.class));
+ }
+ BusinessLicense dto = ocrClient.recognizeBusinessLicense(url);
+ // 缓存
+ redisCache.setCacheObject(cacheKey, JSONUtil.toJsonStr(dto), 10, TimeUnit.MINUTES);
+ return R.ok(BeanUtil.toBean(dto, BusinessLicenseVO.class));
+ }
+
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/vo/BusinessLicenseVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/vo/BusinessLicenseVO.java
new file mode 100644
index 000000000..09c8a2292
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/vo/BusinessLicenseVO.java
@@ -0,0 +1,87 @@
+package com.ruoyi.web.controller.common.vo;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author liangyq
+ * @date 2025-06-29
+ */
+@ApiModel
+@Data
+public class BusinessLicenseVO {
+ /**
+ * 统一社会信用代码
+ */
+ @ApiModelProperty("统一社会信用代码")
+ private String creditCode;
+ /**
+ * 营业名称
+ */
+ @ApiModelProperty("营业名称")
+ private String companyName;
+ /**
+ * 类型
+ */
+ @ApiModelProperty("类型")
+ private String companyType;
+ /**
+ * 营业场所/住所
+ */
+ @ApiModelProperty("营业场所/住所")
+ private String businessAddress;
+ /**
+ * 法人/负责人
+ */
+ @ApiModelProperty("法人/负责人")
+ private String legalPerson;
+ /**
+ * 经营范围
+ */
+ @ApiModelProperty("经营范围")
+ private String businessScope;
+ /**
+ * 注册资本
+ */
+ @ApiModelProperty("注册资本")
+ private String registeredCapital;
+ /**
+ * 注册日期
+ */
+ @ApiModelProperty("注册日期")
+ private String registrationDate;
+ /**
+ * 营业期限
+ */
+ @ApiModelProperty("营业期限")
+ private String validPeriod;
+ /**
+ * 格式化营业期限起始日期
+ */
+ @ApiModelProperty("格式化营业期限起始日期")
+ private String validFromDate;
+ /**
+ * 格式化营业期限终止日期
+ */
+ @ApiModelProperty("格式化营业期限终止日期")
+ private String validToDate;
+ /**
+ * 组成形式
+ */
+ @ApiModelProperty("组成形式")
+ private String companyForm;
+ /**
+ * 发照日期
+ */
+ @ApiModelProperty("发照日期")
+ private String issueDate;
+ /**
+ * 证照标题
+ */
+ @ApiModelProperty("证照标题")
+ private String title;
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/vo/IdCardVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/vo/IdCardVO.java
new file mode 100644
index 000000000..9847d21c5
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/vo/IdCardVO.java
@@ -0,0 +1,57 @@
+package com.ruoyi.web.controller.common.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author liangyq
+ * @date 2025-06-29
+ */
+@ApiModel
+@Data
+public class IdCardVO {
+ /**
+ * 身份证号码
+ */
+ @ApiModelProperty("身份证号码")
+ private String idNumber;
+ /**
+ * 姓名
+ */
+ @ApiModelProperty("姓名")
+ private String name;
+ /**
+ * 性别
+ */
+ @ApiModelProperty("性别")
+ private String sex;
+ /**
+ * 民族
+ */
+ @ApiModelProperty("民族")
+ private String ethnicity;
+ /**
+ * 出生日期
+ */
+ @ApiModelProperty("出生日期")
+ private String birthDate;
+ /**
+ * 住址
+ */
+ @ApiModelProperty("住址")
+ private String address;
+
+ /**
+ * 签发机关
+ */
+ @ApiModelProperty("签发机关")
+ private String issueAuthority;
+ /**
+ * 有效期限
+ */
+ @ApiModelProperty("有效期限")
+ private String validPeriod;
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/vo/UrlVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/vo/UrlVO.java
new file mode 100644
index 000000000..ff22b74be
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/vo/UrlVO.java
@@ -0,0 +1,21 @@
+package com.ruoyi.web.controller.common.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+/**
+ * @author liangyq
+ * @date 2025-06-29
+ */
+@ApiModel
+@Data
+public class UrlVO {
+
+ @NotEmpty
+ @ApiModelProperty("url")
+ private String url;
+
+}
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 7c9524865..a6dea8d69 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -40,6 +40,10 @@ sms:
verificationCode:
signName: 成都一方鹏商贸
templateCode: SMS_318925388
+ocr:
+ accessKeyId: LTAI5tDTNoFKAUXJ996azKdS
+ accessKeySecret: tbchxLXOioKcQW9PF9sgjPUZyMA3zm
+ endpoint: ocr-api.cn-hangzhou.aliyuncs.com
es:
#多个用","分割
hosts: es-cn-em94bkrrl0005djma.public.elasticsearch.aliyuncs.com:9200
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
index f2dcaa161..92d9535c1 100644
--- a/ruoyi-common/pom.xml
+++ b/ruoyi-common/pom.xml
@@ -55,6 +55,11 @@
alibabacloud-dysmsapi20170525
+
+ com.aliyun
+ ocr_api20210707
+
+
com.baomidou
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java
index 9d226d358..5e973cf9f 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java
@@ -261,5 +261,9 @@ public class CacheConstants {
* 档口会员
*/
public static final String STORE_MEMBER = "store_member:";
+ /**
+ * OCR缓存
+ */
+ public static final String OCR_CACHE = "ocr_cache:";
}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/BusinessLicense.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/BusinessLicense.java
new file mode 100644
index 000000000..bdf33c318
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/BusinessLicense.java
@@ -0,0 +1,73 @@
+package com.ruoyi.framework.ocr;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author liangyq
+ * @date 2025-06-29
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BusinessLicense {
+ /**
+ * 统一社会信用代码
+ */
+ private String creditCode;
+ /**
+ * 营业名称
+ */
+ private String companyName;
+ /**
+ * 类型
+ */
+ private String companyType;
+ /**
+ * 营业场所/住所
+ */
+ private String businessAddress;
+ /**
+ * 法人/负责人
+ */
+ private String legalPerson;
+ /**
+ * 经营范围
+ */
+ private String businessScope;
+ /**
+ * 注册资本
+ */
+ private String registeredCapital;
+ /**
+ * 注册日期
+ */
+ @JSONField(name = "RegistrationDate")
+ private String registrationDate;
+ /**
+ * 营业期限
+ */
+ private String validPeriod;
+ /**
+ * 格式化营业期限起始日期
+ */
+ private String validFromDate;
+ /**
+ * 格式化营业期限终止日期
+ */
+ private String validToDate;
+ /**
+ * 组成形式
+ */
+ private String companyForm;
+ /**
+ * 发照日期
+ */
+ private String issueDate;
+ /**
+ * 证照标题
+ */
+ private String title;
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/IdCard.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/IdCard.java
new file mode 100644
index 000000000..1318c835f
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/IdCard.java
@@ -0,0 +1,48 @@
+package com.ruoyi.framework.ocr;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author liangyq
+ * @date 2025-06-29
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class IdCard {
+ /**
+ * 身份证号码
+ */
+ private String idNumber;
+ /**
+ * 姓名
+ */
+ private String name;
+ /**
+ * 性别
+ */
+ private String sex;
+ /**
+ * 民族
+ */
+ private String ethnicity;
+ /**
+ * 出生日期
+ */
+ private String birthDate;
+ /**
+ * 住址
+ */
+ private String address;
+
+ /**
+ * 签发机关
+ */
+ private String issueAuthority;
+ /**
+ * 有效期限
+ */
+ private String validPeriod;
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/OcrClientWrapper.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/OcrClientWrapper.java
new file mode 100644
index 000000000..b2f4acc6d
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/OcrClientWrapper.java
@@ -0,0 +1,93 @@
+package com.ruoyi.framework.ocr;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.alibaba.fastjson2.JSONObject;
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.ocr_api20210707.models.RecognizeBusinessLicenseRequest;
+import com.aliyun.ocr_api20210707.models.RecognizeBusinessLicenseResponse;
+import com.aliyun.ocr_api20210707.models.RecognizeIdcardRequest;
+import com.aliyun.ocr_api20210707.models.RecognizeIdcardResponse;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.framework.ocr.ali.BusinessLicenseOcrResponse;
+import com.ruoyi.framework.ocr.ali.IdCardOcrResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author liangyq
+ * @date 2025-06-29
+ */
+@Slf4j
+@Component
+public class OcrClientWrapper implements InitializingBean {
+
+ @Value("${ocr.accessKeyId:}")
+ private String aliyunOAccessKeyId;
+
+ @Value("${ocr.accessKeySecret:}")
+ private String aliyunOAccessKeySecret;
+
+ @Value("${ocr.endpoint:}")
+ private String aliyunEndpoint;
+
+ private Client aliyunOcrClient;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
+ .setAccessKeyId(aliyunOAccessKeyId)
+ .setAccessKeySecret(aliyunOAccessKeySecret);
+ config.endpoint = aliyunEndpoint;
+ aliyunOcrClient = new Client(config);
+ }
+
+ /**
+ * 身份证识别
+ *
+ * @param url
+ * @return
+ */
+ public IdCard recognizeIdCard(String url) {
+ RecognizeIdcardRequest request = new RecognizeIdcardRequest();
+ try {
+ RecognizeIdcardResponse response = aliyunOcrClient.recognizeIdcard(request.setUrl(url));
+ String dataStr = response.getBody().getData();
+ log.info("阿里云身份证OCR结果:{}", dataStr);
+ IdCardOcrResponse ocrResp = JSONObject.parseObject(dataStr, IdCardOcrResponse.class);
+ IdCard idCard = new IdCard();
+ if (ocrResp != null && ocrResp.getData() != null) {
+ if (ocrResp.getData().getFace() != null
+ && ocrResp.getData().getFace().getData() != null) {
+ BeanUtil.copyProperties(ocrResp.getData().getFace().getData(), idCard);
+ }
+ if (ocrResp.getData().getBack() != null
+ && ocrResp.getData().getBack().getData() != null) {
+ BeanUtil.copyProperties(ocrResp.getData().getBack().getData(), idCard);
+ }
+ }
+ return idCard;
+ } catch (Exception e) {
+ log.error("阿里云身份证OCR异常", e);
+ }
+ throw new ServiceException("OCR失败");
+ }
+
+ public BusinessLicense recognizeBusinessLicense(String url) {
+ RecognizeBusinessLicenseRequest request = new RecognizeBusinessLicenseRequest();
+ try {
+ RecognizeBusinessLicenseResponse response = aliyunOcrClient.recognizeBusinessLicense(request.setUrl(url));
+ String dataStr = response.getBody().getData();
+ log.info("阿里云营业执照OCR结果:{}", dataStr);
+ BusinessLicenseOcrResponse ocrResp = JSONObject.parseObject(dataStr, BusinessLicenseOcrResponse.class);
+ if (ocrResp != null && ocrResp.getData() != null) {
+ return ocrResp.getData();
+ }
+ return new BusinessLicense();
+ } catch (Exception e) {
+ log.error("阿里云营业执照OCR异常", e);
+ }
+ throw new ServiceException("OCR失败");
+ }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/ali/BusinessLicenseOcrResponse.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/ali/BusinessLicenseOcrResponse.java
new file mode 100644
index 000000000..5dcfab063
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/ali/BusinessLicenseOcrResponse.java
@@ -0,0 +1,14 @@
+package com.ruoyi.framework.ocr.ali;
+
+import com.ruoyi.framework.ocr.BusinessLicense;
+
+/**
+ * @author liangyq
+ * @date 2025-06-29
+ */
+@lombok.Data
+public class BusinessLicenseOcrResponse {
+
+ private BusinessLicense data;
+
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/ali/IdCardOcrResponse.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/ali/IdCardOcrResponse.java
new file mode 100644
index 000000000..687f705d1
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/ocr/ali/IdCardOcrResponse.java
@@ -0,0 +1,75 @@
+package com.ruoyi.framework.ocr.ali;
+
+/**
+ * @author liangyq
+ * @date 2025-06-29
+ */
+@lombok.Data
+public class IdCardOcrResponse {
+
+ private Data data;
+
+ @lombok.Data
+ public static class Data {
+ /**
+ * 正面
+ */
+ private Face face;
+ /**
+ * 背面
+ */
+ private Back back;
+ }
+
+ @lombok.Data
+ public static class Face {
+
+ private Data data;
+
+ @lombok.Data
+ public static class Data {
+ /**
+ * 身份证号码
+ */
+ private String idNumber;
+ /**
+ * 姓名
+ */
+ private String name;
+ /**
+ * 性别
+ */
+ private String sex;
+ /**
+ * 民族
+ */
+ private String ethnicity;
+ /**
+ * 出生日期
+ */
+ private String birthDate;
+ /**
+ * 住址
+ */
+ private String address;
+ }
+ }
+
+ @lombok.Data
+ public static class Back {
+
+ private Data data;
+
+ @lombok.Data
+ public static class Data {
+ /**
+ * 签发机关
+ */
+ private String issueAuthority;
+ /**
+ * 有效期限
+ */
+ private String validPeriod;
+ }
+ }
+}