Jelajahi Sumber

feat(second-goods): implement video moderation result processing and approval handling

- Add handleGoodsApprovalSuccess method to manage post-approval listing
- Integrate risk control checks during goods approval process
- Create processVideoModerationResult endpoint and Feign client
- Implement processAllPendingVideoTasks to handle batch video reviews
- Update job handler to delegate video moderation processing
- Add performPublishRiskCheck method for publish-time risk evaluation
- Refactor video review logic to use new approval handling flow
- Lazy inject required services to avoid circular dependencies
wxd 1 Minggu lalu
induk
melakukan
3c9b2754ea

+ 7 - 0
alien-job/src/main/java/shop/alien/job/feign/SecondGoodsFeign.java

@@ -35,4 +35,11 @@ public interface SecondGoodsFeign {
      */
     @GetMapping("/secondGoods/getAiGoodsCheckResult")
     shop.alien.entity.result.R<String> getAiGoodsCheckResult();
+
+    /**
+     * 处理所有待处理的视频审核任务
+     * @return 处理结果
+     */
+    @GetMapping("/secondGoods/processVideoModerationResult")
+    shop.alien.entity.result.R<String> processVideoModerationResult();
 }

+ 5 - 41
alien-job/src/main/java/shop/alien/job/jobhandler/VideoModerationJobHandler.java

@@ -5,11 +5,8 @@ import com.xxl.job.core.handler.annotation.XxlJob;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
-import shop.alien.entity.SecondVideoTask;
+import shop.alien.entity.result.R;
 import shop.alien.job.feign.SecondGoodsFeign;
-import shop.alien.mapper.system.SecondVideoTaskMapper;
-
-import java.util.List;
 
 /**
  * 视频审核任务XXL-JOB处理器
@@ -20,57 +17,24 @@ import java.util.List;
 public class VideoModerationJobHandler {
 
     /**
-     * 视频审核任务Mapper
-     */
-    private final SecondVideoTaskMapper videoModerationTaskMapper;
-
-    /**
      * 二级商品服务Feign客户端
      */
     private final SecondGoodsFeign secondGoodsFeign;
     
     /**
      * 视频审核结果拉取任务
-     * 每30秒执行一次
+     * 通过Feign调用alien-second服务处理视频审核结果
      */
     @XxlJob("videoModerationResultJobHandler")
     public ReturnT<String> videoModerationResultJobHandler(String param) {
         try {
             log.info("开始执行视频审核结果拉取任务");
-            
-            // 查询待处理的审核任务(状态为SUBMITTED或PROCESSING)
-            List<SecondVideoTask> pendingTasks = videoModerationTaskMapper.selectPendingTasks();
-            
-            if (pendingTasks.isEmpty()) {
-                log.info("没有待处理的视频审核任务");
-                return ReturnT.SUCCESS;
-            }
-            
-            log.info("共找到 {} 个待处理的视频审核任务", pendingTasks.size());
-            
-            // 处理每个任务
-            int successCount = 0;
-            for (SecondVideoTask task : pendingTasks) {
-                try {
-                    boolean success = secondGoodsFeign.processTask(task.getTaskId());
-                    if (success) {
-                        successCount++;
-                        // 处理视频审核结果,更新商品状态
-                        log.info("处理第 {} 个task", successCount);
-                        log.info("TaskId:开始 {} ", task.getTaskId());
-                        secondGoodsFeign.processVideoResult(task.getTaskId());
-                        log.info("TaskId:结束 {} ", task.getTaskId());
-                    }
-                } catch (Exception e) {
-                    log.error("处理视频审核任务时发生异常,任务ID: {}", task.getTaskId(), e);
-                }
-            }
-            
-            log.info("视频审核结果拉取任务执行完成,成功处理 {} 个任务", successCount);
+            R<String> result = secondGoodsFeign.processVideoModerationResult();
+            log.info("视频审核结果拉取任务执行完成: {}", result.getData());
             return ReturnT.SUCCESS;
         } catch (Exception e) {
             log.error("执行视频审核结果拉取任务时发生异常", e);
             return ReturnT.FAIL;
         }
     }
-}
+}

+ 18 - 0
alien-second/src/main/java/shop/alien/second/controller/SecondGoodsController.java

@@ -340,4 +340,22 @@ public class SecondGoodsController {
             return R.fail("获取AI商品审核结果异常: " + e.getMessage());
         }
     }
+
+    /**
+     * 处理视频审核结果
+     * 查询所有待处理的视频审核任务,拉取审核结果并更新商品状态
+     * @return 处理结果
+     */
+    @ApiOperation("处理所有待处理的视频审核任务")
+    @GetMapping("/processVideoModerationResult")
+    public R<String> processVideoModerationResult() {
+        log.info("SecondGoodsController.processVideoModerationResult");
+        try {
+            String result = videoModerationService.processAllPendingVideoTasks();
+            return R.data(result, "视频审核结果处理完成");
+        } catch (Exception e) {
+            log.error("处理视频审核结果异常", e);
+            return R.fail("处理视频审核结果异常: " + e.getMessage());
+        }
+    }
 }

+ 6 - 0
alien-second/src/main/java/shop/alien/second/service/SecondGoodsAuditService.java

@@ -88,5 +88,11 @@ public interface SecondGoodsAuditService {
      * @return 处理结果
      */
     String getAiGoodsCheckResult();
+
+    /**
+     * 商品审核通过后的上架处理(包含风控检查)
+     * @param goods 商品信息
+     */
+    void handleGoodsApprovalSuccess(SecondGoods goods);
 }
 

+ 6 - 0
alien-second/src/main/java/shop/alien/second/service/SecondGoodsService.java

@@ -217,4 +217,10 @@ public interface SecondGoodsService extends IService<SecondGoods> {
      * @return 是否处理成功
      */
     boolean batchShelveGoodsByRiskControlRecord(Integer ruleType, String businessId);
+
+    /**
+     * 执行商品发布风控检查
+     * @param goods 商品信息
+     */
+    void performPublishRiskCheck(SecondGoods goods);
 }

+ 6 - 0
alien-second/src/main/java/shop/alien/second/service/VideoModerationService.java

@@ -32,4 +32,10 @@ public interface VideoModerationService  extends IService<SecondVideoTask> {
      * @return 视频审核任务
      */
     SecondVideoTask getTaskByTaskId(String taskId);
+
+    /**
+     * 处理所有待处理的视频审核任务
+     * @return 处理结果
+     */
+    String processAllPendingVideoTasks();
 }

+ 83 - 9
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsAuditServiceImpl.java

@@ -8,7 +8,9 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.google.common.collect.Lists;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import shop.alien.entity.SecondVideoTask;
@@ -105,6 +107,12 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
 
     private final AiTaskUtils aiTaskUtil;
 
+    @Lazy
+    @Autowired
+    private SecondGoodsService secondGoodsService;
+
+
+
     /**
      * 执行内容审核
      * @param goodsDTO 商品信息
@@ -407,6 +415,43 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
                 operationName = "首次发布";
             }
             operationRecordService.recordGoodsOperation(goods, operationName);
+
+            // 审核通过
+//                goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode());
+//                goods.setFailedReason("");
+//                goods.setReleaseTime(new Date());
+//                updateById(goods);
+//
+//                // 更新审核记录
+//                createGoodsAudit(goods, "", Constants.AuditStatus.PASSED);
+//
+//                // 发送审核成功消息
+//                sendMessage(goods);
+//                // 审核成功,记录操作历史
+//                // 审核成功,记录操作历史
+//                String operationName = "";
+//                QueryWrapper<SecondGoodsRecord> queryRecordWrapper = new QueryWrapper<>();
+//                queryRecordWrapper.eq("goods_id", goods.getId());
+//                log.info("查询操作记录开始 goods_id: {}", goods.getId());
+//                List<SecondGoodsRecord> recordList = secondGoodsRecordMapper.selectList(queryRecordWrapper);
+//                log.info("查询操作记录结束 recordList: {}", recordList);
+//                if (CollectionUtil.isNotEmpty(recordList)){
+//                    operationName = "重新发布";
+//                }else {
+//                    operationName = "首次发布";
+//                }
+//                recordGoodsOperation(goods, operationName);
+//
+//                // 检查用户是否在24小时内发布同类商品超过阈值
+//                if (!checkUserPublishSameCategoryLimit(goods)) {
+//                    log.warn("用户 {} 在24小时内发布同类商品次数超过限制", goods.getUserId());
+//                }
+//
+//                // 检查用户是否在24小时内发布商品超过阈值
+//                if (!checkUserPublishLimit(goods)) {
+//                    log.warn("用户 {} 在24小时内发布商品次数超过限制", goods.getUserId());
+//
+//                }
         } catch (Exception e) {
             log.error("商品上架过程中发生异常,执行回滚", e);
             // 如果通知已发送但后续操作失败,需要补偿
@@ -640,14 +685,14 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
                 if ("pass".equals(result)) {
                     // 审核通过,上架商品
                     log.info("AI审核通过,商品ID: {}, taskId: {}", goods.getId(), task.getTaskId());
-                    approveAndListGoods(goods);
+                    handleGoodsApprovalSuccess(goods);
                     return "success";
-                } else if ("reject".equals(result)) {
+                } else if ("reject".equals(result) || "suspect".equals(result)) {
                     // 审核拒绝,处理失败流程
                     String reason = dataJson.getString("reason");
                     String violation = dataJson.getString("violation");
                     String ruleHit = dataJson.getString("rule_hit");
-                    
+
                     // 构建失败原因
                     StringBuilder failReason = new StringBuilder("AI审核不通过");
                     if (StringUtils.hasText(reason)) {
@@ -659,23 +704,23 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
                     if (StringUtils.hasText(ruleHit)) {
                         failReason.append(",命中规则:").append(ruleHit);
                     }
-                    
+
                     log.info("AI审核拒绝,商品ID: {}, taskId: {}, 原因: {}", goods.getId(), task.getTaskId(), failReason);
-                    
+
                     // 更新商品状态为审核失败
                     goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode());
                     goods.setFailedReason(failReason.toString());
                     secondGoodsMapper.updateById(goods);
-                    
+
                     // 创建审核失败记录
                     createGoodsAudit(goods, failReason.toString(), Constants.AuditStatus.FAILED);
-                    
+
                     // 记录操作历史
                     operationRecordService.recordGoodsOperation(goods, "AI审核失败");
-                    
+
                     // 发送审核失败消息通知
                     notificationService.sendFailedMsg(goods);
-                    
+
                     return "reject";
                 }
             }
@@ -685,5 +730,34 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
             return "fail";
         }
     }
+
+    /**
+     * 商品审核通过后的上架处理(包含风控检查)
+     * @param goods 商品信息
+     */
+    @Override
+    public void handleGoodsApprovalSuccess(SecondGoods goods) {
+        // 审核通过,设置上架状态
+        goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode());
+        goods.setFailedReason("");
+        goods.setReleaseTime(new Date());
+        secondGoodsMapper.updateById(goods);
+
+        // 更新审核记录
+        createGoodsAudit(goods, "", Constants.AuditStatus.PASSED);
+
+        // 发送审核成功消息
+        notificationService.sendMessage(goods);
+
+        // 记录操作历史
+        QueryWrapper<SecondGoodsRecord> queryRecordWrapper = new QueryWrapper<>();
+        queryRecordWrapper.eq("goods_id", goods.getId());
+        List<SecondGoodsRecord> recordList = secondGoodsRecordMapper.selectList(queryRecordWrapper);
+        String operationName = CollectionUtil.isNotEmpty(recordList) ? "重新发布" : "首次发布";
+        operationRecordService.recordGoodsOperation(goods, operationName);
+
+        // 执行风控检查
+        secondGoodsService.performPublishRiskCheck(goods);
+    }
 }
 

+ 74 - 48
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java

@@ -751,7 +751,22 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
         return true;
     }
 
+    /**
+     * 执行商品发布风控检查
+     * @param goods 商品信息
+     */
+    @Override
+    public void performPublishRiskCheck(SecondGoods goods) {
+        // 检查用户是否在24小时内发布同类商品超过阈值
+        if (!checkUserPublishSameCategoryLimit(goods)) {
+            log.warn("用户 {} 在24小时内发布同类商品次数超过限制", goods.getUserId());
+        }
 
+        // 检查用户是否在24小时内发布商品超过阈值
+        if (!checkUserPublishLimit(goods)) {
+            log.warn("用户 {} 在24小时内发布商品次数超过限制", goods.getUserId());
+        }
+    }
 
     /**
      * 执行内容审核
@@ -1717,62 +1732,73 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
             
             // 根据审核结果更新商品状态
             if ("none".equals(task.getRiskLevel())) {
-                // 审核通过
-                goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode());
-                goods.setFailedReason("");
-                goods.setReleaseTime(new Date());
-                updateById(goods);
-                
-                // 更新审核记录
-                createGoodsAudit(goods, "", Constants.AuditStatus.PASSED);
-                
-                // 发送审核成功消息
-                sendMessage(goods);
-                // 审核成功,记录操作历史
-                // 审核成功,记录操作历史
-                String operationName = "";
-                QueryWrapper<SecondGoodsRecord> queryRecordWrapper = new QueryWrapper<>();
-                queryRecordWrapper.eq("goods_id", goods.getId());
-                log.info("查询操作记录开始 goods_id: {}", goods.getId());
-                List<SecondGoodsRecord> recordList = secondGoodsRecordMapper.selectList(queryRecordWrapper);
-                log.info("查询操作记录结束 recordList: {}", recordList);
-                if (CollectionUtil.isNotEmpty(recordList)){
-                    operationName = "重新发布";
-                }else {
-                    operationName = "首次发布";
-                }
-                recordGoodsOperation(goods, operationName);
-
-                // 检查用户是否在24小时内发布同类商品超过阈值
-                if (!checkUserPublishSameCategoryLimit(goods)) {
-                    log.warn("用户 {} 在24小时内发布同类商品次数超过限制", goods.getUserId());
-                }
-
-                // 检查用户是否在24小时内发布商品超过阈值
-                if (!checkUserPublishLimit(goods)) {
-                    log.warn("用户 {} 在24小时内发布商品次数超过限制", goods.getUserId());
-
-                }
-            } else {
-                log.warn("视频审核未通过,任务ID: {}", task.getTaskId());
+                log.warn("视频审核通过,任务ID: {}", task.getTaskId());
                 SecondGoodsVo secondGoodsVo = new SecondGoodsVo();
                 BeanUtils.copyProperties(goods, secondGoodsVo);
+                QueryWrapper<StoreImg> imgQueryWrapper = new QueryWrapper<>();
+                imgQueryWrapper.eq("store_id", goods.getId());
+                imgQueryWrapper.eq("img_type", 18);
+                List<StoreImg> storeImgs = storeImgMapper.selectList(imgQueryWrapper);
+                List<String> imgUrls = storeImgs.stream()
+                        .map(StoreImg::getImgUrl)
+                        .filter(imgUrl -> StringUtils.hasText(imgUrl))
+                        .collect(Collectors.toList());
+                secondGoodsVo.setImgUrl(imgUrls);
+
                 secondGoodsAuditService.performSecondRoundReview(goods, secondGoodsVo);
-//                // 审核不通过
-//                goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode());
 //
-//                // 解析审核结果,生成具体的失败原因
-//                String failedReason = parseVideoModerationFailureReason(task);
-//                goods.setFailedReason(failedReason);
+//                // 审核通过
+//                goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode());
+//                goods.setFailedReason("");
+//                goods.setReleaseTime(new Date());
 //                updateById(goods);
 //
 //                // 更新审核记录
-//                createGoodsAudit(goods, failedReason, Constants.AuditStatus.FAILED);
+//                createGoodsAudit(goods, "", Constants.AuditStatus.PASSED);
+//
+//                // 发送审核成功消息
+//                sendMessage(goods);
+//                // 审核成功,记录操作历史
+//                // 审核成功,记录操作历史
+//                String operationName = "";
+//                QueryWrapper<SecondGoodsRecord> queryRecordWrapper = new QueryWrapper<>();
+//                queryRecordWrapper.eq("goods_id", goods.getId());
+//                log.info("查询操作记录开始 goods_id: {}", goods.getId());
+//                List<SecondGoodsRecord> recordList = secondGoodsRecordMapper.selectList(queryRecordWrapper);
+//                log.info("查询操作记录结束 recordList: {}", recordList);
+//                if (CollectionUtil.isNotEmpty(recordList)){
+//                    operationName = "重新发布";
+//                }else {
+//                    operationName = "首次发布";
+//                }
+//                recordGoodsOperation(goods, operationName);
 //
-//                // 记录操作历史
-//                recordGoodsOperation(goods, "视频审核失败");
-//                // 发送审核失败消息
-//                sendFailedMsg(goods);
+//                // 检查用户是否在24小时内发布同类商品超过阈值
+//                if (!checkUserPublishSameCategoryLimit(goods)) {
+//                    log.warn("用户 {} 在24小时内发布同类商品次数超过限制", goods.getUserId());
+//                }
+//
+//                // 检查用户是否在24小时内发布商品超过阈值
+//                if (!checkUserPublishLimit(goods)) {
+//                    log.warn("用户 {} 在24小时内发布商品次数超过限制", goods.getUserId());
+//
+//                }
+            } else {
+                // 审核不通过
+                goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode());
+
+                // 解析审核结果,生成具体的失败原因
+                String failedReason = parseVideoModerationFailureReason(task);
+                goods.setFailedReason(failedReason);
+                updateById(goods);
+
+                // 更新审核记录
+                createGoodsAudit(goods, failedReason, Constants.AuditStatus.FAILED);
+
+                // 记录操作历史
+                recordGoodsOperation(goods, "视频审核失败");
+                // 发送审核失败消息
+                sendFailedMsg(goods);
             }
         } catch (Exception e) {
             log.error("处理视频审核结果时发生异常,任务ID: {}", task.getTaskId(), e);

+ 71 - 0
alien-second/src/main/java/shop/alien/second/service/impl/VideoModerationServiceImpl.java

@@ -6,13 +6,17 @@ import com.aliyun.green20220302.models.VideoModerationResultResponse;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import shop.alien.entity.SecondVideoTask;
 import shop.alien.mapper.system.SecondVideoTaskMapper;
+import shop.alien.second.service.SecondGoodsService;
 import shop.alien.second.service.VideoModerationService;
 import shop.alien.util.common.safe.video.VideoModerationUtil;
 
 import java.util.Date;
+import java.util.List;
 import java.util.UUID;
 
 /**
@@ -34,6 +38,13 @@ public class VideoModerationServiceImpl extends ServiceImpl<SecondVideoTaskMappe
     private final SecondVideoTaskMapper videoModerationTaskMapper;
 
     /**
+     * 二手商品服务(使用@Lazy避免循环依赖)
+     */
+    @Lazy
+    @Autowired
+    private SecondGoodsService secondGoodsService;
+
+    /**
      * 提交视频审核任务
      *
      * @param videoUrl 视频URL
@@ -201,4 +212,64 @@ public class VideoModerationServiceImpl extends ServiceImpl<SecondVideoTaskMappe
             return false;
         }
     }
+
+    /**
+     * 处理所有待处理的视频审核任务
+     * @return 处理结果
+     */
+    @Override
+    public String processAllPendingVideoTasks() {
+        try {
+            log.info("开始执行视频审核结果处理");
+
+            // 查询待处理的审核任务(状态为SUBMITTED或PROCESSING)
+            List<SecondVideoTask> pendingTasks = videoModerationTaskMapper.selectPendingTasks();
+
+            if (pendingTasks.isEmpty()) {
+                log.info("没有待处理的视频审核任务");
+                return "没有待处理的视频审核任务";
+            }
+
+            log.info("共找到 {} 个待处理的视频审核任务", pendingTasks.size());
+
+            int successCount = 0;
+            int processingCount = 0;
+            int failCount = 0;
+
+            for (SecondVideoTask task : pendingTasks) {
+                try {
+                    // 1. 拉取视频审核结果
+                    boolean success = processTask(task);
+                    if (success) {
+                        successCount++;
+                        // 2. 视频审核完成后,更新商品业务状态
+                        log.info("TaskId:开始处理商品状态 {}", task.getTaskId());
+                        SecondVideoTask updatedTask = videoModerationTaskMapper.selectByTaskId(task.getTaskId());
+                        if (updatedTask != null) {
+                            secondGoodsService.processVideoModerationResult(updatedTask);
+                        }
+                        log.info("TaskId:商品状态处理完成 {}", task.getTaskId());
+                    } else {
+                        // 检查任务状态判断是处理中还是失败
+                        SecondVideoTask updatedTask = videoModerationTaskMapper.selectByTaskId(task.getTaskId());
+                        if (updatedTask != null && "PROCESSING".equals(updatedTask.getStatus())) {
+                            processingCount++;
+                        } else {
+                            failCount++;
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("处理视频审核任务时发生异常,任务ID: {}", task.getTaskId(), e);
+                    failCount++;
+                }
+            }
+
+            String result = String.format("视频审核结果处理完成,成功:%d,处理中:%d,失败:%d", successCount, processingCount, failCount);
+            log.info(result);
+            return result;
+        } catch (Exception e) {
+            log.error("执行视频审核结果处理任务时发生异常", e);
+            return "执行异常: " + e.getMessage();
+        }
+    }
 }