|
|
@@ -21,7 +21,10 @@ import shop.alien.store.util.ai.AiContentModerationUtil;
|
|
|
import shop.alien.store.util.ai.AiVideoModerationUtil;
|
|
|
import shop.alien.util.common.JwtUtil;
|
|
|
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import javax.annotation.PreDestroy;
|
|
|
import java.util.*;
|
|
|
+import java.util.concurrent.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
@@ -36,6 +39,9 @@ import java.util.stream.Collectors;
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreRenovationRequirementMapper, StoreRenovationRequirement> implements StoreRenovationRequirementService {
|
|
|
|
|
|
+ // 1. 自定义视频审核线程池(全局复用,避免频繁创建线程)
|
|
|
+ private ExecutorService videoAuditExecutor;
|
|
|
+
|
|
|
private final StoreInfoMapper storeInfoMapper;
|
|
|
|
|
|
private final AiContentModerationUtil aiContentModerationUtil;
|
|
|
@@ -50,6 +56,55 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
|
|
|
new HashSet<>(Arrays.asList(".mp4", ".avi", ".mov", ".flv", ".mkv", ".wmv", ".mpeg"))
|
|
|
);
|
|
|
|
|
|
+ // 初始化线程池
|
|
|
+ @PostConstruct
|
|
|
+ public void initExecutor() {
|
|
|
+ // 核心参数根据业务调整,IO密集型任务线程数可设为CPU核心数*2~4
|
|
|
+ int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
|
|
|
+ int maxPoolSize = Runtime.getRuntime().availableProcessors() * 4;
|
|
|
+ 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) {
|
|
|
+ log.error("视频审核任务被拒绝,队列已满!当前队列大小:{},活跃线程数:{}",
|
|
|
+ e.getQueue().size(), e.getActiveCount());
|
|
|
+ super.rejectedExecution(r, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 优雅关闭线程池
|
|
|
+ @PreDestroy
|
|
|
+ public void destroyExecutor() {
|
|
|
+ if (Objects.nonNull(videoAuditExecutor)) {
|
|
|
+ videoAuditExecutor.shutdown();
|
|
|
+ try {
|
|
|
+ if (!videoAuditExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
|
|
|
+ videoAuditExecutor.shutdownNow();
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ videoAuditExecutor.shutdownNow();
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public boolean saveOrUpdateRequirement(StoreRenovationRequirementDto dto) {
|
|
|
log.info("StoreRenovationRequirementServiceImpl.saveOrUpdateRequirement?dto={}", dto);
|
|
|
@@ -104,15 +159,54 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
|
|
|
requirement.setAuditStatus(2);
|
|
|
requirement.setAuditReason(auditResult.getFailureReason());
|
|
|
} else {
|
|
|
- // 2.调用视频审核接口
|
|
|
- AiVideoModerationUtil.VideoAuditResult videoAuditResult = aiVideoModerationUtil.auditVideos(urlCategoryMap.get("video"));
|
|
|
- if (!videoAuditResult.isPassed()) {
|
|
|
- requirement.setAuditStatus(2);
|
|
|
- requirement.setAuditReason(videoAuditResult.getFailureReason());
|
|
|
- }
|
|
|
- requirement.setAuditStatus(1);
|
|
|
+ // 异步调用视频审核接口,图片审核通过后调用(核心优化)
|
|
|
+ CompletableFuture.runAsync(() -> {
|
|
|
+ AiVideoModerationUtil.VideoAuditResult videoAuditResult = null;
|
|
|
+ try {
|
|
|
+ // 调用审核接口,增加超时控制(避免接口挂死)
|
|
|
+ videoAuditResult = CompletableFuture.supplyAsync(
|
|
|
+ () -> aiVideoModerationUtil.auditVideos(urlCategoryMap.get("video")),
|
|
|
+ videoAuditExecutor
|
|
|
+ ).get();
|
|
|
+
|
|
|
+ // 审核不通过则更新状态和原因
|
|
|
+ if (Objects.nonNull(videoAuditResult) && !videoAuditResult.isPassed()) {
|
|
|
+ // 重新查询最新的requirement,避免并发覆盖
|
|
|
+ StoreRenovationRequirement latestRequirement = this.getById(requirement.getId());
|
|
|
+ if (Objects.isNull(latestRequirement)) {
|
|
|
+ log.error("视频审核后更新失败,requirement不存在,ID:{}", requirement.getId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ latestRequirement.setAuditStatus(2);
|
|
|
+ latestRequirement.setAuditReason(videoAuditResult.getFailureReason());
|
|
|
+ this.saveOrUpdate(latestRequirement);
|
|
|
+ log.info("视频审核不通过,已更新状态,requirementID:{},原因:{}",
|
|
|
+ requirement.getId(), videoAuditResult.getFailureReason());
|
|
|
+ } else if (Objects.nonNull(videoAuditResult) && videoAuditResult.isPassed()) {
|
|
|
+ // 审核通过也更新状态(可选,根据业务需求)
|
|
|
+ StoreRenovationRequirement latestRequirement = this.getById(requirement.getId());
|
|
|
+ if (Objects.nonNull(latestRequirement)) {
|
|
|
+ latestRequirement.setAuditStatus(1);
|
|
|
+ latestRequirement.setAuditReason(null);
|
|
|
+ this.saveOrUpdate(latestRequirement);
|
|
|
+ log.info("视频审核通过,已更新状态,requirementID:{}", requirement.getId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("视频审核接口调用异常,requirementID:{}", requirement.getId(), e);
|
|
|
+ StoreRenovationRequirement latestRequirement = this.getById(requirement.getId());
|
|
|
+ if (Objects.nonNull(latestRequirement)) {
|
|
|
+ latestRequirement.setAuditStatus(2);
|
|
|
+ latestRequirement.setAuditReason("视频审核接口调用异常:" + e.getMessage()); // 实际需捕获具体异常信息
|
|
|
+ this.saveOrUpdate(latestRequirement);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, videoAuditExecutor);
|
|
|
}
|
|
|
- return this.saveOrUpdate(requirement);
|
|
|
+ requirement.setAuditTime(new Date());
|
|
|
+ boolean isSuccess = this.saveOrUpdate(requirement);
|
|
|
+
|
|
|
+ return isSuccess;
|
|
|
} catch (Exception e) {
|
|
|
log.error("保存装修需求失败: {}", e.getMessage(), e);
|
|
|
throw new RuntimeException("保存装修需求失败: " + e.getMessage());
|