Parcourir la source

fix:店铺评价,评论,人员评价,评论 ai审核。

刘云鑫 il y a 2 mois
Parent
commit
577e3c2f9d

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/CommonComment.java

@@ -98,5 +98,9 @@ public class CommonComment implements Serializable {
     @ApiModelProperty(value = "扩展字段:如评论楼层")
     @TableField(value = "ext_info", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
     private String extInfo;
+
+    @ApiModelProperty(value = "审核失败原因")
+    @TableField(value = "audit_reason")
+    private String auditReason;
 }
 

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/CommonRating.java

@@ -106,5 +106,9 @@ public class CommonRating implements Serializable {
     @ApiModelProperty(value = "修改人ID")
     @TableField("updated_user_id")
     private Integer updatedUserId;
+
+    @ApiModelProperty(value = "审核失败原因")
+    @TableField(value = "audit_reason")
+    private String auditReason;
 }
 

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreStaffReview.java

@@ -96,5 +96,13 @@ public class StoreStaffReview {
     @ApiModelProperty(value = "申诉id (如果该评价被申诉 申诉id会有值)")
     @TableField("appeal_id")
     private String appealId;
+
+    @ApiModelProperty(value = "审核状态:0-待审核 1-通过 2-驳回")
+    @TableField("audit_status")
+    private Integer auditStatus;
+
+    @ApiModelProperty(value = "审核失败原因")
+    @TableField(value = "audit_reason")
+    private String auditReason;
 }
 

+ 33 - 1
alien-store/src/main/java/shop/alien/store/config/AsyncConfig.java

@@ -5,7 +5,7 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
-import java.util.concurrent.Executor;
+import java.util.concurrent.*;
 
 /**
  * 异步任务配置类
@@ -44,5 +44,37 @@ public class AsyncConfig {
         executor.initialize();
         return executor;
     }
+
+    @Bean(name = "commonVideoTaskExecutor")
+    public ExecutorService initExecutor() {
+        // 核心参数根据业务调整,IO密集型任务线程数可设为CPU核心数*2~4
+        int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
+        int maxPoolSize = Runtime.getRuntime().availableProcessors() * 4;
+        ExecutorService  videoAuditExecutor = new ThreadPoolExecutor(
+                corePoolSize,
+                maxPoolSize,
+                60L,
+                TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(500), // 任务队列,避免无界队列溢出
+                new ThreadFactory() {
+                    private int count = 0;
+
+                    @Override
+                    public Thread newThread(Runnable r) {
+                        Thread t = new Thread(r);
+                        t.setName("video-audit-" + count++); // 自定义线程名,便于排查
+                        return t;
+                    }
+                },
+                // 任务拒绝策略:记录日志+调用者线程兜底(避免任务丢失)
+                new ThreadPoolExecutor.CallerRunsPolicy() {
+                    @Override
+                    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
+                        super.rejectedExecution(r, e);
+                    }
+                }
+        );
+        return videoAuditExecutor;
+    }
 }
 

+ 5 - 6
alien-store/src/main/java/shop/alien/store/service/impl/CommonCommentServiceImpl.java

@@ -17,8 +17,8 @@ import shop.alien.entity.store.vo.CommonRatingVo;
 import shop.alien.mapper.*;
 import shop.alien.store.service.CommonCommentService;
 import shop.alien.store.util.CommonConstant;
+import shop.alien.store.util.ai.AiContentModerationUtil;
 import shop.alien.util.common.constant.CommentSourceTypeEnum;
-import shop.alien.util.common.safe.TextModerationResultVO;
 import shop.alien.util.common.safe.TextModerationUtil;
 
 import java.util.*;
@@ -48,7 +48,7 @@ public class CommonCommentServiceImpl extends ServiceImpl<CommonCommentMapper, C
     private final LifeUserMapper lifeUserMapper;
     @Autowired
     private LifeFansMapper lifeFansMapper;
-
+    private final AiContentModerationUtil aiContentModerationUtil;
 
     /**
      * 新增评论
@@ -64,11 +64,10 @@ public class CommonCommentServiceImpl extends ServiceImpl<CommonCommentMapper, C
             return 4; // 字数超限
         }
 
-        // 文本内容审核
-        TextModerationResultVO textCheckResult = null;
+        // 文本内容审核 + 视频审核(评论没有)
         try {
-            textCheckResult = textModerationUtil.invokeFunction(commonComment.getContent(), CommonRatingServiceImpl.SERVICES_LIST);
-            if ("high".equals(textCheckResult.getRiskLevel())) {
+            AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(commonComment.getContent(), null);
+            if (!auditResult.isPassed()) {
                 return 2; // 文本内容异常(包含敏感词)
             }
             // 2.审核通过,设置评价状态为已审核

+ 67 - 14
alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java

@@ -15,6 +15,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.result.R;
@@ -24,22 +25,24 @@ import shop.alien.entity.store.vo.CommonRatingVo;
 import shop.alien.entity.store.vo.StoreInfoScoreVo;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
-import shop.alien.entity.store.TagsSynonym;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.CommonCommentService;
 import shop.alien.store.service.CommonRatingService;
 import shop.alien.store.util.CommonConstant;
+import shop.alien.store.util.ai.AiContentModerationUtil;
+import shop.alien.store.util.ai.AiVideoModerationUtil;
+import shop.alien.util.common.DateUtils;
 import shop.alien.util.common.constant.CommentSourceTypeEnum;
 import shop.alien.util.common.constant.RatingBusinessTypeEnum;
-import shop.alien.util.common.safe.TextModerationResultVO;
 import shop.alien.util.common.safe.TextModerationUtil;
 import shop.alien.util.common.safe.TextReviewServiceEnum;
-import shop.alien.util.common.DateUtils;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -72,8 +75,10 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
     private final TagsSynonymMapper tagsSynonymMapper;
     private final CommonCommentService commonCommentService;
     private final StoreCommentAppealMapper storeCommentAppealMapper;
-
-
+    private final AiContentModerationUtil aiContentModerationUtil;
+    @Qualifier("commonVideoTaskExecutor")
+    private final ExecutorService commonVideoTaskExecutor;
+    private final AiVideoModerationUtil aiVideoModerationUtil;
 
     public static final List<String> SERVICES_LIST = ImmutableList.of(
             TextReviewServiceEnum.COMMENT_DETECTION_PRO.getService(),
@@ -84,14 +89,8 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
 
     @Override
     public Integer saveCommonRating(CommonRating commonRating) {
-        // 1. 文本审核
+        // 1. 文本审核 + 视频审核(评价有图片和视频)
         try {
-            TextModerationResultVO textCheckResult = textModerationUtil.invokeFunction(commonRating.getContent(), SERVICES_LIST);
-            if ("high".equals(textCheckResult.getRiskLevel())) {
-                return 2;
-            }
-            // 2.审核通过,设置评价状态为已审核
-            commonRating.setAuditStatus(1);
             // 手动存评分1,2,3
             if (StringUtils.isNotEmpty(commonRating.getOtherScore())) {
                 JSONObject parse = JSONObject.parse(commonRating.getOtherScore());
@@ -100,8 +99,62 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                 commonRating.setScoreThree(parse.getDouble("scoreThree"));
             }
             int i = this.save(commonRating) ? 0 : 1;
-            // 对不同的businessType进行不同的处理,
-            doBusinessWithType(commonRating);
+            // 一次遍历完成分类,避免多次流式处理
+            Map<String, List<String>> urlCategoryMap = StoreRenovationRequirementServiceImpl.classifyUrls(Arrays.asList(commonRating.getImageUrls().split(",")));
+            AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(commonRating.getContent(), urlCategoryMap.get("image"));
+            if (!auditResult.isPassed()) {
+                // 审核不通过
+                CommonRating rating = this.getById(commonRating.getId());
+                rating.setAuditStatus(2);
+                rating.setAuditReason(auditResult.getFailureReason());
+                this.saveOrUpdate(rating);
+            } else{
+                CompletableFuture.runAsync(() -> {
+                    AiVideoModerationUtil.VideoAuditResult videoAuditResult = null;
+                    try {
+                        // 调用审核接口,增加超时控制(避免接口挂死)
+                        videoAuditResult = CompletableFuture.supplyAsync(
+                                () -> aiVideoModerationUtil.auditVideos(urlCategoryMap.get("video")),
+                                commonVideoTaskExecutor
+                        ).get();
+
+                        // 审核不通过则更新状态和原因
+                        if (Objects.nonNull(videoAuditResult) && !videoAuditResult.isPassed()) {
+                            // 重新查询最新的rating,避免并发覆盖
+                            CommonRating rating = this.getById(commonRating.getId());
+                            if (Objects.isNull(rating)) {
+                                log.error("视频审核后更新失败,rating,ID:{}", rating.getId());
+                                return;
+                            }
+                            rating.setAuditStatus(2);
+                            // 失败原因
+                            rating.setAuditReason(videoAuditResult.getFailureReason());
+                            this.saveOrUpdate(rating);
+                            log.info("视频审核不通过,已更新状态,requirementID:{},原因:{}",
+                                    rating.getId(), videoAuditResult.getFailureReason());
+                        } else if (Objects.nonNull(videoAuditResult) && videoAuditResult.isPassed()) {
+                            // 审核通过也更新状态(可选,根据业务需求)
+                            CommonRating rating = this.getById(commonRating.getId());
+                            if (Objects.nonNull(rating)) {
+                                rating.setAuditStatus(1);
+//                                latestRequirement.setAuditReason(null);
+                                this.saveOrUpdate(rating);
+                                // 对不同的businessType进行不同的处理,
+                                doBusinessWithType(commonRating);
+                                log.info("视频审核通过,已更新状态,requirementID:{}", rating.getId());
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.error("视频审核接口调用异常,commonRating:{}", commonRating.getId(), e);
+                        CommonRating rating = this.getById(commonRating.getId());
+                        if (Objects.nonNull(rating)) {
+                            rating.setAuditStatus(2);
+                            rating.setAuditReason("视频审核接口调用异常:" + e.getMessage()); // 实际需捕获具体异常信息
+                            this.saveOrUpdate(rating);
+                        }
+                    }
+                }, commonVideoTaskExecutor);
+            }
             return i;
         } catch (Exception e) {
             log.error("CommonRatingService.saveCommonRating ERROR Msg={}", e.getMessage());

+ 2 - 2
alien-store/src/main/java/shop/alien/store/service/impl/StoreRenovationRequirementServiceImpl.java

@@ -285,7 +285,7 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
      * @param attachmentUrls 原始URL列表
      * @return 分类后的Map
      */
-    private static Map<String, List<String>> classifyUrls(List<String> attachmentUrls) {
+    public static Map<String, List<String>> classifyUrls(List<String> attachmentUrls) {
         // 初始化分类容器
         List<String> imageUrls = new ArrayList<>();
         List<String> videoUrls = new ArrayList<>();
@@ -325,7 +325,7 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
     /**
      * 构建分类结果Map
      */
-    private static Map<String, List<String>> buildResultMap(List<String> imageUrls, List<String> videoUrls, List<String> otherUrls) {
+    public static Map<String, List<String>> buildResultMap(List<String> imageUrls, List<String> videoUrls, List<String> otherUrls) {
         Map<String, List<String>> resultMap = new HashMap<>(3);
         resultMap.put("image", Collections.unmodifiableList(imageUrls)); // 返回不可修改列表,避免外部篡改
         resultMap.put("video", Collections.unmodifiableList(videoUrls));

+ 8 - 2
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffCommentServiceImpl.java

@@ -20,6 +20,7 @@ import shop.alien.mapper.LifeLikeRecordMapper;
 import shop.alien.mapper.StoreStaffCommentMapper;
 import shop.alien.store.service.StoreStaffCommentService;
 import shop.alien.store.service.StoreStaffReviewService;
+import shop.alien.store.util.ai.AiContentModerationUtil;
 
 import java.util.Date;
 import java.util.List;
@@ -43,13 +44,15 @@ public class StoreStaffCommentServiceImpl extends ServiceImpl<StoreStaffCommentM
     private final StoreStaffCommentMapper storeStaffCommentMapper;
     private final StoreStaffReviewService storeStaffReviewService;
     private final LifeLikeRecordMapper lifeLikeRecordMapper;
+    private final AiContentModerationUtil aiContentModerationUtil;
 
     public StoreStaffCommentServiceImpl(StoreStaffCommentMapper storeStaffCommentMapper,
                                         @Lazy StoreStaffReviewService storeStaffReviewService,
-                                        LifeLikeRecordMapper lifeLikeRecordMapper) {
+                                        LifeLikeRecordMapper lifeLikeRecordMapper, AiContentModerationUtil aiContentModerationUtil) {
         this.storeStaffCommentMapper = storeStaffCommentMapper;
         this.storeStaffReviewService = storeStaffReviewService;
         this.lifeLikeRecordMapper = lifeLikeRecordMapper;
+        this.aiContentModerationUtil = aiContentModerationUtil;
     }
 
     @Override
@@ -70,7 +73,10 @@ public class StoreStaffCommentServiceImpl extends ServiceImpl<StoreStaffCommentM
 
         // 设置评论属性
         setCommentDefaults(comment, review);
-
+        AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(comment.getCommentContent(), null);
+        if (!auditResult.isPassed()) {
+            return R.fail(auditResult.getFailureReason());
+        }
         boolean success = this.save(comment);
         if (success) {
             // 更新评价的评论数

+ 73 - 16
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffReviewServiceImpl.java

@@ -9,33 +9,28 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import shop.alien.entity.result.R;
-import shop.alien.entity.store.LifeLikeRecord;
-import shop.alien.entity.store.StoreStaffComment;
-import shop.alien.entity.store.StoreStaffReview;
+import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.StoreStaffReviewDto;
-import shop.alien.entity.store.vo.StoreStaffCommentVo;
-import shop.alien.entity.store.vo.StoreStaffReviewDetailVo;
-import shop.alien.entity.store.vo.StoreStaffReviewListWithStaffVo;
-import shop.alien.entity.store.vo.StoreStaffReviewStatisticsVo;
-import shop.alien.entity.store.vo.StoreStaffReviewVo;
-import shop.alien.entity.store.StoreStaffConfig;
+import shop.alien.entity.store.vo.*;
 import shop.alien.mapper.LifeLikeRecordMapper;
 import shop.alien.mapper.StoreStaffCommentMapper;
 import shop.alien.mapper.StoreStaffConfigMapper;
 import shop.alien.mapper.StoreStaffReviewMapper;
 import shop.alien.store.service.StoreStaffCommentService;
 import shop.alien.store.service.StoreStaffReviewService;
+import shop.alien.store.util.ai.AiContentModerationUtil;
+import shop.alien.store.util.ai.AiVideoModerationUtil;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
 
 /**
  * 员工评价 服务实现类
@@ -56,6 +51,10 @@ public class StoreStaffReviewServiceImpl extends ServiceImpl<StoreStaffReviewMap
     private final StoreStaffCommentMapper storeStaffCommentMapper;
     private final LifeLikeRecordMapper lifeLikeRecordMapper;
     private final StoreStaffConfigMapper storeStaffConfigMapper;
+    private final AiContentModerationUtil aiContentModerationUtil;
+    @Qualifier("commonVideoTaskExecutor")
+    private final ExecutorService commonVideoTaskExecutor;
+    private final AiVideoModerationUtil aiVideoModerationUtil;
 
     @Override
     public R<StoreStaffReview> createReview(StoreStaffReviewDto reviewDto) {
@@ -70,11 +69,69 @@ public class StoreStaffReviewServiceImpl extends ServiceImpl<StoreStaffReviewMap
         // 创建评价
         StoreStaffReview review = buildReviewFromDto(reviewDto);
         boolean success = this.save(review);
-        
+        // AI审核
+        // 一次遍历完成分类,避免多次流式处理
+        Map<String, List<String>> urlCategoryMap = StoreRenovationRequirementServiceImpl.classifyUrls(reviewDto.getReviewImages());
+        AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(reviewDto.getReviewContent(), urlCategoryMap.get("image"));
+        if (!auditResult.isPassed()) {
+            // 审核不通过
+            StoreStaffReview staffReview = this.getById(review.getId());
+            staffReview.setAuditStatus(2);
+            staffReview.setAuditReason(auditResult.getFailureReason());
+            this.saveOrUpdate(staffReview);
+        } else{
+            CompletableFuture.runAsync(() -> {
+                AiVideoModerationUtil.VideoAuditResult videoAuditResult = null;
+                try {
+                    // 调用审核接口,增加超时控制(避免接口挂死)
+                    videoAuditResult = CompletableFuture.supplyAsync(
+                            () -> aiVideoModerationUtil.auditVideos(urlCategoryMap.get("video")),
+                            commonVideoTaskExecutor
+                    ).get();
+
+                    // 审核不通过则更新状态和原因
+                    if (Objects.nonNull(videoAuditResult) && !videoAuditResult.isPassed()) {
+                        // 重新查询最新的rating,避免并发覆盖
+                        StoreStaffReview staffReview  = this.getById(review.getId());
+                        if (Objects.isNull(review)) {
+                            log.error("视频审核后更新失败,rating,ID:{}", review.getId());
+                            return;
+                        }
+                        staffReview.setAuditStatus(2);
+                        // 失败原因
+                        staffReview.setAuditReason(videoAuditResult.getFailureReason());
+                        this.saveOrUpdate(staffReview);
+                        log.info("视频审核不通过,已更新状态,reviewID:{},原因:{}",
+                                staffReview.getId(), videoAuditResult.getFailureReason());
+                    } else if (Objects.nonNull(videoAuditResult) && videoAuditResult.isPassed()) {
+                        // 审核通过也更新状态(可选,根据业务需求)
+                        StoreStaffReview staffReview = this.getById(review.getId());
+                        if (Objects.nonNull(staffReview)) {
+                            staffReview.setAuditStatus(1);
+                        // latestRequirement.setAuditReason(null);
+                            this.saveOrUpdate(staffReview);
+                            // 更新员工评分
+                            updateStaffServiceScore(review.getStaffUserId());
+                            log.info("视频审核通过,已更新状态,reviewID:{}", staffReview.getId());
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("视频审核接口调用异常,reviewID:{}", review.getId(), e);
+                    StoreStaffReview staffReview = this.getById(review.getId());
+                    if (Objects.nonNull(staffReview)) {
+                        staffReview.setAuditStatus(2);
+                        staffReview.setAuditReason("视频审核接口调用异常:" + e.getMessage()); // 实际需捕获具体异常信息
+                        this.saveOrUpdate(staffReview);
+                    }
+                }
+            }, commonVideoTaskExecutor);
+        }
+
+
+
+
         if (success) {
             log.info("创建评价成功, 评价ID={}", review.getId());
-            // 更新员工评分
-            updateStaffServiceScore(review.getStaffUserId());
             return R.data(review, "提交成功");
         } else {
             log.error("创建评价失败");