|
@@ -10,7 +10,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.springframework.beans.factory.annotation.Qualifier;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.util.CollectionUtils;
|
|
import org.springframework.util.CollectionUtils;
|
|
@@ -22,14 +21,11 @@ import shop.alien.mapper.*;
|
|
|
import shop.alien.store.service.StoreStaffCommentService;
|
|
import shop.alien.store.service.StoreStaffCommentService;
|
|
|
import shop.alien.store.service.StoreStaffReviewService;
|
|
import shop.alien.store.service.StoreStaffReviewService;
|
|
|
import shop.alien.store.util.ai.AiContentModerationUtil;
|
|
import shop.alien.store.util.ai.AiContentModerationUtil;
|
|
|
-import shop.alien.store.util.ai.AiVideoModerationUtil;
|
|
|
|
|
import shop.alien.util.common.DistanceUtil;
|
|
import shop.alien.util.common.DistanceUtil;
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
import java.math.RoundingMode;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
-import java.util.concurrent.CompletableFuture;
|
|
|
|
|
-import java.util.concurrent.ExecutorService;
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 员工评价 服务实现类
|
|
* 员工评价 服务实现类
|
|
@@ -53,9 +49,6 @@ public class StoreStaffReviewServiceImpl extends ServiceImpl<StoreStaffReviewMap
|
|
|
private final StoreInfoMapper storeInfoMapper;
|
|
private final StoreInfoMapper storeInfoMapper;
|
|
|
private final StoreUserMapper storeUserMapper;
|
|
private final StoreUserMapper storeUserMapper;
|
|
|
private final AiContentModerationUtil aiContentModerationUtil;
|
|
private final AiContentModerationUtil aiContentModerationUtil;
|
|
|
- @Qualifier("commonVideoTaskExecutor")
|
|
|
|
|
- private final ExecutorService commonVideoTaskExecutor;
|
|
|
|
|
- private final AiVideoModerationUtil aiVideoModerationUtil;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public R<StoreStaffReview> createReview(StoreStaffReviewDto reviewDto) {
|
|
public R<StoreStaffReview> createReview(StoreStaffReviewDto reviewDto) {
|
|
@@ -67,80 +60,36 @@ public class StoreStaffReviewServiceImpl extends ServiceImpl<StoreStaffReviewMap
|
|
|
return R.fail(validateResult.getMsg());
|
|
return R.fail(validateResult.getMsg());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 创建评价
|
|
|
|
|
|
|
+ // 先审文本再入库:AI 不通过不落库,不计入评价/后续评论数等
|
|
|
|
|
+ if (StringUtils.isNotEmpty(reviewDto.getReviewContent())) {
|
|
|
|
|
+ AiContentModerationUtil.AuditResult auditResult =
|
|
|
|
|
+ aiContentModerationUtil.auditContent(reviewDto.getReviewContent(), null);
|
|
|
|
|
+ if (!auditResult.isPassed()) {
|
|
|
|
|
+ String displayReason = buildAiTextAuditFailMessage(auditResult.getFailureReason());
|
|
|
|
|
+ log.warn("员工评价AI文本审核未通过, 未写入评价表, rawReason={}, displayReason={}",
|
|
|
|
|
+ auditResult.getFailureReason(), displayReason);
|
|
|
|
|
+ return R.fail(displayReason);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
StoreStaffReview review = buildReviewFromDto(reviewDto);
|
|
StoreStaffReview review = buildReviewFromDto(reviewDto);
|
|
|
boolean success = this.save(review);
|
|
boolean success = this.save(review);
|
|
|
- // AI:评价图/视频前端直连审;服务端仅审 reviewContent 文本(图列表在 util 内忽略)
|
|
|
|
|
- Map<String, List<String>> urlCategoryMap = StoreRenovationRequirementServiceImpl.classifyUrls(reviewDto.getReviewImages());
|
|
|
|
|
-
|
|
|
|
|
- AiContentModerationUtil.AuditResult auditResult = new AiContentModerationUtil.AuditResult(true, "");
|
|
|
|
|
- if( StringUtils.isNotEmpty(reviewDto.getReviewContent()) || urlCategoryMap.get("image").size() > 0){
|
|
|
|
|
- auditResult = aiContentModerationUtil.auditContent(reviewDto.getReviewContent(), null);
|
|
|
|
|
- }
|
|
|
|
|
- if (!auditResult.isPassed()) {
|
|
|
|
|
- // 审核不通过
|
|
|
|
|
- StoreStaffReview staffReview = this.getById(review.getId());
|
|
|
|
|
- staffReview.setAuditStatus(2);
|
|
|
|
|
- staffReview.setAuditReason(auditResult.getFailureReason());
|
|
|
|
|
- this.saveOrUpdate(staffReview);
|
|
|
|
|
- } else{
|
|
|
|
|
- // 视频审核由前端完成;此处 auditVideos 恒通过,仅保留异步更新员工分等原流程
|
|
|
|
|
- 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.error("创建评价失败,入库失败");
|
|
|
|
|
+ return R.fail("创建评价失败");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- if (success) {
|
|
|
|
|
- log.info("创建评价成功, 评价ID={}", review.getId());
|
|
|
|
|
- return R.data(review, "提交成功");
|
|
|
|
|
- } else {
|
|
|
|
|
- log.error("创建评价失败");
|
|
|
|
|
- return R.fail("创建评价失败");
|
|
|
|
|
|
|
+ StoreStaffReview approved = this.getById(review.getId());
|
|
|
|
|
+ if (approved != null) {
|
|
|
|
|
+ approved.setAuditStatus(1);
|
|
|
|
|
+ approved.setAuditReason(null);
|
|
|
|
|
+ this.updateById(approved);
|
|
|
|
|
+ updateStaffServiceScore(review.getStaffUserId());
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ log.info("创建评价成功, 评价ID={}", review.getId());
|
|
|
|
|
+ StoreStaffReview latest = this.getById(review.getId());
|
|
|
|
|
+ return R.data(latest != null ? latest : review, "提交成功");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -274,11 +223,14 @@ public class StoreStaffReviewServiceImpl extends ServiceImpl<StoreStaffReviewMap
|
|
|
return R.fail(validateResult.getMsg());
|
|
return R.fail(validateResult.getMsg());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 验证评价是否存在
|
|
|
|
|
|
|
+ // 验证评价是否存在且已展示通过(与列表、详情 SQL 一致,防止未通过审核的评价仍被点赞刷数)
|
|
|
StoreStaffReview review = this.getById(reviewId);
|
|
StoreStaffReview review = this.getById(reviewId);
|
|
|
if (review == null || review.getDeleteFlag() == 1) {
|
|
if (review == null || review.getDeleteFlag() == 1) {
|
|
|
return R.fail("评价不存在或已删除");
|
|
return R.fail("评价不存在或已删除");
|
|
|
}
|
|
}
|
|
|
|
|
+ if (review.getAuditStatus() == null || review.getAuditStatus() != 1) {
|
|
|
|
|
+ return R.fail("评价不存在");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 检查是否已点赞
|
|
// 检查是否已点赞
|
|
|
if (isLiked(reviewId, userId, LIKE_TYPE_REVIEW)) {
|
|
if (isLiked(reviewId, userId, LIKE_TYPE_REVIEW)) {
|
|
@@ -362,6 +314,20 @@ public class StoreStaffReviewServiceImpl extends ServiceImpl<StoreStaffReviewMap
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
|
+ * 人员评价走服务端 moderate 文本审核,失败时对用户与库内 audit_reason 统一为「AI审核失败」为主文案。
|
|
|
|
|
+ */
|
|
|
|
|
+ private static String buildAiTextAuditFailMessage(String rawReason) {
|
|
|
|
|
+ if (!org.springframework.util.StringUtils.hasText(rawReason)) {
|
|
|
|
|
+ return "AI审核失败";
|
|
|
|
|
+ }
|
|
|
|
|
+ String t = rawReason.trim();
|
|
|
|
|
+ if ("审核异常".equals(t)) {
|
|
|
|
|
+ return "AI审核失败(服务调用异常)";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "AI审核失败:" + t;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
* 构建评价对象
|
|
* 构建评价对象
|
|
|
*/
|
|
*/
|
|
|
private StoreStaffReview buildReviewFromDto(StoreStaffReviewDto reviewDto) {
|
|
private StoreStaffReview buildReviewFromDto(StoreStaffReviewDto reviewDto) {
|