Przeglądaj źródła

feat(ai): 集成AI审核任务功能

- 新增AI任务工具类AiTaskUtils,支持获取访问令牌和创建审核任务
- 实现用户违规举报的AI审核任务创建与结果查询功能
- 在商品审核流程中集成第二轮AI审核机制
- 添加定时任务goodsCheckJob和AiUserViolationJob处理AI审核结果
- 支持聊天记录文本提取与格式化用于AI分析
- 实现商品图片与描述的AI审核任务调度逻辑
- 提供完整的AI服务认证与任务状态管理功能
fcw 1 tydzień temu
rodzic
commit
98f3a750b8

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

@@ -3,6 +3,7 @@ package shop.alien.job.feign;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import shop.alien.entity.second.SecondGoods;
 
 @FeignClient(name = "alien-second", url = "${feign.alienSecond.url}")
 public interface SecondGoodsFeign {
@@ -24,4 +25,7 @@ public interface SecondGoodsFeign {
      */
     @GetMapping("/video/moderation/processTask")
     boolean processTask(@RequestParam("taskId") String taskId);
+
+    @GetMapping("/secondGoods/approveAndListGoods")
+    boolean approveAndListGoods(SecondGoods goods);
 }

+ 180 - 0
alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java

@@ -0,0 +1,180 @@
+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.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.LifeUserViolation;
+import shop.alien.entity.store.SecondAiTask;
+import shop.alien.mapper.LifeUserViolationMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiUserViolationJob {
+
+    private final RestTemplate restTemplate;
+    private final LifeUserViolationMapper lifeUserViolationMapper;
+
+    @Value("${third-party-user-name.base-url}")
+    private String userName;
+
+    @Value("${third-party-pass-word.base-url}")
+    private String passWord;
+
+    private String loginUrl = "http://192.168.2.78:9000/ai/user-auth-core/api/v1/auth/login";
+
+    private String aiUserViolationCheckUrl = "http://192.168.2.78:9000/ai/auto-review/api/v1/user_complaint_record/result";
+
+    @XxlJob("getAiGoodsCheckResult")
+    public R<String> getAiUserViolationResult() {
+        String accessToken = fetchAiServiceToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用AI举报用户辅助系统 登录接口失败");
+        }
+        return getAiUserViolationCheck(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);
+
+        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);
+        } catch (Exception e) {
+            log.error("调用差评申诉辅助系统登录接口异常", e);
+        }
+        return null;
+    }
+
+    private R<String> getAiUserViolationCheck(String accessToken) {
+
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+        // 查询所有状态为处理中的申诉
+        List<LifeUserViolation> pendingTasks = lifeUserViolationMapper.selectList(
+                new QueryWrapper<LifeUserViolation>()
+                        .eq("status", "0")
+        );
+
+        // 循环调用查询结果接口
+        for (LifeUserViolation task : pendingTasks) {
+            String completedUrl = buildCompletedUrl(task.getAiTaskId());
+
+            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("AI举报用户审核提交成功, 返回: {}", analyzeBody);
+
+                    JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
+                    JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
+
+                    if (dataJsonObj == null) {
+                        log.error("AI举报用户审核返回数据为空");
+                        R.fail("AI举报用户审核返回数据为空");
+                        continue;
+                    }
+
+                    // 获取task_id用于后续查询
+                    String taskId = dataJsonObj.getString("task_id");
+                    if (taskId == null) {
+                        log.error("AI举报用户审核返回task_id为空");
+                        R.fail("AI举报用户审核返回task_id为空");
+                        continue;
+                    }
+
+                    LifeUserViolation aiTask = new LifeUserViolation();
+                    aiTask.setAiTaskId(taskId);
+                    if (dataJsonObj.getString("status").equals("pending")) {
+                        R.fail("审核未结束");
+                        continue;
+                    }
+
+                    if (dataJsonObj.getString("status").equals("completed")) {
+                        if (dataJsonObj.getString("is_valid").isEmpty() && dataJsonObj.getString("is_valid").equals("true")) {
+                            aiTask.setProcessingStatus("1");
+                        } else {
+                            aiTask.setProcessingStatus("0");
+                        }
+                    }
+
+                    QueryWrapper<LifeUserViolation> queryWrapper = new QueryWrapper<>();
+                    queryWrapper.eq("ai_task_id", taskId);
+                    LifeUserViolation lifeUserViolation = lifeUserViolationMapper.selectOne(queryWrapper);
+                    if (lifeUserViolation == null) {
+                        log.error("AI举报用户不存在");
+                        R.fail("AI举报用户不存在");
+                        continue;
+                    }
+                    lifeUserViolationMapper.update(aiTask, queryWrapper);
+
+                } else {
+                    if (analyzeResp != null) {
+                        log.error("调用AI举报用户审核接口失败, http状态: {}", analyzeResp.getStatusCode());
+                        R.fail("调用AI举报用户审核接口失败, http状态: " + analyzeResp.getStatusCode());
+                    }
+                }
+            } catch (Exception e) {
+                log.error("调用差评申述查询结果接口异常", e);
+            }
+        }
+        return R.success("调用AI举报用户审核结果接口完成");
+    }
+
+    private String buildCompletedUrl(String recordId) {
+        String baseUrl = aiUserViolationCheckUrl;
+        if (!StringUtils.hasText(baseUrl)) {
+            throw new IllegalStateException("ai举报用户接口地址未配置");
+        }
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        // 构建新的URL格式: /api/v1/audit_task/getResult?task_id={recordId}
+        return baseUrl + "?task_id=" + recordId;
+    }
+}

+ 192 - 0
alien-job/src/main/java/shop/alien/job/second/goodsCheckJob.java

@@ -0,0 +1,192 @@
+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 shop.alien.second.service.SecondGoodsAuditService;
+import shop.alien.second.service.impl.SecondGoodsAuditServiceImpl;
+
+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.78:9000/ai/user-auth-core/api/v1/auth/login";
+
+    private String goodsCheckUrl = "http://192.168.2.78: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;
+
+    @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);
+
+        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);
+        } 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);
+        }
+        // 构建新的URL格式: /api/v1/audit_task/getResult?task_id={recordId}
+        return baseUrl + "?task_id=" + recordId;
+    }
+}

+ 1 - 1
alien-store/src/main/java/shop/alien/store/service/SecondAiTaskService.java → alien-second/src/main/java/shop/alien/second/service/SecondAiTaskService.java

@@ -1,4 +1,4 @@
-package shop.alien.store.service;
+package shop.alien.second.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.store.SecondAiTask;

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

@@ -1,5 +1,6 @@
 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;

+ 88 - 8
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsAuditServiceImpl.java

@@ -10,19 +10,22 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 import shop.alien.entity.SecondVideoTask;
 import shop.alien.entity.second.SecondGoods;
 import shop.alien.entity.second.SecondGoodsAudit;
 import shop.alien.entity.second.SecondGoodsRecord;
 import shop.alien.entity.second.enums.SecondGoodsStatusEnum;
 import shop.alien.entity.second.vo.SecondGoodsVo;
+import shop.alien.entity.store.SecondAiTask;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.mapper.SecondAiTaskMapper;
+import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.second.SecondGoodsAuditMapper;
 import shop.alien.mapper.second.SecondGoodsMapper;
 import shop.alien.mapper.second.SecondGoodsRecordMapper;
-import shop.alien.second.service.SecondGoodsAuditService;
-import shop.alien.second.service.SecondGoodsNotificationService;
-import shop.alien.second.service.SecondGoodsOperationRecordService;
-import shop.alien.second.service.VideoModerationService;
+import shop.alien.second.service.*;
+import shop.alien.second.util.AiTaskUtils;
 import shop.alien.util.common.Constants;
 import shop.alien.util.common.safe.ImageModerationResultVO;
 import shop.alien.util.common.safe.ImageModerationUtil;
@@ -46,6 +49,9 @@ import java.util.stream.Collectors;
 @RequiredArgsConstructor
 public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
 
+    private final SecondAiTaskService secondAiTaskService;
+    private final SecondAiTaskMapper secondAiTaskMapper;
+    private final StoreImgMapper storeImgMapper;
     /**
      * 视频审核功能是否启用的配置项
      */
@@ -98,6 +104,8 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
      */
     private final SecondGoodsOperationRecordService operationRecordService;
 
+    private final AiTaskUtils aiTaskUtil;
+
     /**
      * 执行内容审核
      * @param goodsDTO 商品信息
@@ -136,13 +144,69 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
             secondGoodsMapper.updateById(goods);
             // 审核中审核记录
             createGoodsAudit(goods, "", Constants.AuditStatus.UNDER_REVIEW);
-            return;
+        } else {
+            // 第二轮AI审核
+            boolean b = performSecondRoundReview(goods, goodsDTO);
+            if (!b) {
+                // 第二轮审核失败,记录操作历史
+                operationRecordService.recordGoodsOperation(goods, "第二轮审核失败");
+                goods.setGoodsStatus(2);
+                goods.setFailedReason("调用AI接口失败,未获取到task_id");
+                secondGoodsMapper.updateById(goods);
+            }
         }
         
         // 审核通过后上架商品
-        approveAndListGoods(goods);
+//        approveAndListGoods(goods);
     }
 
+    // 第二轮审核(AI)
+    private boolean performSecondRoundReview(SecondGoods goods, SecondGoodsVo goodsDTO) {
+        try {
+            // 参数校验
+            if (goodsDTO == null || CollectionUtil.isEmpty(goodsDTO.getImgUrl())) {
+                log.warn("Second round review skipped: empty image URLs for goods id={}",
+                        goods != null ? goods.getId() : "unknown");
+                return false;
+            }
+
+            // 获取访问令牌
+            String accessToken = aiTaskUtil.getAccessToken();
+            if (StringUtils.isEmpty(accessToken)) {
+                log.error("Failed to obtain access token for second round review, goods id={}", goods.getId());
+                return false;
+            }
+
+            // 创建AI任务
+            String taskId = aiTaskUtil.createTask(accessToken, goods.getDescription(), goodsDTO.getImgUrl());
+            if (StringUtils.isEmpty(taskId)) {
+                log.warn("Failed to create AI task for second round review, goods id={}", goods.getId());
+                return false;
+            }
+            goods.setAiTaskId(taskId);
+            goods.setGoodsStatus(SecondGoodsStatusEnum.UNDER_REVIEW.getCode());
+            secondGoodsMapper.updateById(goods);
+
+            // 保存任务信息
+            SecondAiTask secondAiTask = new SecondAiTask();
+            secondAiTask.setTaskId(taskId);
+            secondAiTask.setStatus("PROCESSING");
+            Date currentTime = new Date();
+            secondAiTask.setCreateTime(currentTime);
+            secondAiTask.setUpdateTime(currentTime);
+            secondAiTaskMapper.insert(secondAiTask);
+
+            log.info("Successfully created second round AI review task, taskId={}, goodsId={}",
+                    taskId, goods.getId());
+            return true;
+        } catch (Exception e) {
+            log.error("Error during second round review for goods id={}",
+                    goods != null ? goods.getId() : "unknown", e);
+            return false;
+        }
+    }
+
+
     /**
      * 执行图片审核
      * @param goods 商品信息
@@ -269,11 +333,27 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
                 log.warn("未找到关联的商品,任务ID: {}", task.getTaskId());
                 return;
             }
-            
+
+
             // 根据审核结果更新商品状态
             if ("none".equals(task.getRiskLevel())) {
+                QueryWrapper<StoreImg> imgQueryWrapper = new QueryWrapper<>();
+                imgQueryWrapper.eq("store_id", goods.getId());
+                imgQueryWrapper.eq("img_type", 19);
+                List<StoreImg> storeImgs = storeImgMapper.selectList(imgQueryWrapper);
+                List<String> imgUrls = storeImgs.stream()
+                        .map(StoreImg::getImgUrl)
+                        .filter(imgUrl -> StringUtils.hasText(imgUrl))
+                        .collect(Collectors.toList());
+
+                SecondGoodsVo goodsDTO = new SecondGoodsVo();
+                goodsDTO.setImgUrl(imgUrls);
+
+                // 开始第二轮审核
+                boolean b = performSecondRoundReview(goods, goodsDTO);
+
                 // 审核通过
-                approveAndListGoods(goods);
+//                approveAndListGoods(goods);
             } else {
                 // 审核不通过
                 goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode());

+ 146 - 0
alien-second/src/main/java/shop/alien/second/util/AiTaskUtils.java

@@ -0,0 +1,146 @@
+package shop.alien.second.util;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.map.HashedMap;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+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.store.StoreCommentAppeal;
+
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiTaskUtils {
+
+    private final RestTemplate restTemplate;
+
+//    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
+    private String loginUrl = "http://192.168.2.78:9000/ai/user-auth-core/api/v1/auth/login";
+
+    @Value("${third-party-user-name.base-url:UdUser}")
+    private String userName;
+
+    @Value("${third-party-pass-word.base-url:123456}")
+    private String passWord;
+
+    private String auditTaskUrl = "http://192.168.2.78:9000/ai/auto-review/api/v1/audit_task/product";
+
+
+
+    private String auditTaskResultUrl = "http://192.168.2.250:9000/ai/task-core/api/v1/audit_task/getResult";
+
+    /**
+     * 登录 AI 服务,获取 token
+     *
+     * @return accessToken
+     */
+    public String getAccessToken() {
+        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);
+
+        ResponseEntity<String> response;
+        try {
+            log.info("请求Ai服务登录接口===================>");
+            response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+        } catch (Exception e) {
+            log.error("请求AI服务登录接口失败", e);
+            return null;
+        }
+
+        if (response != null && response.getStatusCode() == HttpStatus.OK) {
+            String body = response.getBody();
+            log.info("请求Ai服务登录成功 postForEntity.getBody()\t{}", body);
+            if (StringUtils.hasText(body)) {
+                JSONObject jsonObject = JSONObject.parseObject(body);
+                if (jsonObject != null) {
+                    JSONObject dataJson = jsonObject.getJSONObject("data");
+                    if (dataJson != null) {
+                        return dataJson.getString("access_token");
+                    }
+                }
+            }
+            log.warn("AI服务登录响应解析失败 body: {}", body);
+            return null;
+        }
+
+        log.error("请求AI服务 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        return null;
+    }
+
+
+    /**
+     * 调用AI服务创建任务
+     *
+     * @return accessToken
+     */
+    public String createTask(String accessToken, String text, List<String> img_urls) {
+        log.info("创建Ai服务任务...{}", auditTaskUrl);
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+
+        Map<String, Object> analyzeRequest = new HashedMap<>();
+        analyzeRequest.put("text", StringUtils.hasText(text) ? text : "");
+        analyzeRequest.put("img_urls", img_urls);
+
+        HttpEntity<Map<String, Object>> analyzeEntity = new HttpEntity<>(analyzeRequest, analyzeHeaders);
+
+        ResponseEntity<String> analyzeResp = null;
+        try {
+            analyzeResp = restTemplate.postForEntity(auditTaskUrl, analyzeEntity, String.class);
+        } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
+            log.error("调用提交商品审核任务接口返回503 Service Unavailable错误: {}", e.getResponseBodyAsString());
+            return  null;
+        } catch (Exception e) {
+            log.error("调用提交商品审核任务接口异常", e);
+            return  null;
+        }
+
+        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("提交商品审核任务返回数据为空");
+                return  null;
+            }
+
+            // 获取record_id用于后续查询
+            String taskId = dataJsonObj.getString("task_id");
+            if (taskId == null) {
+                log.error("提交商品审核任务返回record_id为空");
+                R.fail("提交商品审核任务返回record_id为空");
+                return  null;
+            }
+            return taskId;
+        } else {
+            if (analyzeResp != null) {
+                log.error("调用提交商品审核任务接口失败, http状态: {}", analyzeResp.getStatusCode());
+                R.fail("调用提交商品审核任务接口失败, http状态: " + analyzeResp.getStatusCode());
+                return  null;
+            }
+        }
+        return  null;
+    }
+}

+ 17 - 1
alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java

@@ -27,6 +27,7 @@ import shop.alien.mapper.*;
 import shop.alien.mapper.second.SecondGoodsRecordMapper;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.*;
+import shop.alien.store.util.AiUserViolationUtils;
 import shop.alien.store.util.FunctionMagic;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.EnumUtil;
@@ -86,6 +87,8 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
     private final SecondGoodsRecordMapper secondGoodsRecordMapper;
 
+    private final AiUserViolationUtils aiUserViolationUtils;
+
     @Value("${spring.web.resources.excel-path}")
     private String excelPath;
 
@@ -106,7 +109,20 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
             }
             int result = lifeUserViolationMapper.insert(lifeuserViolation);
             if (result > 0) {
-                    //String phoneId = Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getString("userType") + "_" + JwtUtil.getCurrentUserInfo().getString("phone");
+                // AI审核
+                //登录获取token
+                String token = aiUserViolationUtils.getAccessToken();
+                //调用AI接口
+                String taskId = aiUserViolationUtils.createTask(token, lifeuserViolation);
+                if (org.springframework.util.StringUtils.isEmpty(taskId)) {
+                    log.warn("Failed to create AI task for second round review, lifeuserViolation id={}", lifeuserViolation.getId());
+                    return 0;
+                }
+                lifeuserViolation.setAiTaskId(taskId);
+                lifeUserViolationMapper.updateById(lifeuserViolation);
+
+
+                //String phoneId = Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getString("userType") + "_" + JwtUtil.getCurrentUserInfo().getString("phone");
                     // 举报人消息
                     LifeNotice lifeNotice = getLifeNotice(lifeuserViolation);
                     lifeNoticeMapper.insert(lifeNotice);

+ 245 - 0
alien-store/src/main/java/shop/alien/store/util/AiUserViolationUtils.java

@@ -0,0 +1,245 @@
+package shop.alien.store.util;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.map.HashedMap;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+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.store.LifeMessage;
+import shop.alien.entity.store.LifeUser;
+import shop.alien.entity.store.LifeUserViolation;
+import shop.alien.entity.store.vo.LifeUserVo;
+import shop.alien.mapper.LifeMessageMapper;
+import shop.alien.mapper.LifeUserMapper;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiUserViolationUtils {
+
+    private final RestTemplate restTemplate;
+    private final LifeUserMapper lifeUserMapper;
+    private final LifeMessageMapper lifeMessageMapper;
+
+    //    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
+    private String loginUrl = "http://192.168.2.78:9000/ai/user-auth-core/api/v1/auth/login";
+
+    @Value("${third-party-user-name.base-url:UdUser}")
+    private String userName;
+
+    @Value("${third-party-pass-word.base-url:123456}")
+    private String passWord;
+
+    private String userComplaintRecordUrl = "http://192.168.2.78:9000/ai/auto-review/api/v1/user_complaint_record/submit";
+
+
+    /**
+     * 登录 AI 服务,获取 token
+     *
+     * @return accessToken
+     */
+    public String getAccessToken() {
+        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);
+
+        ResponseEntity<String> response;
+        try {
+            log.info("请求Ai服务登录接口===================>");
+            response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+        } catch (Exception e) {
+            log.error("请求AI服务登录接口失败", e);
+            return null;
+        }
+
+        if (response != null && response.getStatusCode() == HttpStatus.OK) {
+            String body = response.getBody();
+            log.info("请求Ai服务登录成功 postForEntity.getBody()\t{}", body);
+            if (StringUtils.hasText(body)) {
+                JSONObject jsonObject = JSONObject.parseObject(body);
+                if (jsonObject != null) {
+                    JSONObject dataJson = jsonObject.getJSONObject("data");
+                    if (dataJson != null) {
+                        return dataJson.getString("access_token");
+                    }
+                }
+            }
+            log.warn("AI服务登录响应解析失败 body: {}", body);
+            return null;
+        }
+
+        log.error("请求AI服务 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        return null;
+    }
+
+
+    /**
+     * 调用AI服务创建任务
+     *
+     * @return accessToken
+     */
+    public String createTask(String accessToken, LifeUserViolation lifeuserViolation) {
+        log.info("创建Ai服务任务...{}", userComplaintRecordUrl);
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+
+        Map<String, Object> analyzeRequest = new HashedMap<>();
+        // 对应参数
+        analyzeRequest.put("complaint_type", lifeuserViolation.getViolationType());
+        analyzeRequest.put("reporter_user_id", lifeuserViolation.getReportingUserId());
+        analyzeRequest.put("reported_user_id", lifeuserViolation.getReportedUserId());
+
+        //举报人
+        String reporterUserId = lifeuserViolation.getReportingUserId();
+        //被举报人
+        String reportedUserId = lifeuserViolation.getReportedUserId();
+
+        //通过双方userId查询用户信息,查出用户电话,
+        QueryWrapper<LifeUserVo> reporterUserQueryWrapper = new QueryWrapper<>();
+        reporterUserQueryWrapper.eq("id", reporterUserId);
+        LifeUserVo reporterUserById = lifeUserMapper.getUserById(reporterUserQueryWrapper);
+        QueryWrapper<LifeUserVo> reportedUserQueryWrapper = new QueryWrapper<>();
+        reportedUserQueryWrapper.eq("id", reportedUserId);
+        LifeUserVo reportedUserById = lifeUserMapper.getUserById(reportedUserQueryWrapper);
+        // 用user_去拼接,查询life_message表,获取双方两天记录
+        String userReporterUserById = "user_" + reporterUserById.getUserPhone();
+        String userReportedUserById = "user_" + reportedUserById.getUserPhone();
+
+        //将双方两天记录拼接成text,传入analyzeRequest
+        // 获取双方最近的聊天记录
+        List<LifeMessage> chatMessages = getRecentChatMessages(userReporterUserById, userReportedUserById);
+
+        // 将聊天记录转换为文本格式
+        String text = convertMessagesToText(chatMessages);
+        analyzeRequest.put("conversation_context", StringUtils.hasText(text) ? text : "");
+
+//        analyzeRequest.put("text", StringUtils.hasText(text) ? text : "");
+//        analyzeRequest.put("img_urls", img_urls);
+
+        HttpEntity<Map<String, Object>> analyzeEntity = new HttpEntity<>(analyzeRequest, analyzeHeaders);
+
+        ResponseEntity<String> analyzeResp = null;
+        try {
+            analyzeResp = restTemplate.postForEntity(userComplaintRecordUrl, analyzeEntity, String.class);
+        } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
+            log.error("调用提交用户投诉审核任务接口返回503 Service Unavailable错误: {}", e.getResponseBodyAsString());
+            return  null;
+        } catch (Exception e) {
+            log.error("调用提交用户投诉审核任务接口异常", e);
+            return  null;
+        }
+
+        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("提交用户投诉审核任务返回数据为空");
+                return  null;
+            }
+
+            // 获取record_id用于后续查询
+            Integer taskId = dataJsonObj.getInteger("task_id");
+            if (taskId == null) {
+                log.error("提交用户投诉审核任务返回record_id为空");
+                R.fail("提交用户投诉审核任务返回record_id为空");
+                return  null;
+            }
+            return taskId.toString();
+        } else {
+            if (analyzeResp != null) {
+                log.error("调用提交用户投诉审核任务接口失败, http状态: {}", analyzeResp.getStatusCode());
+                R.fail("调用提交用户投诉审核任务接口失败, http状态: " + analyzeResp.getStatusCode());
+                return  null;
+            }
+        }
+        return  null;
+    }
+
+    /**
+     * 获取双方用户的最近聊天记录
+     * @param userReporterUserById 举报人ID
+     * @param userReportedUserById 被举报人ID
+     * @return 聊天记录列表
+     */
+    public List<LifeMessage> getRecentChatMessages(String userReporterUserById, String userReportedUserById) {
+        // 构建查询条件
+        QueryWrapper<LifeMessage> queryWrapper = new QueryWrapper<>();
+
+        // 查询双方的聊天记录
+        queryWrapper.and(wrapper -> wrapper
+                        .eq("sender_id", userReporterUserById)
+                        .eq("receiver_id", userReportedUserById))
+                .or(wrapper -> wrapper
+                        .eq("sender_id", userReportedUserById)
+                        .eq("receiver_id", userReporterUserById));
+
+        // 按时间倒序排列
+        queryWrapper.orderByDesc("create_time");
+
+        // 限制50条记录
+        queryWrapper.last("LIMIT 50");
+
+        return lifeMessageMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 将聊天消息列表转换为文本格式
+     * @param messages 聊天消息列表
+     * @return 格式化后的文本字符串
+     */
+    private String convertMessagesToText(List<LifeMessage> messages) {
+        if (messages == null || messages.isEmpty()) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        // 按时间顺序排列(因为数据库查询是倒序的,这里需要重新排序)
+        for (int i = messages.size() - 1; i >= 0; i--) {
+            LifeMessage message = messages.get(i);
+            // 格式:[时间] 发送者ID: 消息内容
+            sb.append("[").append(formatTime(message.getCreatedTime())).append("] ")
+                    .append(message.getSenderId()).append(": ")
+                    .append(message.getContent()).append("\n");
+        }
+
+        return sb.toString().trim();
+    }
+
+    /**
+     * 格式化时间显示
+     * @param date 时间
+     * @return 格式化后的时间字符串
+     */
+    private String formatTime(Date date) {
+        if (date == null) {
+            return "";
+        }
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return sdf.format(date);
+    }
+}