Эх сурвалжийг харах

Merge branch 'second-trade' of alien/alien_cloud into sit

zjy 3 долоо хоног өмнө
parent
commit
bcc392c7df

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/second/vo/SecondTradeRecordVo.java

@@ -88,4 +88,7 @@ public class SecondTradeRecordVo extends SecondTradeRecord {
 
 
     @ApiModelProperty(value = "委托人身份证")
     @ApiModelProperty(value = "委托人身份证")
     private String entrustIdCard;
     private String entrustIdCard;
+
+    @ApiModelProperty(value = "委托人身份证图片")
+    private String entrustIdCardImg;
 }
 }

+ 2 - 2
alien-second/src/main/java/shop/alien/second/controller/SecondTradeRecordController.java

@@ -162,9 +162,9 @@ public class SecondTradeRecordController {
                                         String transactionLatitudeLongitude,
                                         String transactionLatitudeLongitude,
                                         String transactionLatitudeLongitudeAddress,
                                         String transactionLatitudeLongitudeAddress,
                                         String transactionLocation,
                                         String transactionLocation,
-                                        String messageId, String entrustUserPhone, String entrustUserName, String entrustIdCard, Integer entrustId) throws Exception {
+                                        String messageId, String entrustUserPhone, String entrustUserName, String entrustIdCard, Integer entrustId, String entrustIdCardImg) throws Exception {
         log.info("SecondTradeRecordController.modifyTradeRecord?record={}", transactionTime);
         log.info("SecondTradeRecordController.modifyTradeRecord?record={}", transactionTime);
-        return R.data(secondTradeRecordService.modifyTradeRecord(type, tradeId, transactionTime, transactionLatitudeLongitude, transactionLatitudeLongitudeAddress, transactionLocation, messageId, entrustUserPhone, entrustUserName, entrustIdCard, entrustId));
+        return R.data(secondTradeRecordService.modifyTradeRecord(type, tradeId, transactionTime, transactionLatitudeLongitude, transactionLatitudeLongitudeAddress, transactionLocation, messageId, entrustUserPhone, entrustUserName, entrustIdCard, entrustId, entrustIdCardImg));
     }
     }
 
 
     @ApiOperation("获取用户作为卖家的交易评价列表(分页)")
     @ApiOperation("获取用户作为卖家的交易评价列表(分页)")

+ 1 - 1
alien-second/src/main/java/shop/alien/second/service/SecondTradeRecordService.java

@@ -41,7 +41,7 @@ public interface SecondTradeRecordService extends IService<SecondTradeRecord> {
     SecondTradeRecord hasInTradeRecord(Integer sideId) throws Exception;
     SecondTradeRecord hasInTradeRecord(Integer sideId) throws Exception;
 
 
     boolean modifyTradeRecord(int type, Integer tradeId, String transactionTime, String transactionLatitudeLongitude, String transactionLatitudeLongitudeAddress, String transactionLocation, String messageId,
     boolean modifyTradeRecord(int type, Integer tradeId, String transactionTime, String transactionLatitudeLongitude, String transactionLatitudeLongitudeAddress, String transactionLocation, String messageId,
-                              String userPhone, String userName, String idCard, Integer entrustId) throws Exception;
+                              String userPhone, String userName, String idCard, Integer entrustId, String idCardImg) throws Exception;
 
 
     /**
     /**
      * 获取用户作为卖家的交易评价列表(分页)
      * 获取用户作为卖家的交易评价列表(分页)

+ 6 - 1
alien-second/src/main/java/shop/alien/second/service/impl/SecondTradeRecordServiceImpl.java

@@ -121,6 +121,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
                 secondEntrustUser.setEntrustUserName(trade.getEntrustUserName());
                 secondEntrustUser.setEntrustUserName(trade.getEntrustUserName());
                 secondEntrustUser.setEntrustIdCard(trade.getEntrustIdCard());
                 secondEntrustUser.setEntrustIdCard(trade.getEntrustIdCard());
                 secondEntrustUser.setEntrustTradeNo(trade.getTradeNo());
                 secondEntrustUser.setEntrustTradeNo(trade.getTradeNo());
+                secondEntrustUser.setEntrustIdCardImg(trade.getEntrustIdCardImg());
                 secondEntrustUserMapper.insert(secondEntrustUser);
                 secondEntrustUserMapper.insert(secondEntrustUser);
             }
             }
 
 
@@ -165,6 +166,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
             message.put("entrustUserPhone", entrustUser.getEntrustUserPhone());
             message.put("entrustUserPhone", entrustUser.getEntrustUserPhone());
             message.put("entrustUserName", entrustUser.getEntrustUserName());
             message.put("entrustUserName", entrustUser.getEntrustUserName());
             message.put("entrustIdCard", entrustUser.getEntrustIdCard());
             message.put("entrustIdCard", entrustUser.getEntrustIdCard());
+            message.put("entrustIdCardImg", entrustUser.getEntrustIdCardImg());
         }
         }
 
 
         if (6 == tradeStatus) {
         if (6 == tradeStatus) {
@@ -600,6 +602,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
                         item.setEntrustUserPhone(entrustUser.getEntrustUserPhone());
                         item.setEntrustUserPhone(entrustUser.getEntrustUserPhone());
                         item.setEntrustUserName(entrustUser.getEntrustUserName());
                         item.setEntrustUserName(entrustUser.getEntrustUserName());
                         item.setEntrustIdCard(entrustUser.getEntrustIdCard());
                         item.setEntrustIdCard(entrustUser.getEntrustIdCard());
+                        item.setEntrustIdCardImg(entrustUser.getEntrustIdCardImg());
                     }
                     }
                 }
                 }
             }
             }
@@ -644,7 +647,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
     @Override
     @Override
     @Transactional(rollbackFor = Exception.class)
     @Transactional(rollbackFor = Exception.class)
     public boolean modifyTradeRecord(int type, Integer tradeId, String transactionTime, String transactionLatitudeLongitude, String transactionLatitudeLongitudeAddress, String transactionLocation, String messageId,
     public boolean modifyTradeRecord(int type, Integer tradeId, String transactionTime, String transactionLatitudeLongitude, String transactionLatitudeLongitudeAddress, String transactionLocation, String messageId,
-                                     String userPhone, String userName, String idCard, Integer entrustId) throws Exception {
+                                     String userPhone, String userName, String idCard, Integer entrustId, String idCardImg) throws Exception {
         try {
         try {
             if (type == 1) {
             if (type == 1) {
                 LambdaUpdateWrapper<SecondTradeRecord> wrapper = new LambdaUpdateWrapper<>();
                 LambdaUpdateWrapper<SecondTradeRecord> wrapper = new LambdaUpdateWrapper<>();
@@ -695,6 +698,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
                     updateWrapper.set(SecondEntrustUser::getEntrustUserPhone, userPhone);
                     updateWrapper.set(SecondEntrustUser::getEntrustUserPhone, userPhone);
                     updateWrapper.set(SecondEntrustUser::getEntrustUserName, userName);
                     updateWrapper.set(SecondEntrustUser::getEntrustUserName, userName);
                     updateWrapper.set(SecondEntrustUser::getEntrustIdCard, idCard);
                     updateWrapper.set(SecondEntrustUser::getEntrustIdCard, idCard);
+                    updateWrapper.set(SecondEntrustUser::getEntrustIdCardImg, idCardImg);
                 }
                 }
                 secondEntrustUserMapper.update(null, updateWrapper);
                 secondEntrustUserMapper.update(null, updateWrapper);
             } else {
             } else {
@@ -708,6 +712,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
                                 .setEntrustUserPhone(userPhone)
                                 .setEntrustUserPhone(userPhone)
                                 .setEntrustUserName(userName)
                                 .setEntrustUserName(userName)
                                 .setEntrustIdCard(idCard)
                                 .setEntrustIdCard(idCard)
+                                .setEntrustIdCardImg(idCardImg)
                                 .setDeleteFlag(0));
                                 .setDeleteFlag(0));
                     }
                     }
                 }
                 }

+ 22 - 0
alien-store/src/main/java/shop/alien/store/controller/AliController.java

@@ -27,6 +27,7 @@ import javax.servlet.http.HttpServletResponse;
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Date;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
@@ -312,4 +313,25 @@ public class AliController {
                 outTradeNo, refundAmount, refundReason, partialRefundCode);
                 outTradeNo, refundAmount, refundReason, partialRefundCode);
         return aliApi.processRefund(outTradeNo, refundAmount, refundReason, partialRefundCode);
         return aliApi.processRefund(outTradeNo, refundAmount, refundReason, partialRefundCode);
     }
     }
+
+    /**
+     * 二手委托人识别(底层调用IDcard识别)
+     */
+    @ApiOperation("二手委托人识别(底层调用IDcard识别)")
+    @ApiOperationSupport(order = 16)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "imageUrl", value = "图片文件", dataType = "File", paramType = "query", required = true),
+            @ApiImplicitParam(name = "ocrType", value = "OCR识别类型", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userName", value = "委托人姓名", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "idCard", value = "委托人身份证号", dataType = "String", paramType = "query", required = true)
+    })
+    @PostMapping("/secondClient")
+    public R secondClient(@RequestBody Map<String, String> params)
+    {
+        try {
+            return aliApi.secondClient(params.get("imageUrl"), params.get("ocrType"), params.get("userName"), params.get("idCard"));
+        } catch (Exception e) {
+            return R.fail(e.getMessage());
+        }
+    }
 }
 }

+ 75 - 0
alien-store/src/main/java/shop/alien/store/util/ali/AliApi.java

@@ -14,6 +14,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;
@@ -22,12 +24,15 @@ import shop.alien.store.service.LifeUserService;
 import shop.alien.store.service.StoreAliPayErrorLogService;
 import shop.alien.store.service.StoreAliPayErrorLogService;
 import shop.alien.store.service.StoreAliPayLogService;
 import shop.alien.store.service.StoreAliPayLogService;
 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;
 import shop.alien.util.system.OSUtil;
 import shop.alien.util.system.OSUtil;
 
 
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Base64;
 import java.util.Date;
 import java.util.Date;
 
 
 /**
 /**
@@ -51,6 +56,8 @@ public class AliApi {
     private final StoreAliPayErrorLogService storeAliPayErrorLogService;
     private final StoreAliPayErrorLogService storeAliPayErrorLogService;
     
     
 
 
+    private final OcrStrategyFactory ocrStrategyFactory;
+
     /**
     /**
      * 商家端appId
      * 商家端appId
      */
      */
@@ -690,6 +697,46 @@ 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识别
+     *
+     * @param imageUrl 图片url
+     * @param ocrType OCR识别类型
+     * @return OCR识别结果
+     * @throws Exception 识别异常
+     */
+    public R ocrRequestUrl(String imageUrl, String ocrType) throws Exception {
+        OcrStrategy strategy = ocrStrategyFactory.getStrategy(ocrType);
+        return strategy.recognizeUrl(imageUrl);
+    }
+
+    /**
+     * 支付宝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) {
@@ -730,4 +777,32 @@ public class AliApi {
         return "支付宝请求错误";
         return "支付宝请求错误";
     }
     }
 
 
+    public R secondClient(String imageUrl, String ocrType, String userName, String idCard) {
+        try {
+            R r = this.ocrRequestUrl(imageUrl, ocrType);
+            r.getCode();
+            if(200 == r.getCode()) {
+                if(!JSONObject.parseObject(r.getData().toString()).containsKey("face")) {
+                    return R.fail("OCR识别异常:请上传身份证正面照片");
+                }
+                JSONObject jsonObject = JSONObject.parseObject(r.getData().toString()).getJSONObject("face").getJSONObject("data");
+                String name = jsonObject.getString("name");
+                String idCardNo = jsonObject.getString("idNumber");
+                if(userName.equals(name) && idCard.equals(idCardNo)) {
+                    return R.success("验证成功");
+                } else {
+                    return R.fail("委托人姓名或身份证号错误");
+                }
+            }
+        } catch (IllegalArgumentException e) {
+            // 7. 细化异常类型(JSON 解析失败,比如数据格式不匹配)
+            log.error("OCR 结果解析失败(数据格式异常):{}", e.getMessage(), e);
+            return R.fail("OCR识别异常:返回数据格式错误");
+        } catch (Exception e) {
+            // 8. 兜底异常(记录详细日志,便于排查)
+            log.error("委托人身份验证未知异常:{}", e.getMessage(), e);
+            return R.fail("委托人身份验证未知异常:"+e.getMessage());
+        }
+        return R.fail("OCR识别异常");
+    }
 }
 }

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

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

@@ -0,0 +1,49 @@
+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识别
+     *
+     * @param imageUrl 图片url
+     * @return OCR识别结果(JSON格式)
+     * @throws Exception 识别异常
+     */
+    R recognizeUrl(String imageUrl) 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);
+    }
+}
+
+
+

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

@@ -0,0 +1,115 @@
+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 {
+
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        return recognizeUrl(imageUrl);
+    }
+
+    @Override
+    public R recognizeUrl(String imageUrl) throws Exception {
+        Client client = createOcrClient();
+        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();
+    }
+}
+

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

@@ -0,0 +1,111 @@
+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 {
+
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        return recognizeUrl(imageUrl);
+    }
+
+    @Override
+    public R recognizeUrl(String imageUrl) throws Exception {
+        Client client = createOcrClient();
+        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();
+    }
+}

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

@@ -0,0 +1,113 @@
+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 {
+
+        // 从数据库中获取图片URL
+        StoreImg storeImage = storeImgService.getById(imageId);
+        String imageUrl = storeImage.getImgUrl();
+        return recognizeUrl(imageUrl);
+    }
+
+    @Override
+    public R recognizeUrl(String imageUrl) throws Exception {
+        Client client = createOcrClient();
+        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;
+    }
+}
+