刘云鑫 пре 1 месец
родитељ
комит
d2b6822a4f

+ 24 - 0
alien-store/src/main/java/shop/alien/store/config/AsyncConfig.java

@@ -6,6 +6,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
 import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * 异步任务配置类
@@ -76,5 +77,28 @@ public class AsyncConfig {
         );
         return videoAuditExecutor;
     }
+
+    /**
+     * 图片审核专用线程池,用于门店图片等并行审核,避免每次请求创建新线程池
+     */
+    @Bean(name = "imgAuditExecutor")
+    public ExecutorService imgAuditExecutor() {
+        int core = Math.max(4, Runtime.getRuntime().availableProcessors() * 2);
+        int max = Math.max(8, Runtime.getRuntime().availableProcessors() * 4);
+        AtomicInteger counter = new AtomicInteger(0);
+        return new ThreadPoolExecutor(
+                core,
+                max,
+                60L,
+                TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(300),
+                r -> {
+                    Thread t = new Thread(r);
+                    t.setName("img-audit-" + counter.getAndIncrement());
+                    return t;
+                },
+                new ThreadPoolExecutor.CallerRunsPolicy()
+        );
+    }
 }
 

+ 35 - 28
alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java

@@ -21,6 +21,8 @@ import java.util.List;
 import java.util.concurrent.*;
 import java.util.stream.Collectors;
 
+import org.springframework.beans.factory.annotation.Qualifier;
+
 /**
  * 二期-门店图片Controller
  *
@@ -39,6 +41,8 @@ public class StoreImgController {
     private final StoreInfoService storeInfoService;
     private final StoreOfficialAlbumService storeOfficialAlbumService;
     private final AiContentModerationUtil aiContentModerationUtil;
+    @Qualifier("imgAuditExecutor")
+    private final ExecutorService imgAuditExecutor;
 
     @ApiOperation("获取图片")
     @ApiOperationSupport(order = 1)
@@ -86,37 +90,44 @@ public class StoreImgController {
         if(storeImgInfoVo.getStoreImgList().isEmpty()){
             return R.fail("图片列表为空,请重新上传图片");
         }
-        // 审核图片是否违规:有多少张图片就创建多少个线程并行审核
+        Integer imgType = storeImgInfoVo.getImgType();
+        Integer storeId = storeImgInfoVo.getStoreId();
         List<String> imageUrls = storeImgList.stream()
                 .map(StoreImg::getImgUrl)
                 .collect(Collectors.toList());
-        AiContentModerationUtil.AuditResult auditResult = auditImagesInParallel(imageUrls);
+        // 审核与环境相册查询并行,减少总耗时(imgType==4 时相册查询与审核同时进行)
+        CompletableFuture<AiContentModerationUtil.AuditResult> auditFuture = CompletableFuture
+                .supplyAsync(() -> auditImagesInParallel(imageUrls), imgAuditExecutor);
+        CompletableFuture<List<StoreOfficialAlbum>> albumFuture = (imgType != null && imgType == 4)
+                ? CompletableFuture.supplyAsync(() -> storeOfficialAlbumService.lambdaQuery()
+                        .eq(StoreOfficialAlbum::getStoreId, storeId)
+                        .eq(StoreOfficialAlbum::getAlbumName, "环境")
+                        .orderByAsc(StoreOfficialAlbum::getId)
+                        .list(), imgAuditExecutor)
+                : CompletableFuture.completedFuture(null);
+        AiContentModerationUtil.AuditResult auditResult = auditFuture.join();
         if (!auditResult.isPassed()) {
-            return R.fail(auditResult.getFailureReason()); // 文本内容异常(包含敏感词)
+            return R.fail(auditResult.getFailureReason());
         }
-        // 判断是否是头图(20:单图模式, 21:多图模式)
-        Integer imgType = storeImgInfoVo.getImgType();
-        boolean isHeadImage = (imgType == 20 || imgType == 21);
-        if(imgType==4){
-            Integer storeId = storeImgInfoVo.getStoreId();
-            // 查询名称为"环境"的相册,因为一个门店可能有多个相册
-            List<StoreOfficialAlbum> albumList = storeOfficialAlbumService.lambdaQuery()
-                    .eq(StoreOfficialAlbum::getStoreId, storeId)
-                    .eq(StoreOfficialAlbum::getAlbumName, "环境")
-                    .orderByAsc(StoreOfficialAlbum::getId)
-                    .list();
+        if (imgType != null && imgType == 4) {
+            List<StoreOfficialAlbum> albumList;
+            try {
+                albumList = albumFuture.get(10, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                log.error("查询环境相册异常", e);
+                return R.fail("查询环境相册失败");
+            }
             if (albumList == null || albumList.isEmpty()) {
                 return R.fail("没有默认环境相册");
             }
-            // 如果有多条记录,取第一条(按ID升序)
             StoreOfficialAlbum album = albumList.get(0);
             storeImgList.forEach(storeImg -> storeImg.setBusinessId(album.getId()));
-            // 添加图片时,修改数量(只更新当前使用的相册)
             storeOfficialAlbumService.lambdaUpdate()
                     .eq(StoreOfficialAlbum::getId, album.getId())
                     .setSql("img_count = img_count + " + storeImgList.size())
                     .update();
         }
+        boolean isHeadImage = imgType != null && (imgType == 20 || imgType == 21);
         // 清空storeid,imgType下图片
         int deleteCount = storeImgService.saveOrUpdateImg(storeImgInfoVo.getStoreId(),storeImgInfoVo.getImgType());
         log.info("StoreImgController.updateStoreImgModeInfo?deleteCount={}", deleteCount);
@@ -140,12 +151,11 @@ public class StoreImgController {
                         .orElse(null);
             }
             
-            // 遍历图片列表,设置图片描述,标记需要异步提取颜色的图片
+            // 图片描述按类型固定,只计算一次
+            String imgDescription = getImgDescriptionByType(imgType);
             final StoreImg[] needExtractColorImgRef = {null}; // 需要提取颜色的图片(单图模式或多图模式的第一张)
             for (StoreImg storeImg : storeImgList) {
                 if (storeImg != null && storeImg.getImgUrl() != null) {
-                    // 根据图片类型设置图片描述
-                    String imgDescription = getImgDescriptionByType(imgType);
                     if (imgDescription != null) {
                         storeImg.setImgDescription(imgDescription);
                     }
@@ -337,7 +347,7 @@ public class StoreImgController {
     }
 
     /**
-     * 有多少张图片就创建多少个线程并行审核,每张图片单独审核
+     * 使用共享线程池并行审核,每张图片单独审核,线程数不超过 IMG_AUDIT_MAX_PARALLEL
      *
      * @param imageUrls 图片URL列表
      * @return 合并后的审核结果,任一张不通过则整体不通过
@@ -346,15 +356,14 @@ public class StoreImgController {
         if (imageUrls == null || imageUrls.isEmpty()) {
             return new AiContentModerationUtil.AuditResult(true, null);
         }
-        int threadCount = imageUrls.size();
-        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
         try {
-            List<Future<AiContentModerationUtil.AuditResult>> futures = imageUrls.stream()
-                    .map(url -> executor.submit(() ->
-                            aiContentModerationUtil.auditContent(null, Collections.singletonList(url))))
+            List<CompletableFuture<AiContentModerationUtil.AuditResult>> futures = imageUrls.stream()
+                    .map(url -> CompletableFuture.supplyAsync(
+                            () -> aiContentModerationUtil.auditContent(null, Collections.singletonList(url)),
+                            imgAuditExecutor))
                     .collect(Collectors.toList());
             String firstFailureReason = null;
-            for (Future<AiContentModerationUtil.AuditResult> future : futures) {
+            for (CompletableFuture<AiContentModerationUtil.AuditResult> future : futures) {
                 AiContentModerationUtil.AuditResult result = future.get(30, TimeUnit.SECONDS);
                 if (!result.isPassed() && firstFailureReason == null) {
                     firstFailureReason = result.getFailureReason();
@@ -373,8 +382,6 @@ public class StoreImgController {
         } catch (ExecutionException e) {
             log.error("图片审核异常", e);
             return new AiContentModerationUtil.AuditResult(false, "审核异常");
-        } finally {
-            executor.shutdown();
         }
     }