Sfoglia il codice sorgente

Merge branch 'sit' into uat-20260202

dujian 1 mese fa
parent
commit
d28b27281b

+ 5 - 4
alien-dining/src/main/java/shop/alien/dining/controller/StoreInfoController.java

@@ -64,16 +64,17 @@ public class StoreInfoController {
         }
     }
 
-    @ApiOperation(value = "根据门店ID查询菜品种类及各类别下菜品", notes = "一次返回所有菜品种类及每个分类下的菜品列表")
+    @ApiOperation(value = "根据门店ID查询菜品种类及各类别下菜品", notes = "一次返回所有菜品种类及每个分类下的菜品列表;可选 keyword 按菜品名称模糊查询")
     @GetMapping("/categories-with-cuisines")
     public R<List<CategoryWithCuisinesVO>> getCategoriesWithCuisinesByStoreId(
-            @ApiParam(value = "门店ID", required = true) @RequestParam Integer storeId) {
-        log.info("StoreInfoController.getCategoriesWithCuisinesByStoreId?storeId={}", storeId);
+            @ApiParam(value = "门店ID", required = true) @RequestParam Integer storeId,
+            @ApiParam(value = "菜品名称模糊查询关键词(可选)") @RequestParam(required = false) String keyword) {
+        log.info("StoreInfoController.getCategoriesWithCuisinesByStoreId?storeId={}, keyword={}", storeId, keyword);
         try {
             if (storeId == null) {
                 return R.fail("门店ID不能为空");
             }
-            List<CategoryWithCuisinesVO> list = storeInfoService.getCategoriesWithCuisinesByStoreId(storeId);
+            List<CategoryWithCuisinesVO> list = storeInfoService.getCategoriesWithCuisinesByStoreId(storeId, keyword);
             return R.data(list);
         } catch (Exception e) {
             log.error("查询菜品种类及菜品失败: {}", e.getMessage(), e);

+ 2 - 1
alien-dining/src/main/java/shop/alien/dining/service/StoreInfoService.java

@@ -44,9 +44,10 @@ public interface StoreInfoService {
      * 根据门店ID查询菜品种类及每个分类下的菜品列表(一次返回种类+菜品)
      *
      * @param storeId 门店ID
+     * @param keyword 菜品名称模糊查询关键词(可选,为空则不按名称筛选)
      * @return 菜品种类及下属菜品列表
      */
-    List<CategoryWithCuisinesVO> getCategoriesWithCuisinesByStoreId(Integer storeId);
+    List<CategoryWithCuisinesVO> getCategoriesWithCuisinesByStoreId(Integer storeId, String keyword);
 
     /**
      * 删除菜品种类:仅逻辑删除分类并解除菜品与该分类的绑定关系,价目表(菜品)本身不改动

+ 61 - 32
alien-dining/src/main/java/shop/alien/dining/service/impl/CartServiceImpl.java

@@ -670,7 +670,7 @@ public class CartServiceImpl implements CartService {
      * 餐具的特殊ID(用于标识餐具项)
      */
     private static final Integer TABLEWARE_CUISINE_ID = -1;
-    private static final String TABLEWARE_NAME = "餐具";
+    private static final String TABLEWARE_NAME = "餐具";
 
     /**
      * 获取餐具单价(从 store_info 表获取)
@@ -719,39 +719,47 @@ public class CartServiceImpl implements CartService {
         }
         BigDecimal tablewareUnitPrice = getTablewareUnitPrice(storeId);
 
-        // 查找是否已存在餐具项
-        CartItemDTO tablewareItem = items.stream()
-                .filter(item -> TABLEWARE_CUISINE_ID.equals(item.getCuisineId()))
-                .findFirst()
-                .orElse(null);
+        // 商铺未设置餐具费时,不往购物车加餐具;若已有餐具项则移除
+        if (tablewareUnitPrice == null || tablewareUnitPrice.compareTo(BigDecimal.ZERO) <= 0) {
+            log.info("门店未设置餐具费, storeId={},设置就餐人数时不添加餐具", storeId);
+            CartItemDTO existing = items.stream()
+                    .filter(item -> TABLEWARE_CUISINE_ID.equals(item.getCuisineId()))
+                    .findFirst()
+                    .orElse(null);
+            if (existing != null) {
+                items.remove(existing);
+            }
+        } else {
+            // 查找是否已存在餐具项
+            CartItemDTO tablewareItem = items.stream()
+                    .filter(item -> TABLEWARE_CUISINE_ID.equals(item.getCuisineId()))
+                    .findFirst()
+                    .orElse(null);
 
-        // 获取当前用户信息
-        Integer userId = TokenUtil.getCurrentUserId();
-        String userPhone = TokenUtil.getCurrentUserPhone();
+            Integer userId = TokenUtil.getCurrentUserId();
+            String userPhone = TokenUtil.getCurrentUserPhone();
 
-        if (tablewareItem != null) {
-            // 下单后只能增不能减:已有已下单数量时,用餐人数不能少于已下单数量
-            Integer lockedQuantity = tablewareItem.getLockedQuantity();
-            if (lockedQuantity != null && lockedQuantity > 0 && dinerCount < lockedQuantity) {
-                throw new RuntimeException("餐具数量不能少于已下单数量(" + lockedQuantity + ")");
+            if (tablewareItem != null) {
+                Integer lockedQuantity = tablewareItem.getLockedQuantity();
+                if (lockedQuantity != null && lockedQuantity > 0 && dinerCount < lockedQuantity) {
+                    throw new RuntimeException("餐具数量不能少于已下单数量(" + lockedQuantity + ")");
+                }
+                tablewareItem.setQuantity(dinerCount);
+                tablewareItem.setUnitPrice(tablewareUnitPrice);
+                tablewareItem.setSubtotalAmount(tablewareUnitPrice.multiply(BigDecimal.valueOf(dinerCount)));
+            } else {
+                CartItemDTO newTablewareItem = new CartItemDTO();
+                newTablewareItem.setCuisineId(TABLEWARE_CUISINE_ID);
+                newTablewareItem.setCuisineName(TABLEWARE_NAME);
+                newTablewareItem.setCuisineType(0);
+                newTablewareItem.setCuisineImage("");
+                newTablewareItem.setUnitPrice(tablewareUnitPrice);
+                newTablewareItem.setQuantity(dinerCount);
+                newTablewareItem.setSubtotalAmount(tablewareUnitPrice.multiply(BigDecimal.valueOf(dinerCount)));
+                newTablewareItem.setAddUserId(userId);
+                newTablewareItem.setAddUserPhone(userPhone);
+                items.add(newTablewareItem);
             }
-            // 更新餐具数量和单价
-            tablewareItem.setQuantity(dinerCount);
-            tablewareItem.setUnitPrice(tablewareUnitPrice);
-            tablewareItem.setSubtotalAmount(tablewareUnitPrice.multiply(BigDecimal.valueOf(dinerCount)));
-        } else {
-            // 添加餐具项
-            CartItemDTO newTablewareItem = new CartItemDTO();
-            newTablewareItem.setCuisineId(TABLEWARE_CUISINE_ID);
-            newTablewareItem.setCuisineName(TABLEWARE_NAME);
-            newTablewareItem.setCuisineType(0); // 0表示餐具
-            newTablewareItem.setCuisineImage("");
-            newTablewareItem.setUnitPrice(tablewareUnitPrice);
-            newTablewareItem.setQuantity(dinerCount);
-            newTablewareItem.setSubtotalAmount(tablewareUnitPrice.multiply(BigDecimal.valueOf(dinerCount)));
-            newTablewareItem.setAddUserId(userId);
-            newTablewareItem.setAddUserPhone(userPhone);
-            items.add(newTablewareItem);
         }
 
         // 重新计算总金额和总数量
@@ -790,7 +798,6 @@ public class CartServiceImpl implements CartService {
         // 获取门店ID和餐具单价
         Integer storeId = cart.getStoreId();
         if (storeId == null) {
-            // 如果购物车中没有门店ID,从桌号获取
             StoreTable table = storeTableMapper.selectById(tableId);
             if (table != null) {
                 storeId = table.getStoreId();
@@ -798,6 +805,28 @@ public class CartServiceImpl implements CartService {
         }
         BigDecimal tablewareUnitPrice = getTablewareUnitPrice(storeId);
 
+        // 商铺未设置餐具费(单价为0或未配置)时,不往购物车加餐具;若已有餐具项则移除
+        if (tablewareUnitPrice == null || tablewareUnitPrice.compareTo(BigDecimal.ZERO) <= 0) {
+            log.info("门店未设置餐具费, storeId={},不添加餐具到购物车", storeId);
+            CartItemDTO existing = items.stream()
+                    .filter(item -> TABLEWARE_CUISINE_ID.equals(item.getCuisineId()))
+                    .findFirst()
+                    .orElse(null);
+            if (existing != null) {
+                items.remove(existing);
+                BigDecimal totalAmount = items.stream()
+                        .map(CartItemDTO::getSubtotalAmount)
+                        .reduce(BigDecimal.ZERO, BigDecimal::add);
+                Integer totalQuantity = items.stream()
+                        .mapToInt(CartItemDTO::getQuantity)
+                        .sum();
+                cart.setTotalAmount(totalAmount);
+                cart.setTotalQuantity(totalQuantity);
+                saveCart(cart);
+            }
+            return cart;
+        }
+
         // 查找餐具项
         CartItemDTO tablewareItem = items.stream()
                 .filter(item -> TABLEWARE_CUISINE_ID.equals(item.getCuisineId()))

+ 3 - 0
alien-dining/src/main/java/shop/alien/dining/service/impl/DiningUserServiceImpl.java

@@ -81,6 +81,9 @@ public class DiningUserServiceImpl implements DiningUserService {
                 log.warn("解析手机号失败,phoneCode可能已过期或无效");
             }
         }
+        if (StringUtils.isBlank(parsedPhone)) {
+            log.info("微信登录未传手机号或解析失败: openid={}, phoneCode={}", maskString(openid, 8), StringUtils.isNotBlank(phoneCode) ? "已传" : "未传");
+        }
 
         // 3. 查找或创建用户(传入解析后的手机号,避免重复解析)
         LifeUser user = findOrCreateUser(openid, parsedPhone);

+ 5 - 2
alien-dining/src/main/java/shop/alien/dining/service/impl/StoreInfoServiceImpl.java

@@ -105,8 +105,8 @@ public class StoreInfoServiceImpl implements StoreInfoService {
     }
 
     @Override
-    public List<CategoryWithCuisinesVO> getCategoriesWithCuisinesByStoreId(Integer storeId) {
-        log.info("根据门店ID查询菜品种类及下属菜品, storeId={}", storeId);
+    public List<CategoryWithCuisinesVO> getCategoriesWithCuisinesByStoreId(Integer storeId, String keyword) {
+        log.info("根据门店ID查询菜品种类及下属菜品, storeId={}, keyword={}", storeId, keyword);
         List<StoreCuisineCategory> categories = getCategoriesByStoreId(storeId);
         if (categories == null || categories.isEmpty()) {
             return new ArrayList<>();
@@ -119,11 +119,14 @@ public class StoreInfoServiceImpl implements StoreInfoService {
         if (allCuisines == null) {
             allCuisines = new ArrayList<>();
         }
+        boolean filterByName = StringUtils.isNotBlank(keyword);
+        String keywordLower = filterByName ? keyword.trim().toLowerCase() : null;
         List<CategoryWithCuisinesVO> result = new ArrayList<>();
         for (StoreCuisineCategory category : categories) {
             Integer categoryId = category.getId();
             List<StoreCuisine> cuisines = allCuisines.stream()
                     .filter(c -> belongsToCategory(c, categoryId))
+                    .filter(c -> !filterByName || (c.getName() != null && c.getName().toLowerCase().contains(keywordLower)))
                     .collect(Collectors.toList());
             result.add(new CategoryWithCuisinesVO(category, cuisines));
         }

+ 18 - 1
alien-dining/src/main/java/shop/alien/dining/service/impl/StoreOrderServiceImpl.java

@@ -1354,7 +1354,23 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         detailWrapper.orderByDesc(StoreOrderDetail::getCreatedTime);
         List<StoreOrderDetail> details = orderDetailMapper.selectList(detailWrapper);
         
-        // 转换为CartItemDTO
+        // 批量查询菜品标签(订单明细无 tags,从价目表取)
+        java.util.Set<Integer> cuisineIds = details.stream()
+                .map(StoreOrderDetail::getCuisineId)
+                .filter(java.util.Objects::nonNull)
+                .collect(Collectors.toSet());
+        java.util.Map<Integer, String> cuisineIdToTags = new java.util.HashMap<>();
+        if (!cuisineIds.isEmpty()) {
+            List<StoreCuisine> cuisines = storeCuisineMapper.selectBatchIds(cuisineIds);
+            if (cuisines != null) {
+                for (StoreCuisine c : cuisines) {
+                    cuisineIdToTags.put(c.getId(), c.getTags());
+                }
+            }
+        }
+        final java.util.Map<Integer, String> tagsMap = cuisineIdToTags;
+        
+        // 转换为CartItemDTO(含 tags)
         List<CartItemDTO> items = details.stream().map(detail -> {
             CartItemDTO item = new CartItemDTO();
             item.setCuisineId(detail.getCuisineId());
@@ -1367,6 +1383,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             item.setAddUserId(detail.getAddUserId());
             item.setAddUserPhone(detail.getAddUserPhone());
             item.setRemark(detail.getRemark());
+            item.setTags(detail.getCuisineId() != null ? tagsMap.get(detail.getCuisineId()) : null);
             return item;
         }).collect(Collectors.toList());
         

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CartItemDTO.java

@@ -48,4 +48,7 @@ public class CartItemDTO {
 
     @ApiModelProperty(value = "备注")
     private String remark;
+
+    @ApiModelProperty(value = "菜品标签(JSON数组,如:[\"招牌菜\",\"推荐\"])")
+    private String tags;
 }

+ 7 - 5
alien-store-platform/src/main/java/shop/alien/storeplatform/feign/AlienAIFeign.java

@@ -6,6 +6,8 @@ import org.springframework.http.MediaType;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.Map;
+
 @FeignClient(url = "${feign.alienAI.url}", name = "alien-AI")
 public interface AlienAIFeign {
 
@@ -22,16 +24,16 @@ public interface AlienAIFeign {
     JsonNode login(@RequestBody MultiValueMap<String, String> formData);
 
     /**
-     * 使用 JsonNode 灵活调用接口 - 生成促销图片
+     * 生成促销图片。请求体格式:{"text": "图片描述内容"}
      *
      * @param authorization Bearer token,格式为 "Bearer {access_token}"
-     * @param requestBody JsonNode 请求体,可以灵活构建
+     * @param requestBody 请求体,需包含 key "text",值为图片描述。例如:Map.of("text", "描述内容")
      * @return JsonNode 响应体,可以灵活解析
      */
     @PostMapping(value = "/ai/life-manager/api/v1/promotion_image/generate",
-                 consumes = MediaType.APPLICATION_JSON_VALUE,
-                 produces = MediaType.APPLICATION_JSON_VALUE)
-    JsonNode generatePromotionImage(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode generatePromotionImage(@RequestHeader("Authorization") String authorization, @RequestBody Map<String, String> requestBody);
 
 //    /**
 //     * 使用 JsonNode 灵活调用接口 - 发起AI审核(多模态)

+ 3 - 2
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -43,6 +43,7 @@ import shop.alien.util.common.Constants;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -218,7 +219,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                                 dto.getImgDescribe()
                         );
                         requestBody.put("text", filled);
-                        JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, requestBody);
+                        JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, Collections.singletonMap("text", filled));
                         // 解析响应
                         if (imgResponse.has("data")) {
                             JsonNode data = imgResponse.get("data");
@@ -380,7 +381,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                             dto.getImgDescribe()
                     );
                     requestBody.put("text", filled);
-                    JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, requestBody);
+                    JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, Collections.singletonMap("text", filled));
                     // 解析响应
                     if (imgResponse.has("data")) {
                         JsonNode data = imgResponse.get("data");

+ 5 - 4
alien-store/src/main/java/shop/alien/store/controller/DiningServiceController.java

@@ -292,14 +292,15 @@ public class DiningServiceController {
         }
     }
 
-    @ApiOperation(value = "根据门店ID查询菜品种类及各类别下菜品", notes = "一次返回所有菜品种类及每个分类下的菜品列表")
+    @ApiOperation(value = "根据门店ID查询菜品种类及各类别下菜品", notes = "一次返回所有菜品种类及每个分类下的菜品列表;可选 keyword 按菜品名称模糊查询")
     @ApiOperationSupport(order = 11)
     @GetMapping("/store/info/categories-with-cuisines")
     public R<List<CategoryWithCuisinesVO>> getCategoriesWithCuisinesByStoreId(
-            @ApiParam(value = "门店ID", required = true) @RequestParam Integer storeId) {
+            @ApiParam(value = "门店ID", required = true) @RequestParam Integer storeId,
+            @ApiParam(value = "菜品名称模糊查询关键词(可选)") @RequestParam(required = false) String keyword) {
         try {
-            log.info("根据门店ID查询菜品种类及菜品: storeId={}", storeId);
-            return diningServiceFeign.getCategoriesWithCuisinesByStoreId(storeId);
+            log.info("根据门店ID查询菜品种类及菜品: storeId={}, keyword={}", storeId, keyword);
+            return diningServiceFeign.getCategoriesWithCuisinesByStoreId(storeId, keyword);
         } catch (Exception e) {
             log.error("查询菜品种类及菜品失败: {}", e.getMessage(), e);
             return R.fail("查询菜品种类及菜品失败: " + e.getMessage());

+ 4 - 2
alien-store/src/main/java/shop/alien/store/feign/DiningServiceFeign.java

@@ -227,14 +227,16 @@ public interface DiningServiceFeign {
             @RequestParam("storeId") Integer storeId);
 
     /**
-     * 根据门店ID查询菜品种类及各类别下菜品
+     * 根据门店ID查询菜品种类及各类别下菜品(可选按菜品名称模糊查询)
      *
      * @param storeId 门店ID
+     * @param keyword 菜品名称模糊查询关键词(可选)
      * @return R.data 为 List&lt;CategoryWithCuisinesVO&gt;
      */
     @GetMapping("/store/info/categories-with-cuisines")
     R<List<CategoryWithCuisinesVO>> getCategoriesWithCuisinesByStoreId(
-            @RequestParam("storeId") Integer storeId);
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam(value = "keyword", required = false) String keyword);
 
     /**
      * 删除菜品种类(仅解除绑定+逻辑删分类,价目表菜品不变)

+ 28 - 5
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -1,5 +1,6 @@
 package shop.alien.store.service.impl;
 
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -3404,24 +3405,46 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             }
             if (StringUtils.isNotEmpty(response.getBody())) {
                 JSONObject jsonObject = JSONObject.parseObject(response.getBody());
-                if (jsonObject.getInteger("code") == 200) {
+                if (Integer.valueOf(200).equals(jsonObject.getInteger("code"))) {
                     JSONObject data = jsonObject.getJSONObject("data");
+                    if (data == null) {
+                        log.info("AI门店审核接口返回 data 为空, userId={}", aiApproveStoreInfo.getUserId());
+                        throw new RuntimeException("AI门店审核接口返回 data 为空");
+                    }
                     List<StoreInfo> storeInfos = storeInfoMapper.selectList(new LambdaQueryWrapper<StoreInfo>()
                             .eq(StoreInfo::getCreatedUserId, aiApproveStoreInfo.getUserId()).eq(StoreInfo::getStoreApplicationStatus, 0).eq(StoreInfo::getDeleteFlag, 0));
+                    String status = data.getString("status");
                     for (StoreInfo storeInfo : storeInfos) {
-                        if ("approved".equals(data.getString("status"))) {
+                        if ("approved".equals(status)) {
+                            log.info("AI门店审核通过, storeId={}", storeInfo.getId());
                             approveStoreInfo(storeInfo.getId().toString(), 1, "审核通过");
-                        } else if ("rejected".equals(data.getString("status"))) {
-                            approveStoreInfo(storeInfo.getId().toString(), 2, data.getString("audit_summary"));
+                        } else if ("rejected".equals(status)) {
+                            log.info("AI门店审核拒绝, storeId={}", storeInfo.getId());
+                            String reason = data.getString("audit_summary");
+                            if (StringUtils.isEmpty(reason) && data.containsKey("risk_tags")) {
+                                log.info("AI门店审核拒绝, risk_tags 不为空, storeId={}", storeInfo.getId());
+                                JSONArray riskTags = data.getJSONArray("risk_tags");
+                                if (riskTags != null && !riskTags.isEmpty()) {
+                                    StringBuilder sb = new StringBuilder();
+                                    for (int i = 0; i < riskTags.size(); i++) {
+                                        if (i > 0) sb.append(";");
+                                        sb.append(riskTags.getString(i));
+                                    }
+                                    reason = sb.toString();
+                                }
+                            }
+                            approveStoreInfo(storeInfo.getId().toString(), 2, StringUtils.isNotEmpty(reason) ? reason : "审核未通过");
                         } else {
-                            System.out.println("未知状态");
+                            log.warn("AI门店审核返回未知状态: status={}, storeId={}", status, storeInfo.getId());
                         }
                     }
                 } else {
+                    log.error("AI门店审核接口调用失败, userId={}, code={}", aiApproveStoreInfo.getUserId(), jsonObject.getInteger("code"));
                     throw new RuntimeException("AI门店审核接口调用失败 code:" + jsonObject.getInteger("code"));
                 }
             }
         } catch (Exception e) {
+            log.error("调用门店审核接口异常, userId={},{}", aiApproveStoreInfo.getUserId(), e);
             throw new RuntimeException("调用门店审核接口异常", e);
         }
     }

+ 186 - 69
alien-store/src/main/java/shop/alien/store/util/FileUploadUtil.java

@@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartRequest;
@@ -16,10 +17,13 @@ import shop.alien.util.common.VideoUtils;
 import shop.alien.util.file.FileUtil;
 
 import java.io.File;
+import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * 二期-文件上传
@@ -124,77 +128,143 @@ public class FileUploadUtil {
      * @return List<String>
      */
     public List<String> uploadMoreFile(MultipartRequest multipartRequest) {
+        long startTime = System.currentTimeMillis();
         try {
             log.info("FileUpload.uploadMoreFile multipartRequest={}", multipartRequest.getFileNames());
-            Set<String> fileNameSet = multipartRequest.getMultiFileMap().keySet();
-            List<String> filePathList = new ArrayList<>();
-            for (String s : fileNameSet) {
-                MultipartFile multipartFile = multipartRequest.getFileMap().get(s);
-                log.info("FileUpload.uploadMoreFile fileName={}", multipartFile.getOriginalFilename());
-                String uploadDir = this.uploadDir.replace("file:///", "").replace("\\", "/");
-                String prefix;
-                Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(multipartFile);
-                //区分文件类型
-                if (imageFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/image";
-                    prefix = "image/";
-                    log.info("FileUpload.uploadMoreFile 获取到图片文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String imageFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + imageFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
-                    ;
-                } else if (videoFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/video/";
-                    prefix = "video/";
-                    //上传视频文件
-                    log.info("FileUpload.uploadMoreFile 获取到视频文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String videoFileName = fileNameAndType.get("name").replaceAll(",", "") + RandomCreateUtil.getRandomNum(6);
-                    String cacheVideoPath = copyFile(uploadDir, multipartFile);
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + videoFileName + "." + fileNameAndType.get("type")));
-                    //缓存视频截图使用
-//                    File videoFile = new File(cacheVideoPath);
-//                    //获取视频某帧截图
-//                    log.info("FileUpload.uploadMoreFile 视频文件复制完毕, 获取第一秒图片 {}", videoFile.getName());
-//                    String videoPath = videoUtils.getImg(uploadDir + videoFile.getName());
-//                    log.info("FileUpload.uploadMoreFile 视频文件复制完毕, 图片位置 {}", videoPath);
-//                    if (!videoPath.isEmpty()) {
-//                        File videoImgFile = new File(videoPath);
-//                        Map<String, String> videoImg = FileUtil.getFileNameAndType(videoImgFile);
-//                        filePathList.add(aliOSSUtil.uploadFile(videoImgFile, prefix + videoFileName + "." + videoImg.get("type")));
-//                        videoImgFile.delete();
-//                        videoFile.delete();
-//                    } else {
-//                        throw new RuntimeException("视频截图失败");
-//                    }
-                } else if (voiceFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/voice";
-                    prefix = "voice/";
-                    log.info("FileUpload.uploadMoreFile 获取到语音文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String voiceFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + voiceFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
-                } else if (privacyFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/privacy/";
-                    prefix = "privacy/";
-                    log.info("FileUpload.uploadMoreFile 获取到隐私文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String privacyFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + privacyFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
-                } else if (pdfFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/pdf";
-                    prefix = "pdf/";
-                    log.info("FileUpload.uploadMoreFile 获取到PDF文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String pdfFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + pdfFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
-                } else if (ohterFileType.contains(fileNameAndType.get("type").toLowerCase())) {
-                    uploadDir += "/other/";
-                    prefix = "other/";
-                    log.info("FileUpload.uploadMoreFile 获取到其他文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
-                    // 去除文件名中的逗号,避免URL拼接时被错误分割
-                    String otherFileName = fileNameAndType.get("name").replaceAll(",", "");
-                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + otherFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
+            Collection<MultipartFile> files = multipartRequest.getFileMap().values();
+
+            if (files.isEmpty()) {
+                return Collections.emptyList();
+            }
+
+            String baseUploadDir = this.uploadDir.replace("file:///", "").replace("\\", "/");
+
+            // 如果只有一个文件,直接处理(避免线程池开销)
+            if (files.size() == 1) {
+                MultipartFile file = files.iterator().next();
+                return processSingleFileForUploadMore(file, baseUploadDir);
+            }
+
+            // 多个文件使用并行流处理
+            List<String> filePathList = files.parallelStream()
+                    .flatMap(file -> {
+                        try {
+                            return processSingleFileForUploadMore(file, baseUploadDir).stream();
+                        } catch (Exception e) {
+                            log.error("处理文件失败: {}", file.getOriginalFilename(), e);
+                            return Stream.empty();
+                        }
+                    })
+                    .collect(Collectors.toList());
+
+            long totalTime = System.currentTimeMillis() - startTime;
+            log.info("批量文件上传完成,文件数: {}, 总耗时: {} ms", files.size(), totalTime);
+
+            return filePathList;
+        } catch (Exception e) {
+            log.error("FileUpload.uploadMoreFile ERROR Msg={}", e.getMessage(), e);
+            throw new RuntimeException("批量文件上传失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 处理单个文件(用于 uploadMoreFile)
+     *
+     * @param multipartFile 文件
+     * @param baseUploadDir 基础上传目录
+     * @return 文件URL列表(视频会包含视频URL和截图URL)
+     */
+    private List<String> processSingleFileForUploadMore(MultipartFile multipartFile, String baseUploadDir) {
+        List<String> filePathList = new ArrayList<>();
+        File tempVideoFile = null;
+
+        try {
+            Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(multipartFile);
+            String fileType = fileNameAndType.get("type").toLowerCase();
+            String prefix;
+            String uploadDir = baseUploadDir;
+
+            // 区分文件类型并处理
+            if (imageFileType.contains(fileType)) {
+                prefix = "image/";
+                String imageFileName = fileNameAndType.get("name").replaceAll(",", "");
+                String ossFilePath = prefix + imageFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
+                // 直接流式上传
+                try (InputStream inputStream = multipartFile.getInputStream()) {
+                    String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
+                    if (url != null) {
+                        filePathList.add(url);
+                    }
+                }
+
+            } else if (videoFileType.contains(fileType)) {
+                prefix = "video/";
+                uploadDir += "/video/";
+                String videoFileName = fileNameAndType.get("name").replaceAll(",", "") + RandomCreateUtil.getRandomNum(6);
+                String ossFilePath = prefix + videoFileName + "." + fileNameAndType.get("type");
+
+                // 视频需要截图,先保存文件
+                String tempPath = copyFile(uploadDir, multipartFile);
+                tempVideoFile = new File(tempPath);
+                if (!tempVideoFile.exists() || tempVideoFile.length() == 0) {
+                    throw new RuntimeException("视频文件保存失败: " + tempPath);
+                }
+
+                // 从文件上传视频
+                String videoUrl = aliOSSUtil.uploadFile(tempVideoFile, ossFilePath);
+                if (videoUrl != null) {
+                    filePathList.add(videoUrl);
+                }
+
+                // 异步处理视频截图
+                processVideoScreenshotAsync(tempVideoFile, videoFileName, prefix, uploadDir);
+
+            } else if (voiceFileType.contains(fileType)) {
+                prefix = "voice/";
+                String voiceFileName = fileNameAndType.get("name").replaceAll(",", "");
+                String ossFilePath = prefix + voiceFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
+                // 直接流式上传
+                try (InputStream inputStream = multipartFile.getInputStream()) {
+                    String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
+                    if (url != null) {
+                        filePathList.add(url);
+                    }
+                }
+
+            } else if (privacyFileType.contains(fileType)) {
+                prefix = "privacy/";
+                String privacyFileName = fileNameAndType.get("name").replaceAll(",", "");
+                String ossFilePath = prefix + privacyFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
+                // 直接流式上传
+                try (InputStream inputStream = multipartFile.getInputStream()) {
+                    String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
+                    if (url != null) {
+                        filePathList.add(url);
+                    }
+                }
+
+            } else if (pdfFileType.contains(fileType)) {
+                prefix = "pdf/";
+                String pdfFileName = fileNameAndType.get("name").replaceAll(",", "");
+                String ossFilePath = prefix + pdfFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
+                // 直接流式上传
+                try (InputStream inputStream = multipartFile.getInputStream()) {
+                    String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
+                    if (url != null) {
+                        filePathList.add(url);
+                    }
+                }
+
+            } else if (ohterFileType.contains(fileType)) {
+                prefix = "other/";
+                String otherFileName = fileNameAndType.get("name").replaceAll(",", "");
+                String ossFilePath = prefix + otherFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type");
+                // 直接流式上传
+                try (InputStream inputStream = multipartFile.getInputStream()) {
+                    String url = aliOSSUtil.uploadFile(inputStream, ossFilePath);
+                    if (url != null) {
+                        filePathList.add(url);
+                    }
                 }
             }
             return filePathList;
@@ -205,6 +275,53 @@ public class FileUploadUtil {
     }
 
     /**
+     * 异步处理视频截图
+     *
+     * @param tempVideoFile 临时视频文件
+     * @param videoFileName 视频文件名(不含扩展名)
+     * @param prefix OSS路径前缀
+     * @param uploadDir 上传目录
+     */
+    @Async
+    protected void processVideoScreenshotAsync(File tempVideoFile, String videoFileName, String prefix, String uploadDir) {
+        try {
+            log.info("开始异步处理视频截图: {}", tempVideoFile.getName());
+
+            // 获取视频截图
+            String screenshotPath = videoUtils.getImg(uploadDir + tempVideoFile.getName());
+
+            if (screenshotPath != null && !screenshotPath.isEmpty()) {
+                File screenshotFile = new File(screenshotPath);
+                if (screenshotFile.exists()) {
+                    try {
+                        // 上传截图到OSS
+                        Map<String, String> screenshotInfo = FileUtil.getFileNameAndType(screenshotFile);
+                        String screenshotUrl = aliOSSUtil.uploadFile(screenshotFile, prefix + videoFileName + "." + screenshotInfo.get("type"));
+
+                        log.info("视频截图上传成功: {} -> {}", tempVideoFile.getName(), screenshotUrl);
+                    } finally {
+                        // 清理临时截图文件
+                        screenshotFile.delete();
+                    }
+                } else {
+                    log.warn("视频截图文件不存在: {}", screenshotPath);
+                }
+            } else {
+                log.warn("视频截图失败: {}", tempVideoFile.getName());
+            }
+
+        } catch (Exception e) {
+            log.error("异步处理视频截图失败: {}", tempVideoFile.getName(), e);
+        } finally {
+            // 清理临时视频文件
+            if (tempVideoFile != null && tempVideoFile.exists()) {
+                tempVideoFile.delete();
+            }
+        }
+    }
+
+
+    /**
      * 上传图片
      *
      * @param multipartRequest 文件