diff --git a/pom.xml b/pom.xml index 45852df0..892149f4 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,13 @@ 5.8.31 + + com.aliyun + ocr_api20210707 + 3.1.1 + + + me.lemire.integercompression JavaFastPFOR diff --git a/src/main/java/cn/lihongjie/coal/ocr/controller/OcrController.java b/src/main/java/cn/lihongjie/coal/ocr/controller/OcrController.java new file mode 100644 index 00000000..08ccba8e --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ocr/controller/OcrController.java @@ -0,0 +1,58 @@ +package cn.lihongjie.coal.ocr.controller; + +import cn.lihongjie.coal.annotation.SysLog; +import cn.lihongjie.coal.base.dto.CommonQuery; +import cn.lihongjie.coal.base.dto.IdRequest; +import cn.lihongjie.coal.ocr.dto.CreateOcrDto; +import cn.lihongjie.coal.ocr.dto.OCRRequest; +import cn.lihongjie.coal.ocr.dto.OcrDto; +import cn.lihongjie.coal.ocr.dto.UpdateOcrDto; +import cn.lihongjie.coal.ocr.service.OcrService; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/ocr") +@SysLog(module = "") +@Slf4j +public class OcrController { + @Autowired private OcrService service; + + @PostMapping("/ocr") + public Object ocr(@RequestBody OCRRequest request) { + return this.service.ocr(request); + } + + @PostMapping("/create") + public OcrDto create(@RequestBody CreateOcrDto request) { + return this.service.create(request); + } + + @PostMapping("/update") + public OcrDto update(@RequestBody UpdateOcrDto request) { + return this.service.update(request); + } + + @PostMapping("/delete") + public Object delete(@RequestBody IdRequest request) { + this.service.delete(request); + return true; + } + + @PostMapping("/getById") + public OcrDto getById(@RequestBody IdRequest request) { + return this.service.getById(request.getId()); + } + + @PostMapping("/list") + public Page list(@RequestBody CommonQuery request) { + return this.service.list(request); + } +} diff --git a/src/main/java/cn/lihongjie/coal/ocr/dto/CreateOcrDto.java b/src/main/java/cn/lihongjie/coal/ocr/dto/CreateOcrDto.java new file mode 100644 index 00000000..3cf729b8 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ocr/dto/CreateOcrDto.java @@ -0,0 +1,8 @@ +package cn.lihongjie.coal.ocr.dto; + +import cn.lihongjie.coal.base.dto.CommonDto; + +import lombok.Data; + +@Data +public class CreateOcrDto extends CommonDto {} diff --git a/src/main/java/cn/lihongjie/coal/ocr/dto/OCRRequest.java b/src/main/java/cn/lihongjie/coal/ocr/dto/OCRRequest.java new file mode 100644 index 00000000..adc3b228 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ocr/dto/OCRRequest.java @@ -0,0 +1,19 @@ +package cn.lihongjie.coal.ocr.dto; + +import lombok.Data; + +import java.util.*; + +@Data +public class OCRRequest { + + private String file; + + private String ocrType; + + + private Map options; + + + +} diff --git a/src/main/java/cn/lihongjie/coal/ocr/dto/OcrDto.java b/src/main/java/cn/lihongjie/coal/ocr/dto/OcrDto.java new file mode 100644 index 00000000..e159a87c --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ocr/dto/OcrDto.java @@ -0,0 +1,8 @@ +package cn.lihongjie.coal.ocr.dto; + +import cn.lihongjie.coal.base.dto.CommonDto; + +import lombok.Data; + +@Data +public class OcrDto extends CommonDto {} diff --git a/src/main/java/cn/lihongjie/coal/ocr/dto/UpdateOcrDto.java b/src/main/java/cn/lihongjie/coal/ocr/dto/UpdateOcrDto.java new file mode 100644 index 00000000..ac2f9329 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ocr/dto/UpdateOcrDto.java @@ -0,0 +1,8 @@ +package cn.lihongjie.coal.ocr.dto; + +import cn.lihongjie.coal.base.dto.CommonDto; + +import lombok.Data; + +@Data +public class UpdateOcrDto extends CommonDto {} diff --git a/src/main/java/cn/lihongjie/coal/ocr/entity/OcrEntity.java b/src/main/java/cn/lihongjie/coal/ocr/entity/OcrEntity.java new file mode 100644 index 00000000..2a7f54f9 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ocr/entity/OcrEntity.java @@ -0,0 +1,37 @@ +package cn.lihongjie.coal.ocr.entity; + +import cn.lihongjie.coal.base.entity.CommonEntity; +import cn.lihongjie.coal.file.entity.FileEntity; + +import jakarta.persistence.Entity; +import jakarta.persistence.ManyToOne; + +import lombok.Data; + +import org.hibernate.annotations.Comment; + +@Data +@Entity +public class OcrEntity extends CommonEntity { + + + @ManyToOne + private FileEntity file; + + + @Comment("ocr类型") + private String ocrType; + + + @Comment("ocr结果") + private String ocrResult; + + @Comment("耗时") + private Long costTime; + + + @Comment("ak") + private String ak; + + +} diff --git a/src/main/java/cn/lihongjie/coal/ocr/mapper/OcrMapper.java b/src/main/java/cn/lihongjie/coal/ocr/mapper/OcrMapper.java new file mode 100644 index 00000000..025de2f3 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ocr/mapper/OcrMapper.java @@ -0,0 +1,18 @@ +package cn.lihongjie.coal.ocr.mapper; + +import cn.lihongjie.coal.base.mapper.BaseMapper; +import cn.lihongjie.coal.base.mapper.CommonEntityMapper; +import cn.lihongjie.coal.base.mapper.CommonMapper; +import cn.lihongjie.coal.ocr.dto.CreateOcrDto; +import cn.lihongjie.coal.ocr.dto.OcrDto; +import cn.lihongjie.coal.ocr.dto.UpdateOcrDto; +import cn.lihongjie.coal.ocr.entity.OcrEntity; + +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; + +@Mapper( + componentModel = org.mapstruct.MappingConstants.ComponentModel.SPRING, + uses = {CommonMapper.class, CommonEntityMapper.class}, + mappingControl = DeepClone.class) +public interface OcrMapper extends BaseMapper {} diff --git a/src/main/java/cn/lihongjie/coal/ocr/repository/OcrRepository.java b/src/main/java/cn/lihongjie/coal/ocr/repository/OcrRepository.java new file mode 100644 index 00000000..de2fd430 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ocr/repository/OcrRepository.java @@ -0,0 +1,15 @@ +package cn.lihongjie.coal.ocr.repository; + +import cn.lihongjie.coal.base.dao.BaseRepository; +import cn.lihongjie.coal.ocr.entity.OcrEntity; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface OcrRepository extends BaseRepository { + @Query("select false") + boolean isLinked(List ids); +} diff --git a/src/main/java/cn/lihongjie/coal/ocr/service/OcrService.java b/src/main/java/cn/lihongjie/coal/ocr/service/OcrService.java new file mode 100644 index 00000000..f3f17324 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/ocr/service/OcrService.java @@ -0,0 +1,395 @@ +package cn.lihongjie.coal.ocr.service; + +import cn.lihongjie.coal.base.dto.CommonQuery; +import cn.lihongjie.coal.base.dto.IdRequest; +import cn.lihongjie.coal.base.service.BaseService; +import cn.lihongjie.coal.common.ReflectUtils; +import cn.lihongjie.coal.dbFunctions.DbFunctionService; +import cn.lihongjie.coal.exception.BizException; +import cn.lihongjie.coal.file.entity.FileEntity; +import cn.lihongjie.coal.file.service.FileService; +import cn.lihongjie.coal.ocr.dto.CreateOcrDto; +import cn.lihongjie.coal.ocr.dto.OCRRequest; +import cn.lihongjie.coal.ocr.dto.OcrDto; +import cn.lihongjie.coal.ocr.dto.UpdateOcrDto; +import cn.lihongjie.coal.ocr.entity.OcrEntity; +import cn.lihongjie.coal.ocr.mapper.OcrMapper; +import cn.lihongjie.coal.ocr.repository.OcrRepository; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Map; +import java.util.Optional; + +@Service +@Slf4j +@Transactional +public class OcrService extends BaseService { + @Autowired FileService fileService; + @PersistenceContext EntityManager em; + @Autowired ObjectMapper objectMapper; + @Autowired private OcrRepository repository; + @Autowired private OcrMapper mapper; + @Autowired private ConversionService conversionService; + @Autowired private DbFunctionService dbFunctionService; + @Autowired private com.aliyun.ocr_api20210707.Client ocrClient; + + @SneakyThrows + public Object ocr(OCRRequest request) { + + /** + *
+ * RecognizeIdcard - 身份证识别
+ * RecognizePassport - 护照识别
+ * RecognizeHousehold - 户口本识别
+ * RecognizeEstateCertification - 不动产权证识别
+ * RecognizeBankCard - 银行卡识别
+ * RecognizeBirthCertification - 出生证明识别
+ * RecognizeChinesePassport - 中国护照识别
+ * RecognizeExitEntryPermitToMainland - 来往大陆(内地)通行证识别
+ * RecognizeExitEntryPermitToHK - 往来港澳台通行证识别
+ * RecognizeHKIdcard - 中国香港身份证识别
+ * RecognizeSocialSecurityCardVersionII - 社保卡识别
+ * RecognizeInternationalIdcard - 国际身份证识别 + */ + FileEntity file = fileService.get(request.getFile()); + + // 使用缓存信息 + + Optional cached = + em + .createQuery( + "select e from OcrEntity e where e.file.id = :fileId and e.ocrType = :ocrType and e.status = 0", + OcrEntity.class) + .setParameter("fileId", file.getId()) + .setParameter("ocrType", request.getOcrType()) + .getResultList() + .stream() + .findFirst(); + + if (cached.isPresent()) { + + log.info("ocr cache hit: file {}, ocrType {} ", file.getId(), request.getOcrType()); + + return objectMapper.readTree(cached.get().getOcrResult()); + } + + String publicUrl = file.getPublicUrl(); + + Object req = null; + + Object resp = null; + + long start = System.currentTimeMillis(); + Exception exception = null; + + try { + + switch (request.getOcrType()) { + case "RecognizeIdcard": + { + com.aliyun.ocr_api20210707.models.RecognizeIdcardRequest + recognizeIdcardRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeIdcardRequest(); + recognizeIdcardRequest.setUrl(publicUrl); + + setOptions((req = recognizeIdcardRequest), request.getOptions()); + + resp = ocrClient.recognizeIdcard(recognizeIdcardRequest); + break; + } + + case "RecognizePassport": + { + com.aliyun.ocr_api20210707.models.RecognizePassportRequest + recognizePassportRequest = + new com.aliyun.ocr_api20210707.models + .RecognizePassportRequest(); + recognizePassportRequest.setUrl(publicUrl); + + setOptions((req = recognizePassportRequest), request.getOptions()); + + resp = ocrClient.recognizePassport(recognizePassportRequest); + break; + } + + case "RecognizeHousehold": + { + com.aliyun.ocr_api20210707.models.RecognizeHouseholdRequest + recognizeHouseholdRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeHouseholdRequest(); + recognizeHouseholdRequest.setUrl(publicUrl); + + setOptions((req = recognizeHouseholdRequest), request.getOptions()); + + resp = ocrClient.recognizeHousehold(recognizeHouseholdRequest); + break; + } + + case "RecognizeEstateCertification": + { + com.aliyun.ocr_api20210707.models.RecognizeEstateCertificationRequest + recognizeEstateCertificationRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeEstateCertificationRequest(); + recognizeEstateCertificationRequest.setUrl(publicUrl); + + setOptions( + (req = recognizeEstateCertificationRequest), request.getOptions()); + + resp = + ocrClient.recognizeEstateCertification( + recognizeEstateCertificationRequest); + break; + } + + case "RecognizeBankCard": + { + com.aliyun.ocr_api20210707.models.RecognizeBankCardRequest + recognizeBankCardRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeBankCardRequest(); + recognizeBankCardRequest.setUrl(publicUrl); + + setOptions((req = recognizeBankCardRequest), request.getOptions()); + + resp = ocrClient.recognizeBankCard(recognizeBankCardRequest); + break; + } + + case "RecognizeBirthCertification": + { + com.aliyun.ocr_api20210707.models.RecognizeBirthCertificationRequest + recognizeBirthCertificationRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeBirthCertificationRequest(); + recognizeBirthCertificationRequest.setUrl(publicUrl); + + setOptions( + (req = recognizeBirthCertificationRequest), request.getOptions()); + + resp = + ocrClient.recognizeBirthCertification( + recognizeBirthCertificationRequest); + break; + } + + case "RecognizeChinesePassport": + { + com.aliyun.ocr_api20210707.models.RecognizeChinesePassportRequest + recognizeChinesePassportRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeChinesePassportRequest(); + recognizeChinesePassportRequest.setUrl(publicUrl); + + setOptions((req = recognizeChinesePassportRequest), request.getOptions()); + + resp = ocrClient.recognizeChinesePassport(recognizeChinesePassportRequest); + break; + } + + case "RecognizeExitEntryPermitToMainland": + { + com.aliyun.ocr_api20210707.models.RecognizeExitEntryPermitToMainlandRequest + recognizeExitEntryPermitToMainlandRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeExitEntryPermitToMainlandRequest(); + recognizeExitEntryPermitToMainlandRequest.setUrl(publicUrl); + + setOptions( + (req = recognizeExitEntryPermitToMainlandRequest), + request.getOptions()); + + resp = + ocrClient.recognizeExitEntryPermitToMainland( + recognizeExitEntryPermitToMainlandRequest); + break; + } + + case "RecognizeExitEntryPermitToHK": + { + com.aliyun.ocr_api20210707.models.RecognizeExitEntryPermitToHKRequest + recognizeExitEntryPermitToHKRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeExitEntryPermitToHKRequest(); + recognizeExitEntryPermitToHKRequest.setUrl(publicUrl); + + setOptions( + (req = recognizeExitEntryPermitToHKRequest), request.getOptions()); + + resp = + ocrClient.recognizeExitEntryPermitToHK( + recognizeExitEntryPermitToHKRequest); + break; + } + + case "RecognizeHKIdcard": + { + com.aliyun.ocr_api20210707.models.RecognizeHKIdcardRequest + recognizeHKIdcardRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeHKIdcardRequest(); + recognizeHKIdcardRequest.setUrl(publicUrl); + + setOptions((req = recognizeHKIdcardRequest), request.getOptions()); + + resp = ocrClient.recognizeHKIdcard(recognizeHKIdcardRequest); + break; + } + + case "RecognizeSocialSecurityCardVersionII": + { + com.aliyun.ocr_api20210707.models + .RecognizeSocialSecurityCardVersionIIRequest + recognizeSocialSecurityCardVersionIIRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeSocialSecurityCardVersionIIRequest(); + recognizeSocialSecurityCardVersionIIRequest.setUrl(publicUrl); + + setOptions( + (req = recognizeSocialSecurityCardVersionIIRequest), + request.getOptions()); + + resp = + ocrClient.recognizeSocialSecurityCardVersionII( + recognizeSocialSecurityCardVersionIIRequest); + break; + } + + case "RecognizeInternationalIdcard": + { + com.aliyun.ocr_api20210707.models.RecognizeInternationalIdcardRequest + recognizeInternationalIdcardRequest = + new com.aliyun.ocr_api20210707.models + .RecognizeInternationalIdcardRequest(); + recognizeInternationalIdcardRequest.setUrl(publicUrl); + + setOptions( + (req = recognizeInternationalIdcardRequest), request.getOptions()); + + resp = + ocrClient.recognizeInternationalIdcard( + recognizeInternationalIdcardRequest); + break; + } + + default: + { + throw new BizException("不支持的识别类型 " + request.getOcrType()); + } + } + + } catch (Exception e) { + exception = e; + } finally { + + OcrEntity ocr = new OcrEntity(); + + ocr.setFile(file); + ocr.setOcrType(request.getOcrType()); + ocr.setCostTime(System.currentTimeMillis() - start); + ocr.setAk(ocrClient.getAccessKeyId()); + + if (exception != null) { + ocr.setStatus(1); + ocr.setRemarks(exception.getMessage()); + + } else { + ocr.setStatus(0); + ocr.setOcrResult(objectMapper.writeValueAsString(resp)); + } + + repository.save(ocr); + } + + if (exception != null) { + + log.info( + "ocr failed: \nclientInfo:{}\nreq:{}\nresp:{}", + ocrClient.getAccessKeyId(), + objectMapper.writeValueAsString(req), + objectMapper.writeValueAsString(resp), + exception); + + throw new BizException("ocr服务调用失败: " + exception.getMessage()); + } + + return resp; + } + + private void setOptions(Object obj, Map options) { + + ReflectUtils.getAllFieldsList(obj.getClass()) + .forEach( + f -> { + if (options.containsKey(f.getName())) { + ReflectUtils.writeField( + obj, + f.getName(), + conversionService.convert( + options.get(f.getName()), f.getType())); + } + }); + } + + public OcrDto create(CreateOcrDto request) { + + OcrEntity entity = mapper.toEntity(request); + + this.repository.save(entity); + return getById(entity.getId()); + } + + public OcrDto update(UpdateOcrDto request) { + OcrEntity entity = this.repository.get(request.getId()); + this.mapper.updateEntity(entity, request); + + this.repository.save(entity); + + return getById(entity.getId()); + } + + public void delete(IdRequest request) { + boolean linked = this.repository.isLinked(request.getIds()); + + if (linked) { + throw new BizException("数据已被关联,无法删除"); + } + + this.repository.deleteAllById(request.getIds()); + } + + public OcrDto getById(String id) { + OcrEntity entity = repository.get(id); + + return mapper.toDto(entity); + } + + public Page list(CommonQuery query) { + Page page = + repository.findAll( + query.specification(conversionService), + PageRequest.of( + query.getPageNo(), + query.getPageSize(), + Sort.by(query.getOrders()))); + + return page.map(this.mapper::toDto); + } +} diff --git a/src/main/java/cn/lihongjie/coal/spring/config/AliyunConfig.java b/src/main/java/cn/lihongjie/coal/spring/config/AliyunConfig.java index cbcb25b9..fa6c2c3f 100644 --- a/src/main/java/cn/lihongjie/coal/spring/config/AliyunConfig.java +++ b/src/main/java/cn/lihongjie/coal/spring/config/AliyunConfig.java @@ -2,6 +2,7 @@ package cn.lihongjie.coal.spring.config; import com.aliyun.oss.OSSClient; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -24,4 +25,26 @@ public class AliyunConfig { return ossClient; } + + @SneakyThrows + @Bean() + @Lazy + public com.aliyun.ocr_api20210707.Client ocrClient() { + + // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。 + // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html。 + com.aliyun.teaopenapi.models.Config config = + new com.aliyun.teaopenapi.models.Config() + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 + .setAccessKeyId(aliyunProperty.getOCR().getAk()) + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 + .setAccessKeySecret(aliyunProperty.getOCR().getSk()); + config.endpoint = aliyunProperty.getOCR().getEndpoint(); + return new com.aliyun.ocr_api20210707.Client(config); + + + + } + + } diff --git a/src/main/java/cn/lihongjie/coal/spring/config/AliyunProperty.java b/src/main/java/cn/lihongjie/coal/spring/config/AliyunProperty.java index e5c87b62..346b1f66 100644 --- a/src/main/java/cn/lihongjie/coal/spring/config/AliyunProperty.java +++ b/src/main/java/cn/lihongjie/coal/spring/config/AliyunProperty.java @@ -14,6 +14,23 @@ public class AliyunProperty { private OSS OSS; + + private OCR OCR; + + + @Data + public static class OCR { + + private String endpoint; + + private String ak; + + private String sk; + + + } + + @Data public static class OSS { diff --git a/src/main/resources/scripts/dict/enum/ocrDict.groovy b/src/main/resources/scripts/dict/enum/ocrDict.groovy new file mode 100644 index 00000000..c59d852c --- /dev/null +++ b/src/main/resources/scripts/dict/enum/ocrDict.groovy @@ -0,0 +1,17 @@ + +package scripts.dict + +import cn.lihongjie.coal.base.dto.CommonQuery +import cn.lihongjie.coal.ocr.controller.OcrController +import org.springframework.context.ApplicationContext + +ApplicationContext ioc = ioc + +def controller = ioc.getBean(OcrController.class) + + + + +return controller.list(new CommonQuery()) + +