Jelajahi Sumber

feat(second): implement AI goods audit result processing

- Refactored goodsCheckJob to use Feign client instead of direct HTTP calls
- Added getAiGoodsCheckResult method in SecondGoodsAuditService to handle AI audit results
- Implemented logic to query pending AI tasks and update their status
- Added controller endpoint to expose AI audit result processing
- Modified SecondGoodsServiceImpl to integrate second round review after video moderation
- Updated Feign client to include new AI audit result endpoint
- Removed redundant code and simplified AI task processing flow
wxd 1 Minggu lalu
induk
melakukan
97a9af171a

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

@@ -28,4 +28,11 @@ public interface SecondGoodsFeign {
 
     @GetMapping("/secondGoods/approveAndListGoods")
     boolean approveAndListGoods(SecondGoods goods);
+
+    /**
+     * 获取AI商品审核结果
+     * @return 处理结果
+     */
+    @GetMapping("/secondGoods/getAiGoodsCheckResult")
+    shop.alien.entity.result.R<String> getAiGoodsCheckResult();
 }

+ 13 - 167
alien-job/src/main/java/shop/alien/job/second/goodsCheckJob.java

@@ -1,190 +1,36 @@
 package shop.alien.job.second;
 
-import com.alibaba.fastjson2.JSONObject;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.xxl.job.core.handler.annotation.XxlJob;
 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.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.http.client.ClientHttpRequestInterceptor;
 import org.springframework.stereotype.Component;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.util.StringUtils;
-import org.springframework.web.client.RestTemplate;
 import shop.alien.entity.result.R;
-import shop.alien.entity.second.SecondGoods;
-import shop.alien.entity.store.SecondAiTask;
-import shop.alien.entity.store.StoreComment;
-import shop.alien.entity.store.StoreCommentAppeal;
 import shop.alien.job.feign.SecondGoodsFeign;
-import shop.alien.mapper.SecondAiTaskMapper;
-import shop.alien.mapper.second.SecondGoodsMapper;
-
-import javax.annotation.Resource;
-import java.util.ArrayList;
-import java.util.List;
 
+/**
+ * 商品审核定时任务
+ */
 @Slf4j
 @Component
 @RequiredArgsConstructor
 public class goodsCheckJob {
 
-    private final RestTemplate restTemplate;
-
-    private final SecondAiTaskMapper secondAiTaskMapper;
-    private final SecondGoodsMapper secondGoodsMapper;
-
     private final SecondGoodsFeign secondGoodsFeign;
 
-    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
-
-    private String goodsCheckUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/audit_task/getResult";
-
-    @Value("${third-party-user-name.base-url}")
-    private String userName;
-
-    @Value("${third-party-pass-word.base-url}")
-    private String passWord;
-
+    /**
+     * 获取AI商品审核结果
+     * 通过Feign调用alien-second服务处理AI审核结果
+     */
     @XxlJob("getAiGoodsCheckResult")
     public R<String> getAiGoodsCheckResult() {
-        String accessToken = fetchAiServiceToken();
-        if (!StringUtils.hasText(accessToken)) {
-            return R.fail("调用差评申诉辅助系统 登录接口失败");
-        }
-        return getGoodsCheck(accessToken);
-    }
-
-
-    private String fetchAiServiceToken() {
-        log.info("登录Ai服务获取token...{}", loginUrl);
-        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
-        formData.add("username", userName);
-        formData.add("password", passWord);
-
-        HttpHeaders headers = new HttpHeaders();
-        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
-        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
-
+        log.info("开始执行AI商品审核结果获取任务");
         try {
-            ResponseEntity<String> response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
-            if (response != null && response.getStatusCodeValue() == 200 && response.getBody() != null) {
-                JSONObject jsonObject = JSONObject.parseObject(response.getBody());
-                JSONObject dataJson = jsonObject.getJSONObject("data");
-                return dataJson != null ? dataJson.getString("access_token") : null;
-            }
-            log.error("请求差评申诉辅助系统 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+            R<String> result = secondGoodsFeign.getAiGoodsCheckResult();
+            log.info("AI商品审核结果获取任务执行完成: {}", result.getData());
+            return result;
         } catch (Exception e) {
-            log.error("调用差评申诉辅助系统登录接口异常", e);
-        }
-        return null;
-    }
-
-
-    private R<String> getGoodsCheck(String accessToken) {
-
-
-        HttpHeaders analyzeHeaders = new HttpHeaders();
-        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
-        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
-        // 查询所有状态为处理中的申诉
-        List<SecondAiTask> pendingTasks = secondAiTaskMapper.selectList(
-                new QueryWrapper<SecondAiTask>()
-                        .eq("status", "PROCESSING")
-        );
-
-        // 循环调用查询结果接口
-        for (SecondAiTask task : pendingTasks) {
-            String completedUrl = buildCompletedUrl(task.getTaskId());
-
-            ResponseEntity<String> analyzeResp;
-
-            RestTemplate restTemplateWithAuth = new RestTemplate();
-            List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
-            interceptors.add((request, body, execution) -> {
-                request.getHeaders().set("Authorization", "Bearer " + accessToken);
-                return execution.execute(request, body);
-            });
-            restTemplateWithAuth.setInterceptors(interceptors);
-
-            ResponseEntity<String> response = null;
-
-            try {
-                analyzeResp = restTemplateWithAuth.getForEntity(completedUrl, String.class);
-
-                if (analyzeResp != null && analyzeResp.getStatusCodeValue() == 200) {
-                    String analyzeBody = analyzeResp.getBody();
-                    log.info("商品审核提交成功, 返回: {}", analyzeBody);
-
-                    JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
-                    JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
-
-                    if (dataJsonObj == null) {
-                        log.error("商品审核返回数据为空");
-                        R.fail("商品审核返回数据为空");
-                        continue;
-                    }
-
-                    // 获取task_id用于后续查询
-                    String taskId = dataJsonObj.getString("task_id");
-                    if (taskId == null) {
-                        log.error("商品审核返回task_id为空");
-                        R.fail("商品审核返回task_id为空");
-                        continue;
-                    }
-
-                    SecondAiTask aiTask = new SecondAiTask();
-                    aiTask.setTaskId(taskId);
-                    if (dataJsonObj.getString("status").equals("pending")) {
-                        R.fail("审核未结束");
-                        continue;
-                    }
-
-                    if (dataJsonObj.getString("status").equals("done")) {
-                        aiTask.setStatus("SUCCESS");
-                    }
-
-                    aiTask.setResult(dataJsonObj.toJSONString());
-
-                    QueryWrapper<SecondGoods> queryWrapper = new QueryWrapper<>();
-                    queryWrapper.eq("ai_task_id", taskId);
-                    SecondGoods goods = secondGoodsMapper.selectOne(queryWrapper);
-                    if (goods == null) {
-                        log.error("商品不存在");
-                        R.fail("商品不存在");
-                        continue;
-                    }
-                    secondGoodsFeign.approveAndListGoods(goods);
-
-                } else {
-                    if (analyzeResp != null) {
-                        log.error("调用商品审核接口失败, http状态: {}", analyzeResp.getStatusCode());
-                        R.fail("调用商品审核接口失败, http状态: " + analyzeResp.getStatusCode());
-                    }
-                }
-            } catch (Exception e) {
-                log.error("调用差评申述查询结果接口异常", e);
-            }
-        }
-        return R.success("调用商品审核结果接口完成");
-    }
-
-
-    private String buildCompletedUrl(String recordId) {
-        String baseUrl = goodsCheckUrl;
-        if (!StringUtils.hasText(baseUrl)) {
-            throw new IllegalStateException("差评申述分析接口地址未配置");
-        }
-        if (baseUrl.endsWith("/")) {
-            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+            log.error("AI商品审核结果获取任务执行异常", e);
+            return R.fail("任务执行异常: " + e.getMessage());
         }
-        // 构建新的URL格式: /api/v1/audit_task/getResult?task_id={recordId}
-        return baseUrl + "?task_id=" + recordId;
     }
 }

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

@@ -12,6 +12,7 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.second.SecondGoods;
 import shop.alien.entity.second.vo.SecondGoodsVo;
 import shop.alien.mapper.second.SecondGoodsAuditMapper;
+import shop.alien.second.service.SecondGoodsAuditService;
 import shop.alien.second.service.SecondGoodsService;
 import shop.alien.second.service.VideoModerationService;
 import shop.alien.util.common.JwtUtil;
@@ -56,6 +57,9 @@ public class SecondGoodsController {
     // 注入图片审核工具
     private final ImageModerationUtil imageModerationUtil;
 
+    // 注入二手商品审核服务
+    private final SecondGoodsAuditService secondGoodsAuditService;
+
     /**
      * 根据ID获取二手商品
      */
@@ -318,4 +322,22 @@ public class SecondGoodsController {
             return false;
         }
     }
+
+    /**
+     * 获取AI商品审核结果
+     * 查询所有状态为处理中的AI审核任务,并更新审核结果
+     * @return 处理结果
+     */
+    @ApiOperation("获取AI商品审核结果")
+    @GetMapping("/getAiGoodsCheckResult")
+    public R<String> getAiGoodsCheckResult() {
+        log.info("SecondGoodsController.getAiGoodsCheckResult");
+        try {
+            String result = secondGoodsAuditService.getAiGoodsCheckResult();
+            return R.data(result, "获取AI审核结果完成");
+        } catch (Exception e) {
+            log.error("获取AI商品审核结果异常", e);
+            return R.fail("获取AI商品审核结果异常: " + e.getMessage());
+        }
+    }
 }

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

@@ -1,6 +1,5 @@
 package shop.alien.second.service;
 
-import org.springframework.stereotype.Service;
 import shop.alien.entity.SecondVideoTask;
 import shop.alien.entity.second.SecondGoods;
 import shop.alien.entity.second.vo.SecondGoodsVo;
@@ -82,5 +81,12 @@ public interface SecondGoodsAuditService {
     boolean isVideoUrl(String url);
 
     boolean performSecondRoundReview(SecondGoods goods, SecondGoodsVo goodsDTO);
+
+    /**
+     * 获取AI商品审核结果
+     * 查询所有状态为处理中的AI审核任务,并更新审核结果
+     * @return 处理结果
+     */
+    String getAiGoodsCheckResult();
 }
 

+ 144 - 1
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsAuditServiceImpl.java

@@ -178,7 +178,8 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
             }
 
             // 创建AI任务
-            String taskId = aiTaskUtil.createTask(accessToken, goods.getDescription(), goodsDTO.getImgUrl());
+            String test = goodsDTO.getDescription() + goodsDTO.getTitle() + goods.getLabel() + goods.getTopic();
+            String taskId = aiTaskUtil.createTask(accessToken, test, goodsDTO.getImgUrl());
             if (StringUtils.isEmpty(taskId)) {
                 log.warn("Failed to create AI task for second round review, goods id={}", goods.getId());
                 return false;
@@ -542,5 +543,147 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
         
         return reasonBuilder.toString();
     }
+
+    /**
+     * 获取AI商品审核结果
+     * 查询所有状态为处理中的AI审核任务,并更新审核结果
+     * @return 处理结果
+     */
+    @Override
+    public String getAiGoodsCheckResult() {
+        // 获取AI服务Token
+        String accessToken = aiTaskUtil.getAccessToken();
+        if (!StringUtils.hasText(accessToken)) {
+            log.error("调用AI服务登录接口失败,无法获取token");
+            return "调用AI服务登录接口失败";
+        }
+
+        // 查询所有状态为处理中的任务
+        List<SecondAiTask> pendingTasks = secondAiTaskMapper.selectList(
+                new QueryWrapper<SecondAiTask>().eq("status", "PROCESSING")
+        );
+
+        if (CollectionUtil.isEmpty(pendingTasks)) {
+            log.info("没有处理中的AI审核任务");
+            return "没有处理中的AI审核任务";
+        }
+
+        int passCount = 0;
+        int rejectCount = 0;
+        int pendingCount = 0;
+        int failCount = 0;
+
+        for (SecondAiTask task : pendingTasks) {
+            String result = processAiTaskResult(task, accessToken);
+            switch (result) {
+                case "success": passCount++; break;
+                case "reject": rejectCount++; break;
+                case "pending": pendingCount++; break;
+                default: failCount++;
+            }
+        }
+
+        return String.format("AI审核结果处理完成,通过:%d,拒绝:%d,处理中:%d,异常:%d", passCount, rejectCount, pendingCount, failCount);
+    }
+
+    /**
+     * 处理单个AI任务结果
+     * @param task AI任务
+     * @param accessToken 访问令牌
+     * @return success-审核通过, reject-审核拒绝, pending-处理中, fail-处理失败
+     */
+    private String processAiTaskResult(SecondAiTask task, String accessToken) {
+        String resultUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/audit_task/getResult?task_id=" + task.getTaskId();
+        try {
+            org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            org.springframework.web.client.RestTemplate restTemplate = new org.springframework.web.client.RestTemplate();
+            org.springframework.http.ResponseEntity<String> response = restTemplate.exchange(
+                    resultUrl, org.springframework.http.HttpMethod.GET,
+                    new org.springframework.http.HttpEntity<>(headers), String.class);
+
+            if (response.getStatusCodeValue() != 200 || response.getBody() == null) {
+                log.error("调用AI审核结果接口失败,taskId: {}, http状态: {}", task.getTaskId(), response.getStatusCode());
+                return "fail";
+            }
+
+            JSONObject dataJson = JSONObject.parseObject(response.getBody()).getJSONObject("data");
+            if (dataJson == null) {
+                log.error("AI审核返回数据为空,taskId: {}", task.getTaskId());
+                return "fail";
+            }
+
+            String status = dataJson.getString("status");
+            // 任务仍在处理中
+            if ("pending".equals(status)) {
+                return "pending";
+            }
+
+            // 任务已完成
+            if ("done".equals(status)) {
+                // 更新任务状态和结果
+                task.setStatus("SUCCESS");
+                task.setResult(dataJson.toJSONString());
+                task.setUpdateTime(new Date());
+                secondAiTaskMapper.updateById(task);
+
+                // 查询关联商品
+                SecondGoods goods = secondGoodsMapper.selectOne(
+                        new QueryWrapper<SecondGoods>().eq("ai_task_id", task.getTaskId()));
+                if (goods == null) {
+                    log.error("商品不存在,taskId: {}", task.getTaskId());
+                    return "fail";
+                }
+
+                String result = dataJson.getString("result");
+                if ("pass".equals(result)) {
+                    // 审核通过,上架商品
+                    log.info("AI审核通过,商品ID: {}, taskId: {}", goods.getId(), task.getTaskId());
+                    approveAndListGoods(goods);
+                    return "success";
+                } else if ("reject".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)) {
+                        failReason.append(":").append(reason);
+                    }
+                    if (StringUtils.hasText(violation)) {
+                        failReason.append(",违规类型:").append(violation);
+                    }
+                    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";
+                }
+            }
+            return "fail";
+        } catch (Exception e) {
+            log.error("处理AI审核任务结果异常,taskId: {}", task.getTaskId(), e);
+            return "fail";
+        }
+    }
 }
 

+ 29 - 17
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
+import com.alipay.api.domain.GoodsVO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -777,6 +778,7 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
             recordGoodsOperation(goods,"文本审核失败");
             return;
         }
+
         // 视频审核
         List<String> taskIds = performVideoReviews(goods, goodsDTO);
 
@@ -792,10 +794,16 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
 //            recordGoodsOperation(goods);
             return;
         }
+
+        List<String> videoUrls = extractVideoUrls(goodsDTO.getImgUrl());
+        if (videoUrls.isEmpty()) {
+            // ai 审核
+            secondGoodsAuditService.performSecondRoundReview(goods, goodsDTO);
+            return;
+        }
+
         // 审核通过后上架商品
         approveAndListGoods(goods);
-        // 开始第二轮审核
-//        boolean b = secondGoodsAuditService.performSecondRoundReview(goods, goodsDTO);
 
         // 检查用户是否在24小时内发布同类商品超过阈值
         if (!checkUserPublishSameCategoryLimit(goods)) {
@@ -1746,21 +1754,25 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
 
                 }
             } 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);
+                log.warn("视频审核未通过,任务ID: {}", task.getTaskId());
+                SecondGoodsVo secondGoodsVo = new SecondGoodsVo();
+                BeanUtils.copyProperties(goods, secondGoodsVo);
+                secondGoodsAuditService.performSecondRoundReview(goods, secondGoodsVo);
+//                // 审核不通过
+//                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);