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; + } + } +}