Selaa lähdekoodia

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

zjy 3 viikkoa sitten
vanhempi
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 = "委托人身份证")
     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 transactionLatitudeLongitudeAddress,
                                         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);
-        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("获取用户作为卖家的交易评价列表(分页)")

+ 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;
 
     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.setEntrustIdCard(trade.getEntrustIdCard());
                 secondEntrustUser.setEntrustTradeNo(trade.getTradeNo());
+                secondEntrustUser.setEntrustIdCardImg(trade.getEntrustIdCardImg());
                 secondEntrustUserMapper.insert(secondEntrustUser);
             }
 
@@ -165,6 +166,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
             message.put("entrustUserPhone", entrustUser.getEntrustUserPhone());
             message.put("entrustUserName", entrustUser.getEntrustUserName());
             message.put("entrustIdCard", entrustUser.getEntrustIdCard());
+            message.put("entrustIdCardImg", entrustUser.getEntrustIdCardImg());
         }
 
         if (6 == tradeStatus) {
@@ -600,6 +602,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
                         item.setEntrustUserPhone(entrustUser.getEntrustUserPhone());
                         item.setEntrustUserName(entrustUser.getEntrustUserName());
                         item.setEntrustIdCard(entrustUser.getEntrustIdCard());
+                        item.setEntrustIdCardImg(entrustUser.getEntrustIdCardImg());
                     }
                 }
             }
@@ -644,7 +647,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
     @Override
     @Transactional(rollbackFor = Exception.class)
     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 {
             if (type == 1) {
                 LambdaUpdateWrapper<SecondTradeRecord> wrapper = new LambdaUpdateWrapper<>();
@@ -695,6 +698,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
                     updateWrapper.set(SecondEntrustUser::getEntrustUserPhone, userPhone);
                     updateWrapper.set(SecondEntrustUser::getEntrustUserName, userName);
                     updateWrapper.set(SecondEntrustUser::getEntrustIdCard, idCard);
+                    updateWrapper.set(SecondEntrustUser::getEntrustIdCardImg, idCardImg);
                 }
                 secondEntrustUserMapper.update(null, updateWrapper);
             } else {
@@ -708,6 +712,7 @@ public class SecondTradeRecordServiceImpl extends ServiceImpl<SecondTradeRecordM
                                 .setEntrustUserPhone(userPhone)
                                 .setEntrustUserName(userName)
                                 .setEntrustIdCard(idCard)
+                                .setEntrustIdCardImg(idCardImg)
                                 .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.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
@@ -312,4 +313,25 @@ public class AliController {
                 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.springframework.beans.factory.annotation.Value;
 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.StoreAliPayErrorLog;
 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.StoreAliPayLogService;
 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.UniqueRandomNumGenerator;
 import shop.alien.util.common.UrlEncode;
 import shop.alien.util.system.OSUtil;
 
 import java.text.SimpleDateFormat;
+import java.util.Base64;
 import java.util.Date;
 
 /**
@@ -51,6 +56,8 @@ public class AliApi {
     private final StoreAliPayErrorLogService storeAliPayErrorLogService;
     
 
+    private final OcrStrategyFactory ocrStrategyFactory;
+
     /**
      * 商家端appId
      */
@@ -690,6 +697,46 @@ public class AliApi {
         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) {
         switch (subCode) {
@@ -730,4 +777,32 @@ public class AliApi {
         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>
         </dependency>
 
+        <!-- 阿里云OCR文字识别 -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>ocr_api20210707</artifactId>
+            <version>3.1.3</version>
+        </dependency>
+
         <!--openoffice-->
         <dependency>
             <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;
+    }
+}
+