Просмотр исходного кода

Merge branch 'sit' into uat-20260202

dujian 1 месяц назад
Родитель
Сommit
a066f42523
20 измененных файлов с 261 добавлено и 291 удалено
  1. 6 4
      alien-dining/src/main/java/shop/alien/dining/controller/DiningCouponController.java
  2. 15 21
      alien-dining/src/main/java/shop/alien/dining/controller/StoreOrderController.java
  3. 3 1
      alien-dining/src/main/java/shop/alien/dining/feign/AlienStoreFeign.java
  4. 1 1
      alien-dining/src/main/java/shop/alien/dining/service/DiningCouponService.java
  5. 0 11
      alien-dining/src/main/java/shop/alien/dining/service/StoreOrderService.java
  6. 22 1
      alien-dining/src/main/java/shop/alien/dining/service/impl/CartServiceImpl.java
  7. 3 3
      alien-dining/src/main/java/shop/alien/dining/service/impl/DiningCouponServiceImpl.java
  8. 70 168
      alien-dining/src/main/java/shop/alien/dining/service/impl/StoreOrderServiceImpl.java
  9. 1 1
      alien-dining/src/main/java/shop/alien/dining/strategy/payment/impl/WeChatPaymentMininProgramStrategyImpl.java
  10. 0 33
      alien-entity/src/main/java/shop/alien/entity/store/dto/AddDishDTO.java
  11. 6 6
      alien-entity/src/main/java/shop/alien/entity/store/dto/CreateOrderDTO.java
  12. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderChangeLogBatchVO.java
  13. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOrderPageVO.java
  14. 24 0
      alien-store/src/main/java/shop/alien/store/config/AsyncConfig.java
  15. 4 4
      alien-store/src/main/java/shop/alien/store/config/WebSocketConfig.java
  16. 7 5
      alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponController.java
  17. 69 20
      alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java
  18. 2 2
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  19. 2 1
      alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponService.java
  20. 20 9
      alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponServiceImpl.java

+ 6 - 4
alien-dining/src/main/java/shop/alien/dining/controller/DiningCouponController.java

@@ -49,7 +49,8 @@ public class DiningCouponController {
             @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "tabType", value = "分页类型(0:全部(未使用),1:即将过期,2:已使用,3:已过期)", dataType = "String", paramType = "query", required = true),
             @ApiImplicitParam(name = "type", value = "券类型(不传:优惠券+代金券都返回,1:仅优惠券查 life_discount_coupon,4:仅代金券查 life_coupon)", dataType = "Integer", paramType = "query", required = false),
-            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)", dataType = "Integer", paramType = "query", required = false)
+            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "storeId", value = "商铺ID,可为空,传则仅返回该商铺的优惠券", dataType = "String", paramType = "query", required = false)
     })
     public R<List<LifeDiscountCouponVo>> getUserCouponList(
             HttpServletRequest request,
@@ -57,10 +58,11 @@ public class DiningCouponController {
             @RequestParam(value = "size", defaultValue = "10") int size,
             @RequestParam("tabType") String tabType,
             @RequestParam(value = "type", required = false) Integer type,
-            @RequestParam(value = "couponType", required = false) Integer couponType) {
-        log.info("DiningCouponController.getUserCouponList?page={}, size={}, tabType={}, type={}, couponType={}", page, size, tabType, type, couponType);
+            @RequestParam(value = "couponType", required = false) Integer couponType,
+            @RequestParam(value = "storeId", required = false) String storeId) {
+        log.info("DiningCouponController.getUserCouponList?page={}, size={}, tabType={}, type={}, couponType={}, storeId={}", page, size, tabType, type, couponType, storeId);
         String authorization = request.getHeader("Authorization");
-        return diningCouponService.getUserCouponList(authorization, page, size, tabType, type, couponType);
+        return diningCouponService.getUserCouponList(authorization, page, size, tabType, type, couponType, storeId);
     }
 
     /**

+ 15 - 21
alien-dining/src/main/java/shop/alien/dining/controller/StoreOrderController.java

@@ -192,20 +192,31 @@ public class StoreOrderController {
         }
     }
 
-    @ApiOperation(value = "更新餐具数量", notes = "更新购物车中餐具的数量,并推送SSE和WebSocket消息")
+    @ApiOperation(value = "更新餐具数量", notes = "更新购物车中餐具的数量,并推送SSE和WebSocket消息。餐具数量为0或未传时不往购物车加餐具(0会移除已有餐具项)")
     @PutMapping("/cart/update-tableware")
     public R<CartDTO> updateTablewareQuantity(
             @ApiParam(value = "桌号ID", required = true) @RequestParam Integer tableId,
-            @ApiParam(value = "餐具数量", required = true) @RequestParam Integer quantity) {
+            @ApiParam(value = "餐具数量,0或未传则不往购物车加餐具") @RequestParam(required = false) Integer quantity) {
         log.info("StoreOrderController.updateTablewareQuantity?tableId={}, quantity={}", tableId, quantity);
         try {
             // 验证 token
             if (!TokenUtil.hasValidToken()) {
                 return R.fail("用户未登录");
             }
-            if (quantity == null || quantity < 0) {
+            if (quantity != null && quantity < 0) {
                 return R.fail("餐具数量不能小于0");
             }
+            // 餐具数量为 0 或 null 时不往购物车加餐具:0 则移除已有餐具项,null 则直接返回当前购物车
+            if (quantity == null) {
+                CartDTO cart = cartService.getCart(tableId);
+                sseService.pushCartUpdate(tableId, cart);
+                return R.data(cart);
+            }
+            if (quantity == 0) {
+                CartDTO cart = cartService.updateTablewareQuantity(tableId, 0);
+                sseService.pushCartUpdate(tableId, cart);
+                return R.data(cart);
+            }
             CartDTO cart = cartService.updateTablewareQuantity(tableId, quantity);
             // 推送购物车更新消息(SSE)
             sseService.pushCartUpdate(tableId, cart);
@@ -257,23 +268,6 @@ public class StoreOrderController {
         }
     }
 
-    @ApiOperation(value = "加餐", notes = "在已有订单基础上添加菜品")
-    @PostMapping("/add-dish")
-    public R<StoreOrder> addDishToOrder(@Valid @RequestBody shop.alien.entity.store.dto.AddDishDTO dto) {
-        log.info("StoreOrderController.addDishToOrder?dto={}", dto);
-        try {
-            // 验证 token(用户信息在 StoreOrderService 中从 token 获取)
-            if (!TokenUtil.hasValidToken()) {
-                return R.fail("用户未登录");
-            }
-            StoreOrder order = orderService.addDishToOrder(dto.getOrderId(), dto.getCuisineId(), dto.getQuantity(), dto.getRemark());
-            return R.data(order);
-        } catch (Exception e) {
-            log.error("加餐失败: {}", e.getMessage(), e);
-            return R.fail("加餐失败: " + e.getMessage());
-        }
-    }
-
     @ApiOperation(value = "支付订单", notes = "支付订单,支付成功后订单状态变为已支付(1),需要单独调用完成订单接口将订单状态改为已完成(3)")
     @PostMapping("/pay/{orderId}")
     public R<Object> payOrder(
@@ -418,7 +412,7 @@ public class StoreOrderController {
             @ApiParam(value = "每页数量", required = true) @RequestParam(defaultValue = "10") Long size,
             @ApiParam(value = "门店ID") @RequestParam(required = false) Integer storeId,
             @ApiParam(value = "桌号ID") @RequestParam(required = false) Integer tableId,
-            @ApiParam(value = "订单状态") @RequestParam(required = false) Integer orderStatus,
+            @ApiParam(value = "订单状态:0=进行中(待支付+已支付),3=已完成") @RequestParam(required = false) Integer orderStatus,
             @ApiParam(value = "搜索关键词(订单编号或菜品名称,限15字)") @RequestParam(required = false) String keyword) {
         log.info("StoreOrderController.getOrderPage?current={}, size={}, storeId={}, tableId={}, orderStatus={}, keyword={}", current, size, storeId, tableId, orderStatus, keyword);
         try {

+ 3 - 1
alien-dining/src/main/java/shop/alien/dining/feign/AlienStoreFeign.java

@@ -54,6 +54,7 @@ public interface AlienStoreFeign {
      * @param size          每页条数(默认10)
      * @param type          券类型(不传:优惠券+代金券都返回,1:仅优惠券查 life_discount_coupon,4:仅代金券查 life_coupon)(可选)
      * @param couponType    优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)
+     * @param storeId       商铺ID,可为空,传则仅返回该商铺的优惠券
      * @return R.data 为 List&lt;LifeDiscountCouponVo&gt;
      */
     @GetMapping("life-discount-coupon/getUserCouponList")
@@ -63,7 +64,8 @@ public interface AlienStoreFeign {
             @RequestParam(value = "page", defaultValue = "1") int page,
             @RequestParam(value = "size", defaultValue = "10") int size,
             @RequestParam(value = "type", required = false) Integer type,
-            @RequestParam(value = "couponType", required = false) Integer couponType);
+            @RequestParam(value = "couponType", required = false) Integer couponType,
+            @RequestParam(value = "storeId", required = false) String storeId);
 
     /**
      * 根据优惠券 ID 获取优惠券详情(含规则、门槛等)

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

@@ -28,7 +28,7 @@ public interface DiningCouponService {
      * @param couponType    优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)
      * @return R.data 为 List&lt;LifeDiscountCouponVo&gt;
      */
-    R<List<LifeDiscountCouponVo>> getUserCouponList(String authorization, int page, int size, String tabType, Integer type, Integer couponType);
+    R<List<LifeDiscountCouponVo>> getUserCouponList(String authorization, int page, int size, String tabType, Integer type, Integer couponType, String storeId);
 
     /**
      * 根据优惠券 id 获取优惠券详情

+ 0 - 11
alien-dining/src/main/java/shop/alien/dining/service/StoreOrderService.java

@@ -85,17 +85,6 @@ public interface StoreOrderService extends IService<StoreOrder> {
     IPage<StoreOrderPageVO> getOrderPageWithCuisines(Page<StoreOrder> page, Integer storeId, Integer tableId, Integer orderStatus, String keyword);
 
     /**
-     * 加餐(在已有订单基础上添加菜品)
-     *
-     * @param orderId 订单ID
-     * @param cuisineId 菜品ID
-     * @param quantity 数量
-     * @param remark 备注
-     * @return 订单信息
-     */
-    StoreOrder addDishToOrder(Integer orderId, Integer cuisineId, Integer quantity, String remark);
-
-    /**
      * 完成订单(支付完成后调用)
      *
      * @param orderId 订单ID

+ 22 - 1
alien-dining/src/main/java/shop/alien/dining/service/impl/CartServiceImpl.java

@@ -425,8 +425,19 @@ public class CartServiceImpl implements CartService {
             }
         }
         
-        // 将餐具项添加到保留列表中
+        // 将餐具项添加到保留列表中,若有已下单数量则恢复为已下单数量(与菜品逻辑一致)
         if (tablewareItem != null) {
+            Integer tablewareLocked = tablewareItem.getLockedQuantity();
+            if (tablewareLocked != null && tablewareLocked > 0) {
+                Integer currentQty = tablewareItem.getQuantity();
+                if (currentQty == null || !currentQty.equals(tablewareLocked)) {
+                    tablewareItem.setQuantity(tablewareLocked);
+                    tablewareItem.setSubtotalAmount(tablewareItem.getUnitPrice().multiply(BigDecimal.valueOf(tablewareLocked)));
+                    hasChanges = true;
+                    log.info("恢复餐具数量为已下单数量, cuisineId={}, oldQuantity={}, orderedQuantity={}",
+                            tablewareItem.getCuisineId(), currentQty, tablewareLocked);
+                }
+            }
             orderedItems.add(tablewareItem);
             orderedCuisineIds.add(tablewareItem.getCuisineId());
             log.info("保留餐具项, cuisineId={}, quantity={}", tablewareItem.getCuisineId(), tablewareItem.getQuantity());
@@ -719,6 +730,11 @@ public class CartServiceImpl implements CartService {
         String userPhone = TokenUtil.getCurrentUserPhone();
 
         if (tablewareItem != null) {
+            // 下单后只能增不能减:已有已下单数量时,用餐人数不能少于已下单数量
+            Integer lockedQuantity = tablewareItem.getLockedQuantity();
+            if (lockedQuantity != null && lockedQuantity > 0 && dinerCount < lockedQuantity) {
+                throw new RuntimeException("餐具数量不能少于已下单数量(" + lockedQuantity + ")");
+            }
             // 更新餐具数量和单价
             tablewareItem.setQuantity(dinerCount);
             tablewareItem.setUnitPrice(tablewareUnitPrice);
@@ -803,6 +819,11 @@ public class CartServiceImpl implements CartService {
             tablewareItem.setAddUserPhone(userPhone);
             items.add(tablewareItem);
         } else {
+            // 下单后只能增不能减:已有已下单数量时,数量不能少于已下单数量
+            Integer lockedQuantity = tablewareItem.getLockedQuantity();
+            if (lockedQuantity != null && lockedQuantity > 0 && quantity < lockedQuantity) {
+                throw new RuntimeException("餐具数量不能少于已下单数量(" + lockedQuantity + ")");
+            }
             // 如果已存在,更新单价(可能门店修改了餐具费)
             tablewareItem.setUnitPrice(tablewareUnitPrice);
         }

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

@@ -40,8 +40,8 @@ public class DiningCouponServiceImpl implements DiningCouponService {
     private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
 
     @Override
-    public R<List<LifeDiscountCouponVo>> getUserCouponList(String authorization, int page, int size, String tabType, Integer type, Integer couponType) {
-        log.info("DiningCouponService.getUserCouponList page={}, size={}, tabType={}, type={}, couponType={}", page, size, tabType, type, couponType);
+    public R<List<LifeDiscountCouponVo>> getUserCouponList(String authorization, int page, int size, String tabType, Integer type, Integer couponType, String storeId) {
+        log.info("DiningCouponService.getUserCouponList page={}, size={}, tabType={}, type={}, couponType={}, storeId={}", page, size, tabType, type, couponType, storeId);
         try {
             // 参数校验
             if (StringUtils.isEmpty(tabType)) {
@@ -55,7 +55,7 @@ public class DiningCouponServiceImpl implements DiningCouponService {
                 size = 10;
             }
             
-            R<List<LifeDiscountCouponVo>> result = alienStoreFeign.getUserCouponList(authorization, tabType, page, size, type, couponType);
+            R<List<LifeDiscountCouponVo>> result = alienStoreFeign.getUserCouponList(authorization, tabType, page, size, type, couponType, storeId);
             if (result == null) {
                 log.error("Feign调用返回null");
                 return R.fail("获取优惠券列表失败:服务返回为空");

+ 70 - 168
alien-dining/src/main/java/shop/alien/dining/service/impl/StoreOrderServiceImpl.java

@@ -112,7 +112,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             }
         }
         
-        // 如果没有新增商品且没有商品数量增加,不允许创建订单
+        // 如果没有新增商品且没有商品数量增加,不允许创建/更新订单(下单后只能增不能减,不支持减量同步)
         if (!hasNewItems && !hasQuantityIncrease) {
             throw new RuntimeException("购物车中没有新增商品或商品数量未增加,无法创建订单");
         }
@@ -123,8 +123,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         BigDecimal discountAmount = dto.getDiscountAmount() != null ? dto.getDiscountAmount() : BigDecimal.ZERO;
         BigDecimal payAmount = dto.getPayAmount() != null ? dto.getPayAmount() : BigDecimal.ZERO;
         
-        // 验证优惠券(可选,couponId 可以为 null)
-        // 注意:订单总金额、优惠金额、实付金额都由前端计算并传入,后端不再验证
+        // 优惠券仅校验是否存在及是否属于该门店,不做金额校验;订单总金额、餐具费、优惠金额、实付金额完全采用前端传参
         LifeDiscountCoupon coupon = null;
         
         if (dto.getCouponId() != null) {
@@ -209,15 +208,15 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
                 Integer addDishCountBefore = orderDetailMapper.selectCount(checkBeforeAddDishWrapper);
                 isFirstAddDish = (addDishCountBefore == null || addDishCountBefore == 0);
                 
-                // 更新订单信息(使用前端计算的金额,但经过后端验证
+                // 更新订单信息(完全采用前端传参,不做金额校验
                 order.setDinerCount(dto.getDinerCount());
                 order.setContactPhone(dto.getContactPhone());
                 order.setTablewareFee(tablewareFee);
-                order.setTotalAmount(totalAmount); // 使用验证后的订单总金额
+                order.setTotalAmount(totalAmount);
                 order.setCouponId(dto.getCouponId());
                 order.setCurrentCouponId(dto.getCouponId());
-                order.setDiscountAmount(discountAmount); // 使用验证后的优惠金额
-                order.setPayAmount(payAmount); // 使用验证后的实付金额
+                order.setDiscountAmount(discountAmount);
+                order.setPayAmount(payAmount);
                 order.setRemark(dto.getRemark());
                 order.setUpdatedUserId(userId);
                 order.setUpdatedTime(now);
@@ -257,11 +256,11 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             order.setPayUserId(userId);
             order.setPayUserPhone(userPhone);
             order.setOrderStatus(0); // 待支付
-            order.setTotalAmount(totalAmount); // 使用验证后的订单总金额
+            order.setTotalAmount(totalAmount);
             order.setCouponId(dto.getCouponId());
-            order.setCurrentCouponId(dto.getCouponId()); // 记录当前使用的优惠券
-            order.setDiscountAmount(discountAmount); // 使用验证后的优惠金额
-            order.setPayAmount(payAmount); // 使用验证后的实付金额
+            order.setCurrentCouponId(dto.getCouponId());
+            order.setDiscountAmount(discountAmount);
+            order.setPayAmount(payAmount);
             
             // 如果immediatePay为0,只创建订单不支付;为1则创建订单并支付
             // 暂时不实现立即支付,由前端调用支付接口
@@ -323,7 +322,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             }
         }
 
-        // 创建订单明细
+        // 创建订单明细(单价、小计来自购物车)
         // 如果是更新订单,需要判断哪些商品是新增的或数量增加的,标记为加餐
         // 餐具的特殊ID(用于标识餐具项)
         final Integer TABLEWARE_CUISINE_ID = -1;
@@ -578,8 +577,24 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         return this.getById(orderId);
     }
 
+    /** 前端只传 0=进行中、3=已完成。进行中对应库表 0待支付+1已支付,已完成对应 3。 */
+    private List<Integer> resolveOrderStatusFilter(Integer orderStatus) {
+        if (orderStatus == null) {
+            return null;
+        }
+        if (orderStatus == 0) {
+            return Arrays.asList(0, 1); // 进行中 = 待支付 + 已支付
+        }
+        if (orderStatus == 3) {
+            return Collections.singletonList(3); // 已完成
+        }
+        return null; // 其他值不参与筛选
+    }
+
     @Override
     public IPage<StoreOrder> getOrderPage(Page<StoreOrder> page, Integer storeId, Integer tableId, Integer orderStatus, String keyword) {
+        // 前端只传 0=进行中、3=已完成。进行中=待支付(0)+已支付(1),已完成=3
+        List<Integer> orderStatusList = resolveOrderStatusFilter(orderStatus);
         LambdaQueryWrapper<StoreOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(StoreOrder::getDeleteFlag, 0);
         if (storeId != null) {
@@ -588,8 +603,8 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         if (tableId != null) {
             wrapper.eq(StoreOrder::getTableId, tableId);
         }
-        if (orderStatus != null) {
-            wrapper.eq(StoreOrder::getOrderStatus, orderStatus);
+        if (orderStatusList != null && !orderStatusList.isEmpty()) {
+            wrapper.in(StoreOrder::getOrderStatus, orderStatusList);
         }
         
         // 搜索功能:按订单编号模糊搜索
@@ -605,7 +620,9 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
 
     @Override
     public IPage<StoreOrderPageVO> getOrderPageWithCuisines(Page<StoreOrder> page, Integer storeId, Integer tableId, Integer orderStatus, String keyword) {
-        log.info("分页查询订单列表(包含菜品信息), storeId={}, tableId={}, orderStatus={}, keyword={}", storeId, tableId, orderStatus, keyword);
+        // 前端只传 0=进行中、3=已完成。进行中=待支付(0)+已支付(1),已完成=3
+        List<Integer> orderStatusList = resolveOrderStatusFilter(orderStatus);
+        log.info("分页查询订单列表(包含菜品信息), storeId={}, tableId={}, orderStatus={}, keyword={}", storeId, tableId, orderStatusList, keyword);
         
         // 限制搜索关键词长度(15字)
         String searchKeyword = null;
@@ -628,8 +645,8 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             if (tableId != null) {
                 orderNoWrapper.eq(StoreOrder::getTableId, tableId);
             }
-            if (orderStatus != null) {
-                orderNoWrapper.eq(StoreOrder::getOrderStatus, orderStatus);
+            if (orderStatusList != null && !orderStatusList.isEmpty()) {
+                orderNoWrapper.in(StoreOrder::getOrderStatus, orderStatusList);
             }
             List<StoreOrder> orderNoMatches = this.list(orderNoWrapper);
             orderNoMatches.forEach(order -> orderIdsSet.add(order.getId()));
@@ -641,7 +658,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
                     .eq(StoreOrderDetail::getDeleteFlag, 0);
             
             // 如果有限制条件,需要先查询符合条件的订单ID
-            if (storeId != null || tableId != null || orderStatus != null) {
+            if (storeId != null || tableId != null || (orderStatusList != null && !orderStatusList.isEmpty())) {
                 LambdaQueryWrapper<StoreOrder> filterWrapper = new LambdaQueryWrapper<>();
                 filterWrapper.eq(StoreOrder::getDeleteFlag, 0)
                         .select(StoreOrder::getId);
@@ -651,8 +668,8 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
                 if (tableId != null) {
                     filterWrapper.eq(StoreOrder::getTableId, tableId);
                 }
-                if (orderStatus != null) {
-                    filterWrapper.eq(StoreOrder::getOrderStatus, orderStatus);
+                if (orderStatusList != null && !orderStatusList.isEmpty()) {
+                    filterWrapper.in(StoreOrder::getOrderStatus, orderStatusList);
                 }
                 List<StoreOrder> filteredOrders = this.list(filterWrapper);
                 Set<Integer> filteredOrderIds = filteredOrders.stream()
@@ -694,8 +711,8 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         if (tableId != null) {
             wrapper.eq(StoreOrder::getTableId, tableId);
         }
-        if (orderStatus != null) {
-            wrapper.eq(StoreOrder::getOrderStatus, orderStatus);
+        if (orderStatusList != null && !orderStatusList.isEmpty()) {
+            wrapper.in(StoreOrder::getOrderStatus, orderStatusList);
         }
         // 如果有关键词搜索,只查询匹配的订单ID
         if (matchingOrderIds != null && !matchingOrderIds.isEmpty()) {
@@ -731,10 +748,23 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         Map<Integer, List<StoreOrderDetail>> detailsMap = allDetails.stream()
                 .collect(Collectors.groupingBy(StoreOrderDetail::getOrderId));
         
-        // 5. 构建返回结果
+        // 5. 批量查询门店名称
+        Set<Integer> storeIds = orders.stream().map(StoreOrder::getStoreId).filter(Objects::nonNull).collect(Collectors.toSet());
+        Map<Integer, String> storeNameMap = new HashMap<>();
+        if (!storeIds.isEmpty()) {
+            List<StoreInfo> storeList = storeInfoMapper.selectBatchIds(storeIds);
+            if (storeList != null) {
+                for (StoreInfo s : storeList) {
+                    storeNameMap.put(s.getId(), s.getStoreName());
+                }
+            }
+        }
+        
+        // 6. 构建返回结果
         List<StoreOrderPageVO> voList = orders.stream().map(order -> {
             StoreOrderPageVO vo = new StoreOrderPageVO();
             vo.setOrder(order);
+            vo.setStoreName(storeNameMap.getOrDefault(order.getStoreId(), ""));
             
             // 获取该订单的菜品列表
             List<StoreOrderDetail> orderDetails = detailsMap.getOrDefault(order.getId(), new ArrayList<>());
@@ -754,7 +784,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             return vo;
         }).collect(Collectors.toList());
         
-        // 6. 构建分页结果
+        // 7. 构建分页结果
         Page<StoreOrderPageVO> resultPage = new Page<>(page.getCurrent(), page.getSize());
         resultPage.setRecords(voList);
         resultPage.setTotal(orderPage.getTotal());
@@ -764,148 +794,6 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
     }
 
     @Override
-    public StoreOrder addDishToOrder(Integer orderId, Integer cuisineId, Integer quantity, String remark) {
-        log.info("加餐, orderId={}, cuisineId={}, quantity={}", orderId, cuisineId, quantity);
-
-        // 验证订单状态
-        StoreOrder order = validateOrderForOperation(orderId, 0, "加餐");
-
-        // 验证菜品
-        StoreCuisine cuisine = storeCuisineMapper.selectById(cuisineId);
-        if (cuisine == null) {
-            throw new RuntimeException("菜品不存在");
-        }
-        if (cuisine.getShelfStatus() != 1) {
-            throw new RuntimeException("菜品已下架");
-        }
-
-        // 获取当前用户信息
-        Object[] userInfo = getCurrentUserInfo();
-        Integer userId = (Integer) userInfo[0];
-        String userPhone = (String) userInfo[1];
-
-        // 在加餐之前,检查订单明细中是否已经有 is_add_dish=1 的记录
-        // 如果没有,说明这是首次加餐(首次订单发生变化)
-        LambdaQueryWrapper<StoreOrderDetail> checkBeforeAddDishWrapper = new LambdaQueryWrapper<>();
-        checkBeforeAddDishWrapper.eq(StoreOrderDetail::getOrderId, orderId)
-                .eq(StoreOrderDetail::getDeleteFlag, 0)
-                .eq(StoreOrderDetail::getIsAddDish, 1);
-        Integer addDishCountBefore = orderDetailMapper.selectCount(checkBeforeAddDishWrapper);
-        boolean isFirstAddDish = (addDishCountBefore == null || addDishCountBefore == 0);
-
-        // 查询是否已有该菜品
-        LambdaQueryWrapper<StoreOrderDetail> detailWrapper = new LambdaQueryWrapper<>();
-        detailWrapper.eq(StoreOrderDetail::getOrderId, orderId);
-        detailWrapper.eq(StoreOrderDetail::getCuisineId, cuisineId);
-        detailWrapper.eq(StoreOrderDetail::getDeleteFlag, 0);
-        StoreOrderDetail existingDetail = orderDetailMapper.selectOne(detailWrapper);
-
-        Date now = new Date();
-        Integer quantityBefore = 0; // 加餐前的数量(用于记录变更日志)
-        BigDecimal addAmount; // 新增的金额
-        
-        if (existingDetail != null) {
-            // 记录加餐前的数量
-            quantityBefore = existingDetail.getQuantity();
-            // 计算新增金额(单价 * 新增数量)
-            addAmount = existingDetail.getUnitPrice().multiply(BigDecimal.valueOf(quantity));
-            // 更新数量
-            existingDetail.setQuantity(existingDetail.getQuantity() + quantity);
-            existingDetail.setSubtotalAmount(existingDetail.getUnitPrice()
-                    .multiply(BigDecimal.valueOf(existingDetail.getQuantity())));
-            if (StringUtils.hasText(remark)) {
-                existingDetail.setRemark(remark);
-            }
-            // 如果之前不是加餐,现在标记为加餐
-            if (existingDetail.getIsAddDish() == null || existingDetail.getIsAddDish() == 0) {
-                existingDetail.setIsAddDish(1); // 标记为加餐
-                existingDetail.setAddDishTime(now);
-            }
-            existingDetail.setUpdatedTime(now);
-            existingDetail.setUpdatedUserId(userId);
-            orderDetailMapper.updateById(existingDetail);
-        } else {
-            // 添加新菜品(加餐)
-            quantityBefore = 0; // 新菜品,加餐前数量为0
-            // 计算新增金额(单价 * 数量)
-            addAmount = cuisine.getTotalPrice().multiply(BigDecimal.valueOf(quantity));
-            StoreOrderDetail detail = new StoreOrderDetail();
-            detail.setOrderId(orderId);
-            detail.setOrderNo(order.getOrderNo());
-            detail.setCuisineId(cuisine.getId());
-            detail.setCuisineName(cuisine.getName());
-            
-            // 设置菜品类型:如果为null,默认为1(单品)
-            Integer cuisineType = cuisine.getCuisineType();
-            if (cuisineType == null) {
-                cuisineType = 1; // 默认为单品
-                log.warn("菜品类型为null,使用默认值1, cuisineId={}", cuisine.getId());
-            }
-            detail.setCuisineType(cuisineType);
-            
-            detail.setCuisineImage(cuisine.getImages());
-            detail.setUnitPrice(cuisine.getTotalPrice());
-            detail.setQuantity(quantity);
-            detail.setSubtotalAmount(addAmount);
-            detail.setAddUserId(userId);
-            detail.setAddUserPhone(userPhone);
-            detail.setIsAddDish(1); // 标记为加餐
-            detail.setAddDishTime(now);
-            detail.setRemark(remark);
-            detail.setCreatedUserId(userId);
-            detail.setCreatedTime(now);
-            detail.setUpdatedTime(now);
-            orderDetailMapper.insert(detail);
-        }
-
-        // 增量计算订单总金额(避免重新查询所有订单明细)
-        BigDecimal newTotalAmount = order.getTotalAmount().add(addAmount);
-
-        // 加餐时只更新订单总金额,不计算优惠金额和实付金额(因为可能还会继续加菜,支付时再统一计算)
-        // 如果订单使用了优惠券,不验证最低消费(因为可能还会继续加菜,支付时再验证)
-        order.setTotalAmount(newTotalAmount);
-        // order.setDiscountAmount 和 order.setPayAmount 保持不变,支付时再计算
-        order.setUpdatedTime(new Date());
-        order.setUpdatedUserId(userId);
-        this.updateById(order);
-
-        // 记录加餐变更日志
-        List<shop.alien.entity.store.dto.CartItemDTO> addDishItems = new ArrayList<>();
-        shop.alien.entity.store.dto.CartItemDTO addDishItem = new shop.alien.entity.store.dto.CartItemDTO();
-        addDishItem.setCuisineId(cuisine.getId());
-        addDishItem.setCuisineName(cuisine.getName());
-        addDishItem.setCuisineType(cuisine.getCuisineType());
-        addDishItem.setCuisineImage(cuisine.getImages());
-        addDishItem.setUnitPrice(cuisine.getTotalPrice());
-        // 设置当前数量(加餐后的总数量)
-        addDishItem.setQuantity(quantityBefore + quantity);
-        // 设置已下单数量(加餐前的数量,用于计算变化)
-        addDishItem.setLockedQuantity(quantityBefore);
-        addDishItem.setSubtotalAmount(cuisine.getTotalPrice().multiply(BigDecimal.valueOf(quantity)));
-        addDishItem.setRemark(remark);
-        addDishItems.add(addDishItem);
-        
-        recordOrderChangeLog(orderId, order.getOrderNo(), addDishItems, 2, now, userId, userPhone);
-
-        // 如果是首次加餐(首次订单发生变化),更新餐桌状态为加餐状态(3)
-        if (isFirstAddDish) {
-            StoreTable table = storeTableMapper.selectById(order.getTableId());
-            if (table != null) {
-                table.setStatus(3); // 加餐状态
-                table.setUpdatedTime(new Date());
-                if (userId != null) {
-                    table.setUpdatedUserId(userId);
-                }
-                storeTableMapper.updateById(table);
-                log.info("首次加餐,更新餐桌状态为加餐状态, tableId={}, orderId={}", table.getId(), orderId);
-            }
-        }
-
-        log.info("加餐成功, orderId={}", orderId);
-        return order;
-    }
-
-    @Override
     public boolean completeOrder(Integer orderId) {
         log.info("完成订单, orderId={}", orderId);
 
@@ -1387,10 +1275,23 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         Map<Integer, List<StoreOrderDetail>> detailsMap = allDetails.stream()
                 .collect(Collectors.groupingBy(StoreOrderDetail::getOrderId));
 
-        // 5. 构建返回结果
+        // 5. 批量查询门店名称
+        Set<Integer> storeIds = orders.stream().map(StoreOrder::getStoreId).filter(Objects::nonNull).collect(Collectors.toSet());
+        Map<Integer, String> storeNameMap = new HashMap<>();
+        if (!storeIds.isEmpty()) {
+            List<StoreInfo> storeList = storeInfoMapper.selectBatchIds(storeIds);
+            if (storeList != null) {
+                for (StoreInfo s : storeList) {
+                    storeNameMap.put(s.getId(), s.getStoreName());
+                }
+            }
+        }
+
+        // 6. 构建返回结果
         List<StoreOrderPageVO> voList = orders.stream().map(order -> {
             StoreOrderPageVO vo = new StoreOrderPageVO();
             vo.setOrder(order);
+            vo.setStoreName(storeNameMap.getOrDefault(order.getStoreId(), ""));
 
             // 获取该订单的菜品列表
             List<StoreOrderDetail> orderDetails = detailsMap.getOrDefault(order.getId(), new ArrayList<>());
@@ -1410,7 +1311,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             return vo;
         }).collect(Collectors.toList());
 
-        // 6. 构建分页结果
+        // 7. 构建分页结果
         Page<StoreOrderPageVO> resultPage = new Page<>(page.getCurrent(), page.getSize());
         resultPage.setRecords(voList);
         resultPage.setTotal(orderPage.getTotal());
@@ -1563,6 +1464,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             batchVO.setOperationTime(firstLog.getOperationTime());
             batchVO.setOperatorUserId(firstLog.getOperatorUserId());
             batchVO.setOperatorUserPhone(firstLog.getOperatorUserPhone());
+            batchVO.setRemark(firstLog.getRemark());
             
             // 计算批次统计信息
             Integer totalQuantityChange = batchLogs.stream()

+ 1 - 1
alien-dining/src/main/java/shop/alien/dining/strategy/payment/impl/WeChatPaymentMininProgramStrategyImpl.java

@@ -111,7 +111,7 @@ public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
         }
         LambdaQueryWrapper<StorePaymentConfig> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(StorePaymentConfig::getWechatPayPublicKeyId, wechatPayPublicKeyId);
-        return storePaymentConfigMapper.selectOne(wrapper);
+        return storePaymentConfigMapper.selectList(wrapper).getFirst();
     }
 
     /**

+ 0 - 33
alien-entity/src/main/java/shop/alien/entity/store/dto/AddDishDTO.java

@@ -1,33 +0,0 @@
-package shop.alien.entity.store.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotNull;
-
-/**
- * 加餐DTO
- *
- * @author system
- * @since 2025-01-XX
- */
-@Data
-@ApiModel(value = "AddDishDTO对象", description = "加餐")
-public class AddDishDTO {
-
-    @ApiModelProperty(value = "订单ID", required = true)
-    @NotNull(message = "订单ID不能为空")
-    private Integer orderId;
-
-    @ApiModelProperty(value = "菜品ID", required = true)
-    @NotNull(message = "菜品ID不能为空")
-    private Integer cuisineId;
-
-    @ApiModelProperty(value = "数量", required = true)
-    @NotNull(message = "数量不能为空")
-    private Integer quantity;
-
-    @ApiModelProperty(value = "备注")
-    private String remark;
-}

+ 6 - 6
alien-entity/src/main/java/shop/alien/entity/store/dto/CreateOrderDTO.java

@@ -7,13 +7,13 @@ import lombok.Data;
 import javax.validation.constraints.NotNull;
 
 /**
- * 创建订单DTO
+ * 创建订单DTO(订单总金额、餐具费、优惠金额、实付金额完全采用前端传参,不做后台金额校验)
  *
  * @author system
  * @since 2025-01-XX
  */
 @Data
-@ApiModel(value = "CreateOrderDTO对象", description = "创建订单")
+@ApiModel(value = "CreateOrderDTO对象", description = "创建订单,金额完全采用前端传参")
 public class CreateOrderDTO {
 
     @ApiModelProperty(value = "桌号ID", required = true)
@@ -26,16 +26,16 @@ public class CreateOrderDTO {
     @ApiModelProperty(value = "优惠券ID(可选,不选择优惠券时传 null 或不传此字段)")
     private Integer couponId;
 
-    @ApiModelProperty(value = "订单总金额(由前端计算,菜品总价,不含餐具费和优惠金额)")
+    @ApiModelProperty(value = "订单总金额(前端传参,不做后台校验)")
     private java.math.BigDecimal totalAmount;
 
-    @ApiModelProperty(value = "餐具费(由前端计算,基于门店餐具费单价 × 就餐人数)")
+    @ApiModelProperty(value = "餐具费(前端传参,不做后台校验)")
     private java.math.BigDecimal tablewareFee;
 
-    @ApiModelProperty(value = "优惠金额(由前端计算,如果使用优惠券则必传,不使用优惠券时传 0 或不传)")
+    @ApiModelProperty(value = "优惠金额(前端传参,不做后台校验)")
     private java.math.BigDecimal discountAmount;
 
-    @ApiModelProperty(value = "实付金额(由前端计算,订单总金额 + 餐具费 - 优惠金额)")
+    @ApiModelProperty(value = "实付金额(前端传参,不做后台校验)")
     private java.math.BigDecimal payAmount;
 
     @ApiModelProperty(value = "联系电话")

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderChangeLogBatchVO.java

@@ -36,6 +36,9 @@ public class OrderChangeLogBatchVO {
     @ApiModelProperty(value = "操作人手机号")
     private String operatorUserPhone;
 
+    @ApiModelProperty(value = "备注(该批次对应的备注,如下单/加餐时的备注)")
+    private String remark;
+
     @ApiModelProperty(value = "该批次商品数量变化总和")
     private Integer totalQuantityChange;
 

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOrderPageVO.java

@@ -19,6 +19,9 @@ public class StoreOrderPageVO {
     @ApiModelProperty(value = "订单信息")
     private shop.alien.entity.store.StoreOrder order;
 
+    @ApiModelProperty(value = "门店名称")
+    private String storeName;
+
     @ApiModelProperty(value = "订单菜品列表")
     private List<OrderCuisineItemVO> cuisineItems;
 }

+ 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()
+        );
+    }
 }
 

+ 4 - 4
alien-store/src/main/java/shop/alien/store/config/WebSocketConfig.java

@@ -36,14 +36,14 @@ public class WebSocketConfig {
             Map<String, Object> userProperties = sec.getUserProperties();
             Map<String, List<String>> headers = request.getHeaders();
             
-            // 获取User-Agent
+            // 获取User-Agent(ConcurrentHashMap 不允许 null 值,使用空字符串代替)
             List<String> userAgentList = headers.get("User-Agent");
-            String userAgent = (userAgentList != null && !userAgentList.isEmpty()) ? userAgentList.get(0) : null;
+            String userAgent = (userAgentList != null && !userAgentList.isEmpty()) ? userAgentList.get(0) : "";
             userProperties.put("userAgent", userAgent);
             
-            // 获取IP地址(从请求头中获取)
+            // 获取IP地址(从请求头中获取,null 时使用空字符串
             String ipAddress = getIpAddress(headers);
-            userProperties.put("ipAddress", ipAddress);
+            userProperties.put("ipAddress", ipAddress != null ? ipAddress : "");
         }
         
         /**

+ 7 - 5
alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponController.java

@@ -190,14 +190,16 @@ public class LifeDiscountCouponController {
             @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "tabType", value = "分页类型(0:全部(未使用),1:即将过期,2:已使用,3:已过期)", dataType = "String", paramType = "query", required = true),
             @ApiImplicitParam(name = "type", value = "券类型(不传:优惠券+代金券都返回,1:仅优惠券查 life_discount_coupon,4:仅代金券查 life_coupon)", dataType = "Integer", paramType = "query", required = false),
-            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)", dataType = "Integer", paramType = "query", required = false)
+            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "storeId", value = "商铺ID,可为空,传则仅返回该商铺的优惠券", dataType = "String", paramType = "query", required = false)
     })
     public R<List<LifeDiscountCouponVo>> getUserCouponList(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
                                                            @RequestParam(value = "tabType") String tabType,
                                                            @RequestParam(defaultValue = "1") int page,
                                                            @RequestParam(defaultValue = "10") int size,
                                                            @RequestParam(required = false) Integer type,
-                                                           @RequestParam(value = "couponType", required = false) Integer couponType) {
+                                                           @RequestParam(value = "couponType", required = false) Integer couponType,
+                                                           @RequestParam(value = "storeId", required = false) String storeId) {
         try {
             // 参数校验
             if (StringUtils.isEmpty(tabType)) {
@@ -216,11 +218,11 @@ public class LifeDiscountCouponController {
                 return R.fail("优惠券类型参数错误,必须为1(满减券)或2(折扣券)");
             }
             
-            log.info("LifeDiscountCouponController.getUserCouponList?userId={}, tabType={}, page={}, size={}, type={}, couponType={}", 
-                    userLoginInfo.getUserId(), tabType, page, size, type, couponType);
+            log.info("LifeDiscountCouponController.getUserCouponList?userId={}, tabType={}, page={}, size={}, type={}, couponType={}, storeId={}", 
+                    userLoginInfo.getUserId(), tabType, page, size, type, couponType, storeId);
             
             List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getUserCouponList(
-                    userLoginInfo, page, size, tabType, type, couponType);
+                    userLoginInfo, page, size, tabType, type, couponType, storeId);
             return R.data(storeCouponList);
         } catch (IllegalArgumentException e) {
             log.error("LifeDiscountCouponController.getUserCouponList 参数错误, userId={}, error={}", 

+ 69 - 20
alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java

@@ -16,9 +16,13 @@ import shop.alien.store.service.StoreOfficialAlbumService;
 import shop.alien.store.util.GroupConstant;
 import shop.alien.store.util.ai.AiContentModerationUtil;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.*;
 import java.util.stream.Collectors;
 
+import org.springframework.beans.factory.annotation.Qualifier;
+
 /**
  * 二期-门店图片Controller
  *
@@ -37,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)
@@ -84,37 +90,44 @@ public class StoreImgController {
         if(storeImgInfoVo.getStoreImgList().isEmpty()){
             return R.fail("图片列表为空,请重新上传图片");
         }
-        // TODO 审核图片是否违规
+        Integer imgType = storeImgInfoVo.getImgType();
+        Integer storeId = storeImgInfoVo.getStoreId();
         List<String> imageUrls = storeImgList.stream()
                 .map(StoreImg::getImgUrl)
                 .collect(Collectors.toList());
-        AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(null, 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);
@@ -138,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);
                     }
@@ -334,6 +346,43 @@ public class StoreImgController {
         return R.data(storeImgService.getByCover(storeId, imgType));
     }
 
-
+    /**
+     * 使用共享线程池并行审核,每张图片单独审核,线程数不超过 IMG_AUDIT_MAX_PARALLEL
+     *
+     * @param imageUrls 图片URL列表
+     * @return 合并后的审核结果,任一张不通过则整体不通过
+     */
+    private AiContentModerationUtil.AuditResult auditImagesInParallel(List<String> imageUrls) {
+        if (imageUrls == null || imageUrls.isEmpty()) {
+            return new AiContentModerationUtil.AuditResult(true, null);
+        }
+        try {
+            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 (CompletableFuture<AiContentModerationUtil.AuditResult> future : futures) {
+                AiContentModerationUtil.AuditResult result = future.get(30, TimeUnit.SECONDS);
+                if (!result.isPassed() && firstFailureReason == null) {
+                    firstFailureReason = result.getFailureReason();
+                }
+            }
+            return firstFailureReason != null
+                    ? new AiContentModerationUtil.AuditResult(false, firstFailureReason)
+                    : new AiContentModerationUtil.AuditResult(true, null);
+        } catch (TimeoutException e) {
+            log.error("图片审核超时", e);
+            return new AiContentModerationUtil.AuditResult(false, "审核超时");
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("图片审核被中断", e);
+            return new AiContentModerationUtil.AuditResult(false, "审核被中断");
+        } catch (ExecutionException e) {
+            log.error("图片审核异常", e);
+            return new AiContentModerationUtil.AuditResult(false, "审核异常");
+        }
+    }
 
 }

+ 2 - 2
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -1844,8 +1844,8 @@ public class StoreInfoController {
         }
         String section = businessSection.trim();
         boolean isFood = "3".equals(section) || "美食".equals(section);
-        boolean isLeisure = "2".equals(section) || "休闲娱乐".equals(section);
-        boolean isLifeService = "1".equals(section) || "生活服务".equals(section);
+        boolean isLeisure = "1".equals(section) || "休闲娱乐".equals(section);
+        boolean isLifeService = "2".equals(section) || "生活服务".equals(section);
         if (!isFood && !isLeisure && !isLifeService) {
             return R.fail("未知的经营板块: " + businessSection);
         }

+ 2 - 1
alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponService.java

@@ -67,8 +67,9 @@ public interface LifeDiscountCouponService extends IService<LifeDiscountCoupon>
      * 获取该用户优惠券列表
      * @param type 不传:优惠券+代金券都返回;1:仅优惠券(查 life_discount_coupon);4:仅代金券(查 life_coupon)
      * @param couponType 优惠券类型:1=满减券,2=折扣券,null=全部优惠券(仅当type不为4时有效)
+     * @param storeId 商铺ID,可为空,传则仅返回该商铺的优惠券
      */
-    List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType, Integer type, Integer couponType);
+    List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType, Integer type, Integer couponType, String storeId);
 
     /**
      * 获取所有优惠券列表(分页)

+ 20 - 9
alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponServiceImpl.java

@@ -713,10 +713,11 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
      * @param tabType 分页类型:0-全部(未使用),1-即将过期,2-已使用,3-已过期
      * @param type 券类型:1-仅优惠券,4-仅代金券,null-全部
      * @param couponType 优惠券类型:1-满减券,2-折扣券,null-全部(仅当type不为4时有效)
+     * @param storeId 商铺ID,可为空,传则仅返回该商铺的优惠券
      * @return 优惠券列表
      */
     @Override
-    public List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType, Integer type, Integer couponType) {
+    public List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType, Integer type, Integer couponType, String storeId) {
         // 参数校验
         if (userLoginInfo == null || StringUtils.isEmpty(userLoginInfo.getUserId())) {
             throw new IllegalArgumentException("用户信息不能为空");
@@ -781,7 +782,7 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
         
         // type=4:仅代金券,查询 life_coupon 表
         if (type != null && type == 4) {
-            return processVoucherCoupons(records, zoneId, localNow);
+            return processVoucherCoupons(records, zoneId, localNow, storeId);
         }
 
         // type=1 或 type=null:优惠券(或混合),查询 life_discount_coupon
@@ -797,6 +798,10 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
         if (couponType != null) {
             couponQueryWrapper.eq(LifeDiscountCoupon::getCouponType, couponType);
         }
+        // 商铺ID检索:传则仅返回该商铺的优惠券
+        if (!StringUtils.isEmpty(storeId)) {
+            couponQueryWrapper.eq(LifeDiscountCoupon::getStoreId, storeId);
+        }
         List<LifeDiscountCoupon> lifeDiscountCoupons = lifeDiscountCouponMapper.selectList(couponQueryWrapper);
         
         // 如果type=1且没有查询到优惠券,直接返回
@@ -821,8 +826,11 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
                     .filter(Objects::nonNull)
                     .collect(Collectors.toList());
             if (!voucherIdList.isEmpty()) {
-                lifeCouponsForMerge = lifeCouponMapper.selectList(
-                        new LambdaQueryWrapper<LifeCoupon>().in(LifeCoupon::getId, voucherIdList));
+                LambdaQueryWrapper<LifeCoupon> voucherWrapper = new LambdaQueryWrapper<LifeCoupon>().in(LifeCoupon::getId, voucherIdList);
+                if (!StringUtils.isEmpty(storeId)) {
+                    voucherWrapper.eq(LifeCoupon::getStoreId, storeId);
+                }
+                lifeCouponsForMerge = lifeCouponMapper.selectList(voucherWrapper);
                 List<String> voucherStoreIds = lifeCouponsForMerge.stream()
                         .map(LifeCoupon::getStoreId)
                         .filter(s -> !StringUtils.isEmpty(s))
@@ -877,8 +885,8 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
     /**
      * 处理代金券列表(type=4时使用)
      */
-    private List<LifeDiscountCouponVo> processVoucherCoupons(List<LifeDiscountCouponUser> records, 
-                                                               ZoneId zoneId, LocalDate localNow) {
+    private List<LifeDiscountCouponVo> processVoucherCoupons(List<LifeDiscountCouponUser> records,
+                                                               ZoneId zoneId, LocalDate localNow, String storeId) {
         List<LifeDiscountCouponVo> result = new ArrayList<>();
         List<String> voucherIdList = records.stream()
                 .map(LifeDiscountCouponUser::getVoucherId)
@@ -887,9 +895,12 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
         if (voucherIdList.isEmpty()) {
             return result;
         }
-        
-        List<LifeCoupon> lifeCoupons = lifeCouponMapper.selectList(
-                new LambdaQueryWrapper<LifeCoupon>().in(LifeCoupon::getId, voucherIdList));
+
+        LambdaQueryWrapper<LifeCoupon> voucherWrapper = new LambdaQueryWrapper<LifeCoupon>().in(LifeCoupon::getId, voucherIdList);
+        if (!StringUtils.isEmpty(storeId)) {
+            voucherWrapper.eq(LifeCoupon::getStoreId, storeId);
+        }
+        List<LifeCoupon> lifeCoupons = lifeCouponMapper.selectList(voucherWrapper);
         List<String> storeIdList = lifeCoupons.stream()
                 .map(LifeCoupon::getStoreId)
                 .filter(s -> !StringUtils.isEmpty(s))