Răsfoiți Sursa

BugFix:对接AI的申诉删除功能

panzhilin 2 luni în urmă
părinte
comite
4dba55b2f2

+ 2 - 2
alien-entity/src/main/java/shop/alien/mapper/StoreCommentAppealMapper.java

@@ -87,9 +87,9 @@ public interface StoreCommentAppealMapper extends BaseMapper<StoreCommentAppeal>
     List<Map<String, Object>> getAppealList();
 
 
-    @Select("SELECT sca.id, sca.appeal_status, sca.final_result, sca.comment_id, sca.record_id " +
+    @Select("SELECT sca.id, sca.appeal_status, sca.final_result, sca.comment_id, sca.record_id, sca.store_id " +
             "FROM store_comment_appeal sca " +
-            "WHERE sca.appeal_status = 3")
+            "WHERE sca.appeal_status = 0 AND sca.record_id IS NOT NULL AND sca.delete_flag = 0")
     List<StoreCommentAppeal> getPendingAppeals();
 
     @Update("UPDATE store_comment_appeal " +

+ 204 - 26
alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java

@@ -14,6 +14,8 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
@@ -25,6 +27,14 @@ import shop.alien.mapper.LifeNoticeMapper;
 import shop.alien.mapper.StoreCommentAppealMapper;
 import shop.alien.mapper.StoreCommentMapper;
 import shop.alien.mapper.StoreUserMapper;
+import shop.alien.mapper.CommonRatingMapper;
+import shop.alien.mapper.CommonCommentMapper;
+import shop.alien.mapper.LifeLikeRecordMapper;
+import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.util.common.constant.CommentSourceTypeEnum;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import java.util.stream.Collectors;
 
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -43,6 +53,7 @@ import java.util.Map;
 @Slf4j
 @Component
 @RequiredArgsConstructor
+@EnableScheduling
 public class BadReviewAppealJob {
 
     private final RestTemplate restTemplate;
@@ -52,6 +63,10 @@ public class BadReviewAppealJob {
     private final StoreCommentMapper storeCommentMapper;
     private final StoreUserMapper storeUserMapper;
     private final LifeNoticeMapper lifeNoticeMapper;
+    private final CommonRatingMapper commonRatingMapper;
+    private final CommonCommentMapper commonCommentMapper;
+    private final LifeLikeRecordMapper lifeLikeRecordMapper;
+    private final StoreInfoMapper storeInfoMapper;
 
 //    @Value("${third-party-login.base-url}")
 //    private String loginUrl;
@@ -132,47 +147,74 @@ public class BadReviewAppealJob {
                     log.info("差评申述置信度分析提交成功, 返回: {}", analyzeBody);
 
                     JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
-//                    JSONObject details = analyzeJson.getJSONObject("details");
-//                    String analysisStatus = details.get("analysis_status").toString();
-//                    if (analysisStatus.equals("pending")) {
-//                        log.error("差评申述置信度分析尚未完成");
-//                        R.fail("差评申述置信度分析尚未完成");
-//                        continue;
-//                    }
+                    Integer respCode = analyzeJson.getInteger("code");
                     JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
 
+                    // 检查AI响应码:202表示分析尚未完成
+                    if (respCode != null && respCode == 202) {
+                        JSONObject detailsObj = analyzeJson.getJSONObject("details");
+                        String status = detailsObj != null ? detailsObj.getString("analysis_status") : "unknown";
+                        log.info("差评申述置信度分析尚未完成(code=202),申诉ID: {},recordId: {},状态: {},等待下次轮询", 
+                            appeal.getId(), appeal.getRecordId(), status);
+                        continue;
+                    }
+
+                    // 检查AI响应码:200表示分析完成
+                    if (respCode == null || respCode != 200) {
+                        log.warn("差评申述置信度分析返回非200响应码,申诉ID: {},code: {},message: {}", 
+                            appeal.getId(), respCode, analyzeJson.getString("message"));
+                        continue;
+                    }
+
                     if (dataJsonObj == null) {
-                        log.error("差评申述置信度分析返回数据为空");
-                        R.fail("差评申述置信度分析返回数据为空");
+                        log.error("差评申述置信度分析返回数据为空,申诉ID: {}", appeal.getId());
                         continue;
                     }
 
-                    // 获取record_id用于后续查询
-                    Integer recordId = dataJsonObj.getInteger("id");
-                    if (recordId == null) {
-                        log.error("差评申述置信度分析返回record_id为空");
-                        R.fail("差评申述置信度分析返回record_id为空");
+                    // 检查分析状态
+                    String analysisStatus = dataJsonObj.getString("analysis_status");
+                    if (!"completed".equals(analysisStatus)) {
+                        log.info("差评申述置信度分析尚未完成,申诉ID: {},recordId: {},状态: {}", 
+                            appeal.getId(), appeal.getRecordId(), analysisStatus);
                         continue;
                     }
 
+                    // 获取置信度值
+                    Double userConfidence = dataJsonObj.getDouble("user_confidence");
+                    Double merchantConfidence = dataJsonObj.getDouble("merchant_confidence");
+
+                    if (userConfidence == null || merchantConfidence == null) {
+                        log.error("差评申述置信度分析返回置信度为空,申诉ID: {},user_confidence: {},merchant_confidence: {}", 
+                            appeal.getId(), userConfidence, merchantConfidence);
+                        continue;
+                    }
+
+                    // 计算差值:差值>=10则同意,差值<10则驳回
+                    double confidenceDiff = Math.abs(userConfidence - merchantConfidence);
+                    log.info("差评申述置信度分析结果,申诉ID: {},user_confidence: {},merchant_confidence: {},差值: {}", 
+                        appeal.getId(), userConfidence, merchantConfidence, confidenceDiff);
+
                     StoreCommentAppeal sCommentAppeal = new StoreCommentAppeal();
                     sCommentAppeal.setRecordId(appeal.getRecordId());
-                    // 判断得分大小
-                    if (dataJsonObj.getDouble("user_confidence") > dataJsonObj.getDouble("merchant_confidence")){
-                        sCommentAppeal.setAppealStatus(1);
-                        sCommentAppeal.setFinalResult("已驳回");
-                    } else {
+                    sCommentAppeal.setAppealAiApproval(dataJsonObj.toJSONString());
+
+                    // 差值>=10则同意,差值<10则驳回
+                    if (confidenceDiff >= 10) {
                         sCommentAppeal.setAppealStatus(2);
                         sCommentAppeal.setFinalResult("已同意");
-                        //假删除评论
-                        StoreComment storeComment = new StoreComment();
-                        storeComment.setId(appeal.getCommentId());
-                        storeComment.setDeleteFlag(1);
-                        storeCommentMapper.updateById(storeComment);
+                        log.info("差评申述置信度差值>=10,申诉通过,申诉ID: {},差值: {}", appeal.getId(), confidenceDiff);
+                        
+                        // 删除评价和评论(与 StoreCommentAppealServiceImpl 逻辑一致)
+                        Integer ratingId = appeal.getCommentId(); // commentId 存储的是评价ID(common_rating.id)
+                        if (ratingId != null) {
+                            deleteRatingAndComments(ratingId);
+                        }
+                    } else {
+                        sCommentAppeal.setAppealStatus(1);
+                        sCommentAppeal.setFinalResult("已驳回");
+                        log.info("差评申述置信度差值<10,申诉驳回,申诉ID: {},差值: {}", appeal.getId(), confidenceDiff);
                     }
-                    sCommentAppeal.setAppealAiApproval(dataJsonObj.toJSONString());
 
-                    sCommentAppeal.setRecordId(recordId);
                     storeCommentAppealMapper.updateByRecordId(appeal.getRecordId(),
                             sCommentAppeal.getAppealStatus(),
                             sCommentAppeal.getFinalResult());
@@ -432,4 +474,140 @@ public class BadReviewAppealJob {
         return completedBase + "api/v1/records/" + recordId + "/completed";
     }
 
+    /**
+     * 定时查询AI分析结果
+     * 启动后延迟10秒,然后每10秒执行一次
+     */
+    @Scheduled(initialDelay = 10000, fixedRate = 10000)
+    public void scheduledQueryAppealResult() {
+        log.info("【定时任务】开始查询AI差评申诉分析结果...");
+        try {
+            String accessToken = fetchAiServiceToken();
+            if (!StringUtils.hasText(accessToken)) {
+                log.error("【定时任务】获取AI服务Token失败");
+                return;
+            }
+            getNegativeReviewAppealCompletedResult(accessToken);
+        } catch (Exception e) {
+            log.error("【定时任务】查询AI差评申诉分析结果异常", e);
+        }
+        log.info("【定时任务】查询AI差评申诉分析结果完成");
+    }
+
+    /**
+     * 删除评价和评论(审核通过时调用)
+     * 与 StoreCommentAppealServiceImpl 中的逻辑保持一致
+     *
+     * @param ratingId 评价ID(common_rating.id)
+     */
+    private void deleteRatingAndComments(Integer ratingId) {
+        try {
+            // 1. 删除评价(逻辑删除 common_rating 表)
+            CommonRating rating = commonRatingMapper.selectById(ratingId);
+            if (rating != null) {
+                int rows = commonRatingMapper.logicDeleteById(ratingId);
+                log.info("【定时任务】删除评价结果,ratingId={},影响行数={}", ratingId, rows);
+            }
+
+            // 2. 查询该评价下的所有评论ID(用于后续删除点赞记录)
+            LambdaQueryWrapper<CommonComment> commentQueryWrapper = new LambdaQueryWrapper<>();
+            commentQueryWrapper.eq(CommonComment::getSourceType, CommentSourceTypeEnum.STORE_COMMENT.getType())
+                    .eq(CommonComment::getSourceId, ratingId)
+                    .eq(CommonComment::getDeleteFlag, 0);
+            List<CommonComment> comments = commonCommentMapper.selectList(commentQueryWrapper);
+            List<Long> commentIds = comments.stream().map(CommonComment::getId).collect(Collectors.toList());
+
+            // 3. 删除该评价下的所有评论
+            int commentRows = commonCommentMapper.logicDeleteBySourceId(
+                    CommentSourceTypeEnum.STORE_COMMENT.getType(), ratingId);
+            log.info("【定时任务】删除评价下的评论结果,ratingId={},影响行数={}", ratingId, commentRows);
+
+            // 4. 删除评价的点赞记录(type=7 表示评价点赞)
+            LambdaUpdateWrapper<LifeLikeRecord> ratingLikeWrapper = new LambdaUpdateWrapper<>();
+            ratingLikeWrapper.eq(LifeLikeRecord::getType, "7")
+                    .eq(LifeLikeRecord::getHuifuId, String.valueOf(ratingId))
+                    .eq(LifeLikeRecord::getDeleteFlag, 0);
+            ratingLikeWrapper.set(LifeLikeRecord::getDeleteFlag, 1);
+            ratingLikeWrapper.set(LifeLikeRecord::getUpdatedTime, new java.util.Date());
+            lifeLikeRecordMapper.update(null, ratingLikeWrapper);
+            log.info("【定时任务】删除评价的点赞记录,ratingId={}", ratingId);
+
+            // 5. 删除评论的点赞记录(type=1 表示评论点赞)
+            if (!commentIds.isEmpty()) {
+                for (Long commentId : commentIds) {
+                    LambdaUpdateWrapper<LifeLikeRecord> commentLikeWrapper = new LambdaUpdateWrapper<>();
+                    commentLikeWrapper.eq(LifeLikeRecord::getType, "1")
+                            .eq(LifeLikeRecord::getHuifuId, String.valueOf(commentId))
+                            .eq(LifeLikeRecord::getDeleteFlag, 0);
+                    commentLikeWrapper.set(LifeLikeRecord::getDeleteFlag, 1);
+                    commentLikeWrapper.set(LifeLikeRecord::getUpdatedTime, new java.util.Date());
+                    lifeLikeRecordMapper.update(null, commentLikeWrapper);
+                }
+                log.info("【定时任务】删除评论的点赞记录,评论数={}", commentIds.size());
+            }
+
+            // 6. 重新统计店铺评分(删除评价后需要更新门店评分)
+            if (rating != null && rating.getBusinessType() == 1) {
+                updateStoreScore(rating.getBusinessId());
+            }
+        } catch (Exception e) {
+            log.error("【定时任务】删除评价和评论异常,ratingId={}", ratingId, e);
+        }
+    }
+
+    /**
+     * 更新店铺评分(删除评价后重新计算门店评分)
+     *
+     * @param businessId 店铺ID
+     */
+    private void updateStoreScore(Integer businessId) {
+        try {
+            // 查询该店铺的所有有效评价
+            LambdaQueryWrapper<CommonRating> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(CommonRating::getBusinessType, 1)
+                    .eq(CommonRating::getBusinessId, businessId)
+                    .eq(CommonRating::getDeleteFlag, 0)
+                    .eq(CommonRating::getIsShow, 1)
+                    .eq(CommonRating::getAuditStatus, 1);
+            List<CommonRating> ratings = commonRatingMapper.selectList(wrapper);
+
+            int total = ratings.size();
+            if (total == 0) {
+                // 没有评价,设置默认评分为0
+                StoreInfo storeInfo = new StoreInfo();
+                storeInfo.setId(businessId);
+                storeInfo.setScoreAvg(0.0);
+                storeInfo.setScoreOne(0.0);
+                storeInfo.setScoreTwo(0.0);
+                storeInfo.setScoreThree(0.0);
+                storeInfoMapper.updateById(storeInfo);
+                log.info("【定时任务】店铺无有效评价,重置评分为0,businessId={}", businessId);
+                return;
+            }
+
+            // 计算平均评分
+            double scoreSum = ratings.stream().mapToDouble(r -> r.getScore() != null ? r.getScore() : 0.0).sum();
+            double scoreOneSum = ratings.stream().mapToDouble(r -> r.getScoreOne() != null ? r.getScoreOne() : 0.0).sum();
+            double scoreTwoSum = ratings.stream().mapToDouble(r -> r.getScoreTwo() != null ? r.getScoreTwo() : 0.0).sum();
+            double scoreThreeSum = ratings.stream().mapToDouble(r -> r.getScoreThree() != null ? r.getScoreThree() : 0.0).sum();
+
+            double scoreAvg = Math.round((scoreSum / total) * 100.0) / 100.0;
+            double scoreOne = Math.round((scoreOneSum / total) * 100.0) / 100.0;
+            double scoreTwo = Math.round((scoreTwoSum / total) * 100.0) / 100.0;
+            double scoreThree = Math.round((scoreThreeSum / total) * 100.0) / 100.0;
+
+            StoreInfo storeInfo = new StoreInfo();
+            storeInfo.setId(businessId);
+            storeInfo.setScoreAvg(scoreAvg);
+            storeInfo.setScoreOne(scoreOne);
+            storeInfo.setScoreTwo(scoreTwo);
+            storeInfo.setScoreThree(scoreThree);
+            storeInfoMapper.updateById(storeInfo);
+
+            log.info("【定时任务】更新店铺评分成功,businessId={},评价数={},scoreAvg={}", businessId, total, scoreAvg);
+        } catch (Exception e) {
+            log.error("【定时任务】更新店铺评分失败,businessId={},error={}", businessId, e.getMessage());
+        }
+    }
+
 }

+ 491 - 1
alien-store/src/main/java/shop/alien/store/service/impl/StoreCommentAppealServiceImpl.java

@@ -14,11 +14,16 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartRequest;
 import shop.alien.entity.store.*;
@@ -31,6 +36,7 @@ import shop.alien.mapper.*;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.StoreCommentAppealService;
 import shop.alien.store.util.FileUploadUtil;
+import shop.alien.store.util.ai.AiAuthTokenUtil;
 import shop.alien.util.common.netease.TextCheckUtil;
 import shop.alien.util.common.safe.TextModerationResultVO;
 import shop.alien.util.common.safe.TextModerationUtil;
@@ -52,6 +58,7 @@ import java.util.stream.Collectors;
 @Service
 @Transactional
 @RequiredArgsConstructor
+@RefreshScope
 public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppealMapper, StoreCommentAppeal> implements StoreCommentAppealService {
 
     private final StoreCommentAppealMapper storeCommentAppealMapper;
@@ -85,6 +92,16 @@ public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppea
 
     private final LifeLikeRecordMapper lifeLikeRecordMapper;
 
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+
+    private final RestTemplate restTemplate;
+
+    @Value("${third-party-ai-analyze.base-url}")
+    private String analyzeUrl;
+
+    @Value("${third-party-ai-resultUrl.base-url}")
+    private String resultUrl;
+
     /**
      * 懒得查, 留着导出Excel
      */
@@ -324,10 +341,46 @@ public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppea
             storeCommentAppealLogSystem.setDeleteFlag(0);
             storeCommentAppealLogSystem.setCreatedTime(storeCommentAppeal.getCreatedTime());
             boolean resultSystem = storeCommentAppealSave && storeCommentAppealLogMapper.insert(storeCommentAppealLogSystem) > 0;
+            
+            // ========== 新增:调用AI分析接口并查询结果 ==========
+            if (result && resultSystem) {
+                try {
+                    // 1. 调用AI分析接口提交分析任务
+                    Integer recordId = invokeAiAnalyze(storeCommentAppeal);
+                    if (recordId != null) {
+                        // 2. 更新申诉记录,保存record_id
+                        StoreCommentAppeal updateAppeal = new StoreCommentAppeal();
+                        updateAppeal.setId(storeCommentAppeal.getId());
+                        updateAppeal.setRecordId(recordId);
+                        storeCommentAppealMapper.updateById(updateAppeal);
+                        
+                        // 3. 立即查询AI分析结果
+                        boolean queryResult = queryAiAnalysisResult(storeCommentAppeal.getId(), recordId);
+                        if (queryResult) {
+                            log.info("AI分析任务提交并查询结果成功,申诉ID: {},recordId: {}", 
+                                storeCommentAppeal.getId(), recordId);
+                        } else {
+                            log.warn("AI分析任务提交成功,但查询结果失败,申诉ID: {},recordId: {},将由定时任务后续查询", 
+                                storeCommentAppeal.getId(), recordId);
+                        }
+                        
+                        // 更新返回对象(recordId已通过BeanUtils.copyProperties复制)
+                    } else {
+                        log.warn("AI分析任务提交失败,但申诉记录已保存,申诉ID: {},状态保持: 待处理(0)", 
+                            storeCommentAppeal.getId());
+                        // AI调用失败不影响申诉提交,保持原状态(0:待处理)
+                    }
+                } catch (Exception e) {
+                    log.error("调用AI分析接口异常,申诉ID: {}", storeCommentAppeal.getId(), e);
+                    // AI调用失败不影响申诉提交,保持原状态(0:待处理)
+                }
+            }
+            // ========== 新增代码结束 ==========
+            
             storeCommentAppealInfoVo.setResult(result && resultSystem ? 0 : 1);
         } catch (Exception e) {
+            log.error("新增申诉异常", e);
             storeCommentAppealInfoVo.setResult(1);
-            return storeCommentAppealInfoVo;
         }
         return storeCommentAppealInfoVo;
     }
@@ -669,4 +722,441 @@ public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppea
             log.error("更新店铺评分失败,businessId={},error={}", businessId, e.getMessage());
         }
     }
+
+    /**
+     * 调用AI分析接口提交申诉分析任务
+     *
+     * @param storeCommentAppeal 申诉记录
+     * @return recordId AI返回的记录ID,失败返回null
+     */
+    private Integer invokeAiAnalyze(StoreCommentAppeal storeCommentAppeal) {
+        log.info("开始调用AI差评申诉置信度分析接口,申诉ID: {}", storeCommentAppeal.getId());
+        
+        // 1. 获取访问令牌(使用工具类)
+        String accessToken = aiAuthTokenUtil.getAccessToken();
+        if (!org.springframework.util.StringUtils.hasText(accessToken)) {
+            log.error("调用AI分析接口失败,获取accessToken失败,申诉ID: {}", storeCommentAppeal.getId());
+            return null;
+        }
+
+        // 2. 查询评论信息(CommonRating)
+        CommonRating rating = commonRatingMapper.selectById(storeCommentAppeal.getCommentId());
+        if (rating == null) {
+            log.error("查询评论信息失败,commentId: {}", storeCommentAppeal.getCommentId());
+            return null;
+        }
+
+        // 3. 构建请求参数
+        Map<String, Object> analyzeRequest = new HashMap<>();
+        
+        // 商家申诉材料(必填)
+        analyzeRequest.put("merchant_material", 
+            org.springframework.util.StringUtils.hasText(storeCommentAppeal.getAppealReason()) ? 
+                storeCommentAppeal.getAppealReason() : "");
+        
+        // 用户举证材料(必填)- 使用评价内容
+        analyzeRequest.put("user_material", 
+            org.springframework.util.StringUtils.hasText(rating.getContent()) ? rating.getContent() : "");
+        
+        // 商家图片:暂时传空数组,先测试文本分析流程
+        // TODO: 后续需要将图片转换为Base64
+        List<String> merchantImages = new ArrayList<>();
+        // if (org.springframework.util.StringUtils.hasText(storeCommentAppeal.getImgId())) {
+        //     String[] imgIds = storeCommentAppeal.getImgId().split(",");
+        //     for (String imgId : imgIds) {
+        //         StoreImg storeImg = storeImgMapper.selectById(imgId.trim());
+        //         if (storeImg != null && org.springframework.util.StringUtils.hasText(storeImg.getImgUrl())) {
+        //             String base64 = convertImageToBase64(storeImg.getImgUrl());
+        //             if (org.springframework.util.StringUtils.hasText(base64)) {
+        //                 merchantImages.add(base64);
+        //             }
+        //         }
+        //     }
+        // }
+        analyzeRequest.put("merchant_images", merchantImages);
+        
+        // 用户图片:暂时传空数组,先测试文本分析流程
+        // TODO: 后续需要将图片转换为Base64
+        List<String> userImages = new ArrayList<>();
+        // if (org.springframework.util.StringUtils.hasText(rating.getImageUrls())) {
+        //     String[] imageUrls = rating.getImageUrls().split(",");
+        //     for (String imageUrl : imageUrls) {
+        //         String trimmedUrl = imageUrl.trim();
+        //         if (org.springframework.util.StringUtils.hasText(trimmedUrl)) {
+        //             String base64 = convertImageToBase64(trimmedUrl);
+        //             if (org.springframework.util.StringUtils.hasText(base64)) {
+        //                 userImages.add(base64);
+        //             }
+        //         }
+        //     }
+        // }
+        analyzeRequest.put("user_images", userImages);
+        
+        // 其他字段(可选)
+        analyzeRequest.put("case_id", storeCommentAppeal.getCommentId() != null ? 
+            storeCommentAppeal.getCommentId().toString() : "");
+        analyzeRequest.put("order_id", rating.getBusinessId() != null ? 
+            rating.getBusinessId().toString() : "");
+
+        // 4. 设置请求头
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+        HttpEntity<Map<String, Object>> analyzeEntity = new HttpEntity<>(analyzeRequest, analyzeHeaders);
+
+        // 5. 调用AI接口
+        try {
+            log.info("调用AI分析接口,URL: {},申诉ID: {},商家图片数: {},用户图片数: {}", 
+                analyzeUrl, storeCommentAppeal.getId(), merchantImages.size(), userImages.size());
+            ResponseEntity<String> analyzeResp = restTemplate.postForEntity(analyzeUrl, analyzeEntity, String.class);
+            
+            if (analyzeResp != null && analyzeResp.getStatusCodeValue() == 200) {
+                String analyzeBody = analyzeResp.getBody();
+                log.info("AI差评申诉置信度分析提交成功,申诉ID: {},返回: {}", storeCommentAppeal.getId(), analyzeBody);
+
+                JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
+                JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
+
+                if (dataJsonObj != null) {
+                    // 获取record_id用于后续查询
+                    Integer recordId = dataJsonObj.getInteger("record_id");
+                    if (recordId != null) {
+                        log.info("AI分析任务提交成功,申诉ID: {},recordId: {}", storeCommentAppeal.getId(), recordId);
+                        return recordId;
+                    } else {
+                        log.error("AI分析接口返回record_id为空,申诉ID: {},响应: {}", 
+                            storeCommentAppeal.getId(), analyzeBody);
+                    }
+                } else {
+                    log.error("AI分析接口返回data为空,申诉ID: {},响应: {}", 
+                        storeCommentAppeal.getId(), analyzeBody);
+                }
+            } else {
+                log.error("调用AI分析接口失败,申诉ID: {},http状态: {}", 
+                    storeCommentAppeal.getId(), analyzeResp != null ? analyzeResp.getStatusCode() : null);
+            }
+        } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
+            log.error("调用AI分析接口返回503 Service Unavailable错误,申诉ID: {},错误: {}", 
+                storeCommentAppeal.getId(), e.getResponseBodyAsString());
+        } catch (Exception e) {
+            log.error("调用AI分析接口异常,申诉ID: {}", storeCommentAppeal.getId(), e);
+        }
+        
+        return null;
+    }
+
+    /**
+     * 查询AI分析结果并更新申诉状态
+     * 差值>=10则同意,差值<10则驳回
+     *
+     * @param appealId 申诉ID
+     * @param recordId AI返回的记录ID
+     * @return 是否查询成功
+     */
+    private boolean queryAiAnalysisResult(Integer appealId, Integer recordId) {
+        log.info("开始查询AI分析结果,申诉ID: {},recordId: {}", appealId, recordId);
+        
+        // 1. 获取访问令牌
+        String accessToken = aiAuthTokenUtil.getAccessToken();
+        if (!org.springframework.util.StringUtils.hasText(accessToken)) {
+            log.error("查询AI分析结果失败,获取accessToken失败,申诉ID: {},recordId: {}", appealId, recordId);
+            return false;
+        }
+
+        // 2. 构建查询URL
+        String completedUrl = buildCompletedUrl(recordId);
+        
+        // 3. 设置请求头
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        headers.set("Authorization", "Bearer " + accessToken);
+        HttpEntity<String> requestEntity = new HttpEntity<>(headers);
+
+        try {
+            log.info("查询AI分析结果,URL: {},申诉ID: {},recordId: {}", completedUrl, appealId, recordId);
+            ResponseEntity<String> response = restTemplate.exchange(completedUrl, 
+                org.springframework.http.HttpMethod.GET, requestEntity, String.class);
+
+            if (response != null && response.getStatusCodeValue() == 200) {
+                String responseBody = response.getBody();
+                log.info("AI分析结果查询返回,申诉ID: {},recordId: {},返回: {}", appealId, recordId, responseBody);
+
+                JSONObject responseJson = JSONObject.parseObject(responseBody);
+                Integer respCode = responseJson.getInteger("code");
+                JSONObject dataJsonObj = responseJson.getJSONObject("data");
+
+                // 检查AI响应码:202表示分析尚未完成
+                if (respCode != null && respCode == 202) {
+                    // 从details中获取分析状态
+                    JSONObject detailsObj = responseJson.getJSONObject("details");
+                    String analysisStatus = detailsObj != null ? detailsObj.getString("analysis_status") : "unknown";
+                    log.info("AI分析尚未完成(code=202),申诉ID: {},recordId: {},状态: {},将由定时任务后续查询", 
+                        appealId, recordId, analysisStatus);
+                    return false; // 分析未完成,返回false,等待定时任务后续查询
+                }
+
+                // 检查AI响应码:200表示分析完成
+                if (respCode == null || respCode != 200) {
+                    log.warn("AI分析接口返回非200响应码,申诉ID: {},recordId: {},code: {},message: {}", 
+                        appealId, recordId, respCode, responseJson.getString("message"));
+                    return false;
+                }
+
+                if (dataJsonObj != null) {
+                    // 检查分析状态
+                    String analysisStatus = dataJsonObj.getString("analysis_status");
+                    if (!"completed".equals(analysisStatus)) {
+                        log.info("AI分析尚未完成,申诉ID: {},recordId: {},状态: {}", 
+                            appealId, recordId, analysisStatus);
+                        return false; // 分析未完成,返回false,等待定时任务后续查询
+                    }
+
+                    // 获取置信度值
+                    Double userConfidence = dataJsonObj.getDouble("user_confidence");
+                    Double merchantConfidence = dataJsonObj.getDouble("merchant_confidence");
+
+                    if (userConfidence == null || merchantConfidence == null) {
+                        log.error("AI分析结果返回置信度为空,申诉ID: {},recordId: {},user_confidence: {},merchant_confidence: {}", 
+                            appealId, recordId, userConfidence, merchantConfidence);
+                        return false;
+                    }
+
+                    // 计算差值:差值>=10则同意,差值<10则驳回
+                    double confidenceDiff = Math.abs(userConfidence - merchantConfidence);
+                    log.info("AI分析结果,申诉ID: {},recordId: {},user_confidence: {},merchant_confidence: {},差值: {}", 
+                        appealId, recordId, userConfidence, merchantConfidence, confidenceDiff);
+
+                    // 更新申诉状态
+                    StoreCommentAppeal updateAppeal = new StoreCommentAppeal();
+                    updateAppeal.setId(appealId);
+                    updateAppeal.setAppealAiApproval(dataJsonObj.toJSONString());
+
+                    if (confidenceDiff >= 10) {
+                        // 差值>=10则同意
+                        updateAppeal.setAppealStatus(2);
+                        updateAppeal.setFinalResult("已同意");
+                        log.info("AI分析结果:差值>=10,申诉通过,申诉ID: {},差值: {}", appealId, confidenceDiff);
+                        
+                        // 审核通过后删除评价(common_rating)和评论(common_comment)
+                        StoreCommentAppeal appeal = this.getById(appealId);
+                        if (appeal != null) {
+                            Integer ratingId = appeal.getCommentId(); // commentId 实际存储的是评价ID(common_rating.id)
+                            if (ratingId != null) {
+                                // 调用删除评价的逻辑(复用setAppealStatus中的逻辑)
+                                deleteRatingAndComments(ratingId);
+                            }
+                        }
+                    } else {
+                        // 差值<10则驳回
+                        updateAppeal.setAppealStatus(1);
+                        updateAppeal.setFinalResult("已驳回");
+                        log.info("AI分析结果:差值<10,申诉驳回,申诉ID: {},差值: {}", appealId, confidenceDiff);
+                    }
+
+                    // 更新申诉记录
+                    boolean updateResult = this.updateById(updateAppeal);
+                    
+                    // 创建审核日志
+                    StoreCommentAppealLog storeCommentAppealLog = new StoreCommentAppealLog();
+                    storeCommentAppealLog.setAppealId(appealId);
+                    if (confidenceDiff >= 10) {
+                        storeCommentAppealLog.setProcessType(5); // 已同意
+                    } else {
+                        storeCommentAppealLog.setProcessType(4); // 已驳回
+                    }
+                    storeCommentAppealLog.setDeleteFlag(0);
+                    storeCommentAppealLog.setCreatedTime(new Date());
+                    storeCommentAppealLog.setLogRemark(String.format("AI自动审核:用户置信度=%.2f,商家置信度=%.2f,差值=%.2f", 
+                        userConfidence, merchantConfidence, confidenceDiff));
+                    storeCommentAppealLogMapper.insert(storeCommentAppealLog);
+
+                    // 发送通知给商家
+                    sendAppealResultNotification(appealId, confidenceDiff >= 10);
+
+                    return updateResult;
+                } else {
+                    log.error("AI分析结果返回data为空,申诉ID: {},recordId: {},响应: {}", 
+                        appealId, recordId, responseBody);
+                }
+            } else {
+                log.error("查询AI分析结果失败,申诉ID: {},recordId: {},http状态: {}", 
+                    appealId, recordId, response != null ? response.getStatusCode() : null);
+            }
+        } catch (Exception e) {
+            log.error("查询AI分析结果异常,申诉ID: {},recordId: {}", appealId, recordId, e);
+        }
+        
+        return false;
+    }
+
+    /**
+     * 构建查询已完成分析结果的URL
+     *
+     * @param recordId AI返回的记录ID
+     * @return 完整的查询URL
+     */
+    private String buildCompletedUrl(Integer recordId) {
+        String baseUrl = resultUrl;
+        if (!org.springframework.util.StringUtils.hasText(baseUrl)) {
+            throw new IllegalStateException("差评申述分析接口地址未配置");
+        }
+        // 移除末尾的斜杠
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        // 直接拼接 /{record_id}/completed
+        return baseUrl + "/" + recordId + "/completed";
+    }
+
+    /**
+     * 删除评价和评论(审核通过时调用)
+     *
+     * @param ratingId 评价ID
+     */
+    private void deleteRatingAndComments(Integer ratingId) {
+        try {
+            // 1. 删除评价(逻辑删除 common_rating 表)
+            CommonRating rating = commonRatingMapper.selectById(ratingId);
+            if (rating != null) {
+                int rows = commonRatingMapper.logicDeleteById(ratingId);
+                log.info("删除评价结果,ratingId={},影响行数={}", ratingId, rows);
+            }
+            
+            // 2. 查询该评价下的所有评论ID(用于后续删除点赞记录)
+            LambdaQueryWrapper<CommonComment> commentQueryWrapper = new LambdaQueryWrapper<>();
+            commentQueryWrapper.eq(CommonComment::getSourceType, CommentSourceTypeEnum.STORE_COMMENT.getType())
+                              .eq(CommonComment::getSourceId, ratingId)
+                              .eq(CommonComment::getDeleteFlag, 0);
+            List<CommonComment> comments = commonCommentMapper.selectList(commentQueryWrapper);
+            List<Long> commentIds = comments.stream().map(CommonComment::getId).collect(Collectors.toList());
+            
+            // 3. 删除该评价下的所有评论(使用原生 SQL 绕过 @TableLogic 限制)
+            int commentRows = commonCommentMapper.logicDeleteBySourceId(
+                    CommentSourceTypeEnum.STORE_COMMENT.getType(), ratingId);
+            log.info("删除评价下的评论结果,ratingId={},影响行数={}", ratingId, commentRows);
+            
+            // 4. 删除评价的点赞记录(type=7 表示评价点赞)
+            LambdaUpdateWrapper<LifeLikeRecord> ratingLikeWrapper = new LambdaUpdateWrapper<>();
+            ratingLikeWrapper.eq(LifeLikeRecord::getType, "7")
+                            .eq(LifeLikeRecord::getHuifuId, String.valueOf(ratingId))
+                            .eq(LifeLikeRecord::getDeleteFlag, 0);
+            ratingLikeWrapper.set(LifeLikeRecord::getDeleteFlag, 1);
+            ratingLikeWrapper.set(LifeLikeRecord::getUpdatedTime, new Date());
+            lifeLikeRecordMapper.update(null, ratingLikeWrapper);
+            log.info("删除评价的点赞记录,ratingId={}", ratingId);
+            
+            // 5. 删除评论的点赞记录(type=1 表示评论点赞)
+            if (!commentIds.isEmpty()) {
+                for (Long commentId : commentIds) {
+                    LambdaUpdateWrapper<LifeLikeRecord> commentLikeWrapper = new LambdaUpdateWrapper<>();
+                    commentLikeWrapper.eq(LifeLikeRecord::getType, "1")
+                                     .eq(LifeLikeRecord::getHuifuId, String.valueOf(commentId))
+                                     .eq(LifeLikeRecord::getDeleteFlag, 0);
+                    commentLikeWrapper.set(LifeLikeRecord::getDeleteFlag, 1);
+                    commentLikeWrapper.set(LifeLikeRecord::getUpdatedTime, new Date());
+                    lifeLikeRecordMapper.update(null, commentLikeWrapper);
+                }
+                log.info("删除评论的点赞记录,评论数={}", commentIds.size());
+            }
+            
+            // 6. 重新统计店铺评分(删除评价后需要更新门店评分)
+            if (rating != null && rating.getBusinessType() == 1) {
+                updateStoreScore(rating.getBusinessId());
+            }
+        } catch (Exception e) {
+            log.error("删除评价和评论异常,ratingId={}", ratingId, e);
+        }
+    }
+
+    /**
+     * 发送申诉结果通知给商家
+     *
+     * @param appealId 申诉ID
+     * @param approved 是否通过
+     */
+    private void sendAppealResultNotification(Integer appealId, boolean approved) {
+        try {
+            StoreCommentAppeal appeal = this.getById(appealId);
+            if (appeal == null) {
+                return;
+            }
+
+            StoreInfo storeInfo = storeInfoMapper.selectById(appeal.getStoreId());
+            if (storeInfo == null || !org.springframework.util.StringUtils.hasText(storeInfo.getStoreTel())) {
+                return;
+            }
+
+            StoreUser storeUser = storeUserMapper.selectOne(
+                new LambdaQueryWrapper<StoreUser>()
+                    .eq(StoreUser::getStoreId, storeInfo.getId())
+                    .eq(StoreUser::getDeleteFlag, 0));
+
+            if (storeUser == null) {
+                return;
+            }
+
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            String commonDate = simpleDateFormat.format(new Date());
+            String text = "";
+            if (approved) {
+                text = "您在" + commonDate + "提交了申诉删除差评,经AI审核,确为不实评价,申诉成功,已将此条评价删除。";
+            } else {
+                text = "您在" + commonDate + "提交了申诉删除差评,经AI审核,确为顾客真实感受,申诉失败,此条评价不会被删除。";
+            }
+
+            LifeNotice lifeMessage = new LifeNotice();
+            lifeMessage.setReceiverId("store_" + storeUser.getPhone());
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("message", text);
+            lifeMessage.setContext(jsonObject.toJSONString());
+            lifeMessage.setTitle("申诉删除差评");
+            lifeMessage.setSenderId("system");
+            lifeMessage.setIsRead(0);
+            lifeMessage.setNoticeType(1);
+            lifeNoticeMapper.insert(lifeMessage);
+
+            WebSocketVo websocketVo = new WebSocketVo();
+            websocketVo.setSenderId("system");
+            websocketVo.setReceiverId("store_" + storeUser.getPhone());
+            websocketVo.setCategory("notice");
+            websocketVo.setNoticeType("1");
+            websocketVo.setIsRead(0);
+            websocketVo.setText(JSONObject.from(lifeMessage).toJSONString());
+            try {
+                webSocketProcess.sendMessage("store_" + storeUser.getPhone(), JSONObject.from(websocketVo).toJSONString());
+            } catch (Exception e) {
+                log.error("发送WebSocket通知异常,申诉ID: {}", appealId, e);
+            }
+        } catch (Exception e) {
+            log.error("发送申诉结果通知异常,申诉ID: {}", appealId, e);
+        }
+    }
+
+    /**
+     * 将图片URL转换为Base64编码
+     *
+     * @param imageUrl 图片URL
+     * @return Base64编码字符串
+     */
+    private String convertImageToBase64(String imageUrl) {
+        if (!org.springframework.util.StringUtils.hasText(imageUrl)) {
+            log.warn("图片URL为空");
+            return "";
+        }
+
+        try {
+            // 下载图片并转换为Base64
+            byte[] imageBytes = restTemplate.getForObject(imageUrl, byte[].class);
+            if (imageBytes != null) {
+                return java.util.Base64.getEncoder().encodeToString(imageBytes);
+            }
+        } catch (org.springframework.web.client.HttpClientErrorException.NotFound e) {
+            log.warn("图片不存在 (404), URL: {}", imageUrl);
+            return "";
+        } catch (Exception e) {
+            log.error("图片转换为Base64失败, URL: {}", imageUrl, e);
+            return "";
+        }
+        return "";
+    }
 }