浏览代码

add:OCR初版(未做存储)

lyx 3 周之前
父节点
当前提交
d4cbb13e54

+ 55 - 1
alien-store/src/main/java/shop/alien/store/controller/AliController.java

@@ -9,6 +9,9 @@ import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreAliPayLog;
 import shop.alien.entity.store.StoreAliPayLog;
 import shop.alien.store.service.AliService;
 import shop.alien.store.service.AliService;
+import shop.alien.store.service.LifeUserService;
+import shop.alien.store.service.StoreInfoService;
+import shop.alien.store.service.StoreUserService;
 import shop.alien.store.util.ali.AliApi;
 import shop.alien.store.util.ali.AliApi;
 import shop.alien.store.util.ali.AliSms;
 import shop.alien.store.util.ali.AliSms;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.ali.AliOSSUtil;
@@ -45,8 +48,15 @@ public class AliController {
     private final AlipayTradeRefund alipayTradeRefund;
     private final AlipayTradeRefund alipayTradeRefund;
 
 
     private final AliOSSUtil aliOSSUtil;
     private final AliOSSUtil aliOSSUtil;
+
     private final AliApi aliApi;
     private final AliApi aliApi;
 
 
+    private final StoreUserService storeUserService;
+
+    private final LifeUserService lifeUserService;
+
+    private final StoreInfoService storeInfoService;
+
     @ApiOperation("阿里回调")
     @ApiOperation("阿里回调")
     @ApiOperationSupport(order = 1)
     @ApiOperationSupport(order = 1)
     @GetMapping("/notify")
     @GetMapping("/notify")
@@ -230,4 +240,48 @@ public class AliController {
                                        @RequestParam(value = "partialRefundCode") String partialRefundCode) {
                                        @RequestParam(value = "partialRefundCode") String partialRefundCode) {
         return aliApi.processRefund(outTradeNo, refundAmount, refundReason, partialRefundCode);
         return aliApi.processRefund(outTradeNo, refundAmount, refundReason, partialRefundCode);
     }
     }
-}
+
+    /**
+     * 支付宝OCR识别
+     *
+     * @param imageId 图片id
+     * @param ocrType OCR识别类型
+     * @return OCR识别结果
+     * @throws Exception 识别异常
+     */
+    @ApiOperation("支付宝OCR识别方式一")
+    @ApiOperationSupport(order = 14)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "imageId", value = "图片id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "ocrType", value = "OCR识别类型", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/ocrRequest")
+    public R<JSONObject> ocrRequest(@RequestParam(value = "imageId") String imageId, @RequestParam(value = "ocrType") String ocrType) {
+        try {
+            return aliApi.ocrRequest(imageId, ocrType);
+        } catch (Exception e) {
+            return R.fail("OCR识别异常:"+e.getMessage());
+        }
+    }
+
+    /**
+     * 支付宝OCR识别
+     *
+     * @return OCR识别结果
+     * @throws Exception 识别异常
+     */
+    @ApiOperation("支付宝OCR识别方式二")
+    @ApiOperationSupport(order = 15)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "ocrType", value = "OCR识别类型", dataType = "String", paramType = "query", required = true)
+    })
+    @PostMapping("/ocrRequestByBase64")
+    public R ocrRequestByBase64(MultipartFile imageFile,
+                                @RequestParam("ocrType") String ocrType) {
+        try {
+            return aliApi.ocrRequestByBase64(imageFile, ocrType);
+        } catch (Exception e) {
+            return R.fail("OCR识别异常:"+e.getMessage());
+        }
+    }
+}

+ 49 - 5
alien-store/src/main/java/shop/alien/store/util/ali/AliApi.java

@@ -16,6 +16,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeUser;
 import shop.alien.entity.store.LifeUser;
 import shop.alien.entity.store.StoreAliPayErrorLog;
 import shop.alien.entity.store.StoreAliPayErrorLog;
 import shop.alien.entity.store.StoreAliPayLog;
 import shop.alien.entity.store.StoreAliPayLog;
@@ -26,6 +28,8 @@ import shop.alien.store.service.StoreAliPayErrorLogService;
 import shop.alien.store.service.StoreAliPayLogService;
 import shop.alien.store.service.StoreAliPayLogService;
 import shop.alien.store.service.StoreAliPayRefundLogService;
 import shop.alien.store.service.StoreAliPayRefundLogService;
 import shop.alien.store.service.StoreUserService;
 import shop.alien.store.service.StoreUserService;
+import shop.alien.store.util.ali.ocr.OcrStrategy;
+import shop.alien.store.util.ali.ocr.OcrStrategyFactory;
 import shop.alien.util.common.RandomCreateUtil;
 import shop.alien.util.common.RandomCreateUtil;
 import shop.alien.util.common.UniqueRandomNumGenerator;
 import shop.alien.util.common.UniqueRandomNumGenerator;
 import shop.alien.util.common.UrlEncode;
 import shop.alien.util.common.UrlEncode;
@@ -33,6 +37,7 @@ import shop.alien.util.system.OSUtil;
 
 
 import java.text.ParseException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Base64;
 import java.util.Date;
 import java.util.Date;
 
 
 /**
 /**
@@ -57,6 +62,8 @@ public class AliApi {
 
 
     private final StoreAliPayRefundLogService storeAliPayRefundLogService;
     private final StoreAliPayRefundLogService storeAliPayRefundLogService;
 
 
+    private final OcrStrategyFactory ocrStrategyFactory;
+
     /**
     /**
      * 商家端appId
      * 商家端appId
      */
      */
@@ -401,15 +408,25 @@ public class AliApi {
             model.setOrderTitle("商家转账");
             model.setOrderTitle("商家转账");
             // 设置收款方信息
             // 设置收款方信息
             Participant payeeInfo = new Participant();
             Participant payeeInfo = new Participant();
-            payeeInfo.setCertType("IDENTITY_CARD");
-            payeeInfo.setCertNo(idCard);
-            payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
-            if(account!=null){
+            // 智能判断收款方类型
+            if (account.startsWith("2088") && account.length() == 16) {
+                // 如果是商户ID格式(2088开头的16位数字)
+                payeeInfo.setIdentityType("ALIPAY_USER_ID");
+                log.info("检测到收款方为商户ID: {}", account);
+//                payeeInfo.setName("爱丽恩严(大连)商务科技有限公司");
+            } else {
+                // 默认为支付宝账号(手机号或邮箱)
+                payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
+                log.info("检测到收款方为支付宝账号: {}", account);
+                payeeInfo.setCertType("IDENTITY_CARD");
+                payeeInfo.setCertNo(idCard);
+                payeeInfo.setName(name);
+            }
+            if(null != account){
                 payeeInfo.setIdentity(account);
                 payeeInfo.setIdentity(account);
             }else{
             }else{
                 payeeInfo.setIdentity(phone);
                 payeeInfo.setIdentity(phone);
             }
             }
-            payeeInfo.setName(name);
             model.setPayeeInfo(payeeInfo);
             model.setPayeeInfo(payeeInfo);
             // 设置业务备注
             // 设置业务备注
             model.setRemark("爱丽恩提现业务");
             model.setRemark("爱丽恩提现业务");
@@ -734,6 +751,33 @@ public class AliApi {
         return sdf.format(new Date()) + RandomCreateUtil.getRandomNum(4);
         return sdf.format(new Date()) + RandomCreateUtil.getRandomNum(4);
     }
     }
 
 
+    /**
+     * 支付宝OCR识别(使用策略工厂模式)
+     *
+     * @param imageId 图片id
+     * @param ocrType OCR识别类型
+     * @return OCR识别结果
+     * @throws Exception 识别异常
+     */
+    public R ocrRequest(String imageId, String ocrType) throws Exception {
+        OcrStrategy strategy = ocrStrategyFactory.getStrategy(ocrType);
+        return strategy.recognize(imageId);
+    }
+
+    /**
+     * 支付宝OCR识别(Base64方式,使用策略工厂模式)
+     * 目前没用w
+     * @param imageFile 图片文件
+     * @param ocrType OCR识别类型
+     * @return OCR识别结果
+     * @throws Exception 识别异常
+     */
+    public R ocrRequestByBase64(MultipartFile imageFile, String ocrType) throws Exception {
+        OcrStrategy strategy = ocrStrategyFactory.getStrategy(ocrType);
+        byte[] imageBytes = imageFile.getBytes();
+        String imageBase64 = Base64.getEncoder().encodeToString(imageBytes);
+        return strategy.recognizeByBase64(imageBase64);
+    }
 
 
     private String getMassage(String subCode) {
     private String getMassage(String subCode) {
         switch (subCode) {
         switch (subCode) {

+ 95 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/AbstractOcrStrategy.java

@@ -0,0 +1,95 @@
+package shop.alien.store.util.ali.ocr;
+
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.tea.TeaException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * OCR策略抽象基类
+ * 提供公共的客户端创建和异常处理逻辑
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+public abstract class AbstractOcrStrategy implements OcrStrategy {
+    
+    @Value("${ali.ocr.accessKeyId:}")
+    protected String accessKeyId;
+    
+    @Value("${ali.ocr.accessKeySecret:}")
+    protected String accessKeySecret;
+    
+    @Value("${ali.ocr.endpoint:ocr-api.cn-hangzhou.aliyuncs.com}")
+    protected String endpoint;
+    
+    /**
+     * 创建OCR客户端
+     * 
+     * @return OCR客户端
+     * @throws Exception 创建客户端异常
+     */
+    protected Client createOcrClient() throws Exception {
+        // 工程代码建议使用更安全的无 AK 方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378657.html。
+        com.aliyun.credentials.Client credential = new com.aliyun.credentials.Client();
+        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
+                .setCredential(credential);
+        
+        // 如果配置了AccessKey,则使用配置的值,否则使用默认值(仅用于测试)
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(accessKeyId)) {
+            config.setAccessKeyId(accessKeyId);
+        } else {
+            // 默认值,仅用于测试,生产环境应该从配置文件读取
+            config.setAccessKeyId("LTAI5tLAUTQg7R1xaKvxAYJu");
+        }
+        
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(accessKeySecret)) {
+            config.setAccessKeySecret(accessKeySecret);
+        } else {
+            // 默认值,仅用于测试,生产环境应该从配置文件读取
+            config.setAccessKeySecret("ayVk34nK9vQZ2bs5vDCYftQCEXXN3B");
+        }
+        
+        // Endpoint 请参考 https://api.aliyun.com/product/ocr-api
+        config.endpoint = endpoint;
+        
+        return new Client(config);
+    }
+    
+    /**
+     * 处理OCR识别异常
+     * 
+     * @param error 异常对象
+     * @param errorMessage 错误消息
+     * @throws Exception 重新抛出的异常
+     */
+    protected void handleOcrException(TeaException error, String errorMessage) throws Exception {
+        log.error("OCR识别失败: {}", errorMessage, error);
+        // 错误 message
+        log.error("错误信息: {}", error.getMessage());
+        // 诊断地址
+        if (error.getData() != null && error.getData().get("Recommend") != null) {
+            log.error("诊断地址: {}", error.getData().get("Recommend"));
+        }
+        com.aliyun.teautil.Common.assertAsString(error.message);
+        throw new Exception("OCR识别失败: " + errorMessage, error);
+    }
+    
+    /**
+     * 处理通用异常
+     * 
+     * @param error 异常对象
+     * @param errorMessage 错误消息
+     * @throws Exception 重新抛出的异常
+     */
+    protected void handleOcrException(Exception error, String errorMessage) throws Exception {
+        if (error instanceof TeaException) {
+            handleOcrException((TeaException) error, errorMessage);
+        } else {
+            TeaException teaException = new TeaException(error.getMessage(), error);
+            handleOcrException(teaException, errorMessage);
+        }
+    }
+}
+

+ 40 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/OcrStrategy.java

@@ -0,0 +1,40 @@
+package shop.alien.store.util.ali.ocr;
+
+import shop.alien.entity.result.R;
+
+/**
+ * OCR识别策略接口
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+public interface OcrStrategy {
+    
+    /**
+     * 执行OCR识别
+     * 
+     * @param imageId 图片id
+     * @return OCR识别结果(JSON格式)
+     * @throws Exception 识别异常
+     */
+    R recognize(String imageId) throws Exception;
+    
+    /**
+     * 执行OCR识别(Base64方式)
+     * 
+     * @param imageBase64 图片Base64编码
+     * @return OCR识别结果(JSON格式)
+     * @throws Exception 识别异常
+     */
+    R recognizeByBase64(String imageBase64) throws Exception;
+    
+    /**
+     * 获取策略类型字符串
+     * 
+     * @return 策略类型字符串
+     */
+    String getType();
+}
+
+
+

+ 79 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/OcrStrategyFactory.java

@@ -0,0 +1,79 @@
+package shop.alien.store.util.ali.ocr;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * OCR策略工厂
+ * 根据OCR类型创建对应的策略实例
+ * 
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+public class OcrStrategyFactory {
+    
+    @Autowired
+    private List<OcrStrategy> ocrStrategies;
+    
+    private final Map<String, OcrStrategy> strategyMap = new HashMap<>();
+    
+    /**
+     * 初始化策略映射
+     */
+    @PostConstruct
+    public void init() {
+        if (ocrStrategies != null && !ocrStrategies.isEmpty()) {
+            for (OcrStrategy strategy : ocrStrategies) {
+                strategyMap.put(strategy.getType(), strategy);
+                log.info("注册OCR策略: {} -> {}", strategy.getType(), strategy.getClass().getSimpleName());
+            }
+        }
+    }
+    
+    /**
+     * 根据类型获取OCR策略
+     * 
+     * @param type OCR类型
+     * @return OCR策略实例
+     * @throws IllegalArgumentException 如果类型不存在
+     */
+    public OcrStrategy getStrategy(String type) {
+        OcrStrategy strategy = strategyMap.get(type);
+        if (strategy == null) {
+            throw new IllegalArgumentException("不支持的OCR类型: " + type);
+        }
+        return strategy;
+    }
+    
+    /**
+     * 根据类型代码获取OCR策略
+     * 
+     * @param typeCode 类型代码
+     * @return OCR策略实例
+     * @throws IllegalArgumentException 如果类型不存在
+     */
+    public OcrStrategy getStrategyByCode(String typeCode) {
+        return getStrategy(typeCode);
+    }
+    
+    /**
+     * 检查是否支持指定的OCR类型
+     * 
+     * @param type OCR类型
+     * @return 是否支持
+     */
+    public boolean supports(String type) {
+        return strategyMap.containsKey(type);
+    }
+}
+
+
+

+ 109 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/BusinessLicenseOcrStrategy.java

@@ -0,0 +1,109 @@
+package shop.alien.store.util.ali.ocr.strategy;
+
+import com.alibaba.fastjson.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.RecognizeBusinessLicenseResponseBody;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.store.service.StoreImgService;
+import shop.alien.store.util.ali.ocr.AbstractOcrStrategy;
+import shop.alien.util.common.constant.OcrTypeEnum;
+
+/**
+ * 营业执照OCR识别策略
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class BusinessLicenseOcrStrategy extends AbstractOcrStrategy {
+
+    private final StoreImgService storeImgService;
+
+    @Override
+    public R recognize(String imageId) throws Exception {
+        Client client = createOcrClient();
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        RecognizeBusinessLicenseRequest request = new RecognizeBusinessLicenseRequest()
+                .setUrl(imageUrl);
+        
+        try {
+            RecognizeBusinessLicenseResponse response = client.recognizeBusinessLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeBusinessLicenseResponseBody body = response.getBody();
+            
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+            
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("营业执照OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+            
+        } catch (TeaException error) {
+            handleOcrException(error, "营业执照识别失败");
+            return R.fail("营业执照识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "营业执照识别异常");
+            return R.fail("营业执照识别异常");
+        }
+    }
+    
+    @Override
+    public R recognizeByBase64(String imageBase64) throws Exception {
+        Client client = createOcrClient();
+        // 将Base64字符串转换为字节数组,然后创建InputStream
+        byte[] imageBytes = java.util.Base64.getDecoder().decode(imageBase64);
+        java.io.InputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
+        
+        RecognizeBusinessLicenseRequest request = new RecognizeBusinessLicenseRequest()
+                .setBody(imageInputStream);
+        
+        try {
+            RecognizeBusinessLicenseResponse response = client.recognizeBusinessLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeBusinessLicenseResponseBody body = response.getBody();
+            
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+            
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("营业执照OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+            
+        } catch (TeaException error) {
+            handleOcrException(error, "营业执照识别失败");
+            return R.fail("营业执照识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "营业执照识别异常");
+            return R.fail("营业执照识别异常");
+        } finally {
+            // 关闭流
+            if (imageInputStream != null) {
+                try {
+                    imageInputStream.close();
+                } catch (java.io.IOException e) {
+                    log.warn("关闭输入流失败", e);
+                }
+            }
+        }
+    }
+    
+    @Override
+    public String getType() {
+        return OcrTypeEnum.BUSINESS_LICENSE.getCode();
+    }
+}
+

+ 105 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/FoodManageLicenseOcrStrategy.java

@@ -0,0 +1,105 @@
+package shop.alien.store.util.ali.ocr.strategy;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.ocr_api20210707.models.*;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.store.service.StoreImgService;
+import shop.alien.store.util.ali.ocr.AbstractOcrStrategy;
+import shop.alien.util.common.constant.OcrTypeEnum;
+
+/**
+ * 食品经营许可证OCR识别策略
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class FoodManageLicenseOcrStrategy extends AbstractOcrStrategy {
+
+    private final StoreImgService storeImgService;
+
+    @Override
+    public R recognize(String imageId) throws Exception {
+        Client client = createOcrClient();
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        RecognizeFoodManageLicenseRequest request = new RecognizeFoodManageLicenseRequest()
+                .setUrl(imageUrl);
+
+        try {
+            RecognizeFoodManageLicenseResponse response = client.recognizeFoodManageLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeFoodManageLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("食品经营许可证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+
+        } catch (TeaException error) {
+            handleOcrException(error, "食品经营许可证识别失败");
+            return R.fail("食品经营许可证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "食品经营许可证识别异常");
+            return R.fail("食品经营许可证识别异常");
+        }
+    }
+
+    @Override
+    public R recognizeByBase64(String imageBase64) throws Exception {
+        Client client = createOcrClient();
+        // 将Base64字符串转换为字节数组,然后创建InputStream
+        byte[] imageBytes = java.util.Base64.getDecoder().decode(imageBase64);
+        java.io.InputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
+
+        RecognizeFoodManageLicenseRequest request = new RecognizeFoodManageLicenseRequest()
+                .setBody(imageInputStream);
+
+        try {
+            RecognizeFoodManageLicenseResponse response = client.recognizeFoodManageLicenseWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeFoodManageLicenseResponseBody body = response.getBody();
+
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("食品经营许可证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+        } catch (TeaException error) {
+            handleOcrException(error, "食品经营许可证识别失败");
+            return R.fail("食品经营许可证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "食品经营许可证识别异常");
+            return R.fail("食品经营许可证识别异常");
+        } finally {
+            // 关闭流
+            if (imageInputStream != null) {
+                try {
+                    imageInputStream.close();
+                } catch (java.io.IOException e) {
+                    log.warn("关闭输入流失败", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getType() {
+        return OcrTypeEnum.FOOD_MANAGE_LICENSE.getCode();
+    }
+}

+ 107 - 0
alien-store/src/main/java/shop/alien/store/util/ali/ocr/strategy/IdCardOcrStrategy.java

@@ -0,0 +1,107 @@
+package shop.alien.store.util.ali.ocr.strategy;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.ocr_api20210707.Client;
+import com.aliyun.ocr_api20210707.models.*;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.store.service.StoreImgService;
+import shop.alien.store.util.ali.ocr.AbstractOcrStrategy;
+import shop.alien.util.common.constant.OcrTypeEnum;
+
+/**
+ * 身份证OCR识别策略
+ *
+ * @author lyx
+ * @date 2025/11/18
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class IdCardOcrStrategy extends AbstractOcrStrategy {
+
+    private final StoreImgService storeImgService;
+
+    @Override
+    public R recognize(String imageId) throws Exception {
+        Client client = createOcrClient();
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        RecognizeIdcardRequest request = new RecognizeIdcardRequest()
+                .setUrl(imageUrl); // 默认识别正面,可根据需要修改为 "back" 识别反面
+        
+        try {
+            RecognizeIdcardResponse response = client.recognizeIdcardWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeIdcardResponseBody body = response.getBody();
+            
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+            
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("身份证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+            
+        } catch (TeaException error) {
+            handleOcrException(error, "身份证识别失败");
+            return R.fail("身份证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "身份证识别异常");
+            return R.fail("身份证识别异常");
+        }
+    }
+    
+    @Override
+    public R recognizeByBase64(String imageBase64) throws Exception {
+        Client client = createOcrClient();
+        // 将Base64字符串转换为字节数组,然后创建InputStream
+        byte[] imageBytes = java.util.Base64.getDecoder().decode(imageBase64);
+        java.io.InputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
+
+        RecognizeIdcardRequest request = new RecognizeIdcardRequest()
+                .setBody(imageInputStream); // 默认识别正面,可根据需要修改为 "back" 识别反面
+        
+        try {
+            RecognizeIdcardResponse response = client.recognizeIdcardWithOptions(
+                    request, new RuntimeOptions());
+            RecognizeIdcardResponseBody body = response.getBody();
+            
+            if (body == null || body.getData() == null) {
+                throw new Exception("OCR识别返回结果为空");
+            }
+            
+            JSONObject jsonObject = JSONObject.parseObject(body.getData());
+            log.info("身份证OCR识别成功: {}", jsonObject.getJSONObject("data"));
+            return R.data(jsonObject.getJSONObject("data"));
+            
+        } catch (TeaException error) {
+            handleOcrException(error, "身份证识别失败");
+            return R.fail("身份证识别失败");
+        } catch (Exception error) {
+            handleOcrException(error, "身份证识别异常");
+            return R.fail("身份证识别异常");
+        } finally {
+            // 关闭流
+            if (imageInputStream != null) {
+                try {
+                    imageInputStream.close();
+                } catch (java.io.IOException e) {
+                    log.warn("关闭输入流失败", e);
+                }
+            }
+        }
+    }
+    
+    @Override
+    public String getType() {
+        return OcrTypeEnum.ID_CARD.getCode();
+    }
+}
+

+ 7 - 0
alien-util/pom.xml

@@ -130,6 +130,13 @@
             <version>2.20.0</version>
             <version>2.20.0</version>
         </dependency>
         </dependency>
 
 
+        <!-- 阿里云OCR文字识别 -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>ocr_api20210707</artifactId>
+            <version>3.1.3</version>
+        </dependency>
+
         <!--openoffice-->
         <!--openoffice-->
         <dependency>
         <dependency>
             <groupId>com.artofsolving</groupId>
             <groupId>com.artofsolving</groupId>

+ 50 - 0
alien-util/src/main/java/shop/alien/util/common/constant/OcrTypeEnum.java

@@ -0,0 +1,50 @@
+package shop.alien.util.common.constant;
+
+/**
+ * OCR识别类型枚举
+ * 
+ * @author ssk
+ * @date 2024/12/10
+ */
+public enum OcrTypeEnum {
+    /**
+     * 营业执照识别
+     */
+    BUSINESS_LICENSE("BUSINESS_LICENSE", "营业执照识别"),
+    
+    /**
+     * 身份证识别
+     */
+    ID_CARD("ID_CARD", "身份证识别"),
+    /**
+     * 食品管理许可证识别
+     */
+    FOOD_MANAGE_LICENSE("FOOD_MANAGE_LICENSE", "食品经营许可证识别"),
+
+    /**
+     * 银行卡识别
+     */
+    BANK_CARD("BANK_CARD", "银行卡识别"),
+    
+    /**
+     * 通用文字识别
+     */
+    GENERAL("GENERAL", "通用文字识别");
+    
+    private final String code;
+    private final String description;
+    
+    OcrTypeEnum(String code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+    
+    public String getCode() {
+        return code;
+    }
+    
+    public String getDescription() {
+        return description;
+    }
+}
+