diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java index 7484012..00d3ce6 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java @@ -67,8 +67,14 @@ public class AppTradeOrderController { @PostMapping("/judgePurchase") @Operation(summary = "判断是否为限购商品") public CommonResult judgePurchase(@RequestBody @Valid AppTradeOrderJudgePurchaseRespVo settlementReqVO) { - List skuList = settlementReqVO.getItems().stream().map(AppTradeOrderJudgePurchaseRespVo.Item::getSkuId).collect(Collectors.toList()); - tradeOrderUpdateService.isSkuPurchaseLimit(getLoginUserId(), skuList); + List items = settlementReqVO.getItems().stream().map(item -> { + AppTradeOrderSettlementReqVO.Item settlementItem = new AppTradeOrderSettlementReqVO.Item(); + settlementItem.setSkuId(item.getSkuId()); + settlementItem.setCount(item.getCount()); + settlementItem.setCartId(item.getCartId()); + return settlementItem; + }).collect(Collectors.toList()); + tradeOrderUpdateService.validatePurchaseLimit(getLoginUserId(), items); return success(true); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java index 4c829ee..5304729 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java @@ -95,7 +95,7 @@ public interface TradeOrderItemMapper extends BaseMapperX { } /** - * 查询用户在指定时间范围内购买某个 SPU 的总数量(仅统计已支付订单) + * 查询用户在指定时间范围内购买某个 SPU 的总数量(统计未取消订单,含待支付) * * @param userId 用户 ID * @param spuId 商品 SPU ID @@ -109,7 +109,7 @@ public interface TradeOrderItemMapper extends BaseMapperX { .eq(TradeOrderItemDO::getSpuId, spuId) .ge(TradeOrderItemDO::getCreateTime, startTime) .leftJoin(TradeOrderDO.class, TradeOrderDO::getId, TradeOrderItemDO::getOrderId) - .eq(TradeOrderDO::getPayStatus, true); + .ne(TradeOrderDO::getStatus, TradeOrderStatusEnum.CANCELED.getStatus()); List> result = selectMaps(query); if (CollUtil.isEmpty(result)) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/RedisKeyConstants.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/RedisKeyConstants.java index eff48e5..3a2c009 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/RedisKeyConstants.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/redis/RedisKeyConstants.java @@ -23,4 +23,11 @@ public interface RedisKeyConstants { */ String EXPRESS_TRACK = "express_track"; + /** + * 售货机限购校验分布式锁 + * + * KEY 格式:trade_purchase_limit_lock:{userId}:{spuId} + */ + String TRADE_PURCHASE_LIMIT_LOCK = "trade_purchase_limit_lock:"; + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java index a3badfb..0275bd4 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java @@ -23,6 +23,11 @@ public interface TradeOrderUpdateService { void isSkuPurchaseLimit(Long payUserId, List skuIdList); + /** + * 校验限购(含 SKU 购买数量) + */ + void validatePurchaseLimit(Long userId, List items); + /** * 获得订单结算信息 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 4c3a595..019c0bf 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -30,7 +30,6 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; -import cn.iocoder.yudao.module.product.enums.spu.PurchaseLimitTypeEnum; import cn.iocoder.yudao.module.promotion.api.giftvoucher.GiftVoucherApi; import cn.iocoder.yudao.module.promotion.api.giftvoucher.dto.GiftVoucherRespDTO; import cn.iocoder.yudao.module.system.api.social.SocialClientApi; @@ -79,7 +78,6 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import javax.validation.constraints.NotNull; import java.time.LocalDateTime; -import java.time.temporal.TemporalAdjusters; import java.util.*; import java.util.stream.Collectors; @@ -147,6 +145,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Resource private AdminUserApi adminUserApi; + @Resource + private TradePurchaseLimitService tradePurchaseLimitService; + // =================== Order =================== /** @@ -154,12 +155,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { */ @Override public void isSkuPurchaseLimit(Long payUserId, List skuIdList) { + if (CollUtil.isEmpty(skuIdList)) { + return; + } List skuList = productSkuApi.getSkuList(skuIdList); if (CollUtil.isEmpty(skuList)) { return; } - - // 本次请求每个 SPU 的购买数量(按 skuList 聚合;结算页通常每条 sku 一项) Map currentPurchaseBySpu = new HashMap<>(); for (ProductSkuRespDTO sku : skuList) { if (sku == null || sku.getSpuId() == null) { @@ -167,56 +169,36 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { } currentPurchaseBySpu.merge(sku.getSpuId(), 1, Integer::sum); } - if (currentPurchaseBySpu.isEmpty()) { - return; - } - - List spuList = productSpuApi.getSpuList(new ArrayList<>(currentPurchaseBySpu.keySet())); - if (CollUtil.isEmpty(spuList)) { - return; - } - - for (ProductSpuRespDTO spu : spuList) { - if (spu == null || spu.getId() == null) { - continue; - } - Integer limitType = spu.getPurchaseLimitType(); - Integer limitCount = spu.getPurchaseLimitCount(); - if (limitType == null || Objects.equals(limitType, PurchaseLimitTypeEnum.NONE.getType()) - || limitCount == null || limitCount <= 0) { - continue; // 不限购 - } - - PurchaseLimitTypeEnum limitTypeEnum = PurchaseLimitTypeEnum.valueOf(limitType); - if (limitTypeEnum == null) { - continue; - } - LocalDateTime startTime = getStartTimeByLimitType(limitTypeEnum); - if (startTime == null) { - startTime = LocalDateTime.of(2000, 1, 1, 0, 0); - } - - int historyPurchaseCount = tradeOrderItemMapper.selectPurchaseCountByUserAndSpu(payUserId, spu.getId(), startTime); - int currentPurchaseCount = currentPurchaseBySpu.getOrDefault(spu.getId(), 0); - if (historyPurchaseCount + currentPurchaseCount > limitCount) { - throw exception(ORDER_CREATE_PURCHASE_LIMIT_EXCEED, spu.getName(), limitTypeEnum.getName(), limitCount); - } - } + tradePurchaseLimitService.validate(payUserId, currentPurchaseBySpu); } - private LocalDateTime getStartTimeByLimitType(PurchaseLimitTypeEnum limitType) { - LocalDateTime now = LocalDateTime.now(); - switch (limitType) { - case DAILY: - return now.toLocalDate().atStartOfDay(); - case WEEKLY: - return now.with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY)) - .toLocalDate().atStartOfDay(); - case LIFETIME: - return null; - default: - return null; + /** + * 结算阶段限购预校验(含购买数量) + */ + @Override + public void validatePurchaseLimit(Long userId, List items) { + validatePurchaseLimitBySettlementItems(userId, items); + } + + private void validatePurchaseLimitBySettlementItems(Long userId, List items) { + if (CollUtil.isEmpty(items)) { + return; } + List skuIds = convertList(items, AppTradeOrderSettlementReqVO.Item::getSkuId); + List skuList = productSkuApi.getSkuList(skuIds); + Map skuMap = convertMap(skuList, ProductSkuRespDTO::getId); + Map currentPurchaseBySpu = new HashMap<>(); + for (AppTradeOrderSettlementReqVO.Item item : items) { + if (item == null || item.getSkuId() == null || item.getCount() == null) { + continue; + } + ProductSkuRespDTO sku = skuMap.get(item.getSkuId()); + if (sku == null || sku.getSpuId() == null) { + continue; + } + currentPurchaseBySpu.merge(sku.getSpuId(), item.getCount(), Integer::sum); + } + tradePurchaseLimitService.validate(userId, currentPurchaseBySpu); } @Override @@ -228,8 +210,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { } // TODO 新需求 查看商品有没有限购 - List skuList = settlementReqVO.getItems().stream().map(AppTradeOrderSettlementReqVO.Item::getSkuId).collect(Collectors.toList()); - isSkuPurchaseLimit(userId, skuList); + validatePurchaseLimitBySettlementItems(userId, settlementReqVO.getItems()); // 2. 计算价格 TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, settlementReqVO); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradePurchaseLimitService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradePurchaseLimitService.java new file mode 100644 index 0000000..b5b304a --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradePurchaseLimitService.java @@ -0,0 +1,163 @@ +package cn.iocoder.yudao.module.trade.service.order; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; +import cn.iocoder.yudao.module.product.enums.spu.PurchaseLimitTypeEnum; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; +import cn.iocoder.yudao.module.trade.dal.redis.RedisKeyConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_PURCHASE_LIMIT_EXCEED; + +/** + * 售货机商品限购校验(含并发保护) + */ +@Service +@Slf4j +public class TradePurchaseLimitService { + + private static final Duration LOCK_TIMEOUT = Duration.ofSeconds(10); + + @Resource + private ProductSpuApi productSpuApi; + @Resource + private TradeOrderItemMapper tradeOrderItemMapper; + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 按订单项校验限购(创建订单时使用) + */ + public void validateByOrderItems(Long userId, List orderItems) { + if (userId == null || CollUtil.isEmpty(orderItems)) { + return; + } + Map currentPurchaseBySpu = new HashMap<>(); + for (TradeOrderItemDO item : orderItems) { + if (item == null || item.getSpuId() == null || item.getCount() == null) { + continue; + } + currentPurchaseBySpu.merge(item.getSpuId(), item.getCount(), Integer::sum); + } + validate(userId, currentPurchaseBySpu); + } + + /** + * 按 SPU 购买数量校验限购 + */ + public void validate(Long userId, Map currentPurchaseBySpu) { + if (userId == null || CollUtil.isEmpty(currentPurchaseBySpu)) { + return; + } + + List spuList = productSpuApi.getSpuList(new ArrayList<>(currentPurchaseBySpu.keySet())); + if (CollUtil.isEmpty(spuList)) { + return; + } + + List limitedSpus = new ArrayList<>(); + for (ProductSpuRespDTO spu : spuList) { + if (spu == null || spu.getId() == null) { + continue; + } + if (spu.getPurchaseLimitType() != null + && !Objects.equals(spu.getPurchaseLimitType(), PurchaseLimitTypeEnum.NONE.getType()) + && spu.getPurchaseLimitCount() != null + && spu.getPurchaseLimitCount() > 0) { + limitedSpus.add(spu); + } + } + if (CollUtil.isEmpty(limitedSpus)) { + return; + } + + // 按 SPU ID 排序后加锁,避免并发下单穿透限购 + List lockSpuIds = new ArrayList<>(); + for (ProductSpuRespDTO spu : limitedSpus) { + lockSpuIds.add(spu.getId()); + } + Collections.sort(lockSpuIds); + + List acquiredLockKeys = new ArrayList<>(lockSpuIds.size()); + try { + for (Long spuId : lockSpuIds) { + String lockKey = RedisKeyConstants.TRADE_PURCHASE_LIMIT_LOCK + userId + ":" + spuId; + Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_TIMEOUT); + if (!Boolean.TRUE.equals(locked)) { + ProductSpuRespDTO lockedSpu = limitedSpus.stream() + .filter(spu -> Objects.equals(spu.getId(), spuId)) + .findFirst().orElse(null); + String spuName = lockedSpu != null ? lockedSpu.getName() : "商品"; + Integer limitCount = lockedSpu != null ? lockedSpu.getPurchaseLimitCount() : 1; + PurchaseLimitTypeEnum limitTypeEnum = lockedSpu != null + ? PurchaseLimitTypeEnum.valueOf(lockedSpu.getPurchaseLimitType()) : null; + String limitDesc = limitTypeEnum != null ? limitTypeEnum.getName() : "限购"; + throw exception(ORDER_CREATE_PURCHASE_LIMIT_EXCEED, spuName, limitDesc, limitCount); + } + acquiredLockKeys.add(lockKey); + } + doValidate(userId, limitedSpus, currentPurchaseBySpu); + } finally { + for (String lockKey : acquiredLockKeys) { + stringRedisTemplate.delete(lockKey); + } + } + } + + private void doValidate(Long userId, List limitedSpus, + Map currentPurchaseBySpu) { + for (ProductSpuRespDTO spu : limitedSpus) { + int currentPurchaseCount = currentPurchaseBySpu.getOrDefault(spu.getId(), 0); + int historyPurchaseCount = getHistoryPurchaseCount(userId, spu.getId(), spu.getPurchaseLimitType()); + if (historyPurchaseCount + currentPurchaseCount > spu.getPurchaseLimitCount()) { + PurchaseLimitTypeEnum limitTypeEnum = PurchaseLimitTypeEnum.valueOf(spu.getPurchaseLimitType()); + String limitDesc = limitTypeEnum != null ? limitTypeEnum.getName() : "限购"; + throw exception(ORDER_CREATE_PURCHASE_LIMIT_EXCEED, spu.getName(), limitDesc, spu.getPurchaseLimitCount()); + } + } + } + + private int getHistoryPurchaseCount(Long userId, Long spuId, Integer limitType) { + PurchaseLimitTypeEnum limitTypeEnum = PurchaseLimitTypeEnum.valueOf(limitType); + if (limitTypeEnum == null || limitTypeEnum == PurchaseLimitTypeEnum.NONE) { + return 0; + } + LocalDateTime startTime = getStartTimeByLimitType(limitTypeEnum); + if (startTime == null) { + startTime = LocalDateTime.of(2000, 1, 1, 0, 0); + } + return tradeOrderItemMapper.selectPurchaseCountByUserAndSpu(userId, spuId, startTime); + } + + private LocalDateTime getStartTimeByLimitType(PurchaseLimitTypeEnum limitType) { + LocalDateTime now = LocalDateTime.now(); + switch (limitType) { + case DAILY: + return now.toLocalDate().atStartOfDay(); + case WEEKLY: + return now.with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY)) + .toLocalDate().atStartOfDay(); + case LIFETIME: + return null; + default: + return null; + } + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradePurchaseLimitOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradePurchaseLimitOrderHandler.java index 1442cad..62a09f4 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradePurchaseLimitOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradePurchaseLimitOrderHandler.java @@ -1,156 +1,42 @@ package cn.iocoder.yudao.module.trade.service.order.handler; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; -import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; -import cn.iocoder.yudao.module.product.enums.spu.PurchaseLimitTypeEnum; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; -import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; +import cn.iocoder.yudao.module.trade.service.order.TradePurchaseLimitService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; -import java.time.LocalDateTime; -import java.time.temporal.TemporalAdjusters; -import java.util.*; -import java.util.stream.Collectors; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*; +import java.util.List; +import java.util.Objects; /** * 限购校验订单处理器 *

- * 在订单创建前(beforeOrderCreate),校验商品是否设置了限购规则,如果设置了则检查用户历史购买记录是否超限 + * 在订单创建前(beforeOrderCreate),校验商品是否设置了限购规则 *

- * 限购规则仅对售货机订单(type = MACHINE)生效 - * - * @author 芋道源码 + * 限购规则对售货机订单生效(type = MACHINE 或 deliveryType = 自提柜) */ @Component @Slf4j public class TradePurchaseLimitOrderHandler implements TradeOrderHandler { @Resource - private ProductSpuApi productSpuApi; - - @Resource - private TradeOrderItemMapper tradeOrderItemMapper; + private TradePurchaseLimitService tradePurchaseLimitService; @Override public void beforeOrderCreate(TradeOrderDO order, List orderItems) { - // 限购校验仅对售货机订单生效 - if (!Objects.equals(order.getType(), TradeOrderTypeEnum.MACHINE.getType())) { + if (!isMachineOrder(order)) { return; } - - // 获取订单中所有的 SPU ID - Set spuIds = orderItems.stream() - .map(TradeOrderItemDO::getSpuId) - .collect(Collectors.toSet()); - if (CollUtil.isEmpty(spuIds)) { - return; - } - - // 批量查询 SPU 信息 - List spuList = productSpuApi.getSpuList(new ArrayList<>(spuIds)); - if (CollUtil.isEmpty(spuList)) { - return; - } - - // 过滤出有限购规则的商品 - List limitedSpus = spuList.stream() - .filter(spu -> spu.getPurchaseLimitType() != null - && !Objects.equals(spu.getPurchaseLimitType(), PurchaseLimitTypeEnum.NONE.getType()) - && spu.getPurchaseLimitCount() != null - && spu.getPurchaseLimitCount() > 0) - .collect(Collectors.toList()); - - if (CollUtil.isEmpty(limitedSpus)) { - return; - } - - // 逐个校验限购规则 - for (ProductSpuRespDTO spu : limitedSpus) { - // 查找本次订单中该 SPU 的购买数量 - int currentPurchaseCount = orderItems.stream() - .filter(item -> Objects.equals(item.getSpuId(), spu.getId())) - .mapToInt(TradeOrderItemDO::getCount) - .sum(); - - // 查询用户历史购买记录(根据限购类型决定查询时间范围) - int historyPurchaseCount = getHistoryPurchaseCount(order.getUserId(), spu.getId(), spu.getPurchaseLimitType()); - - // 判断是否超限 - int totalCount = currentPurchaseCount + historyPurchaseCount; - if (totalCount > spu.getPurchaseLimitCount()) { - String limitDesc = getLimitDescription(spu.getPurchaseLimitType()); - throw exception(ORDER_CREATE_PURCHASE_LIMIT_EXCEED, spu.getName(), limitDesc, spu.getPurchaseLimitCount()); - } - } + tradePurchaseLimitService.validateByOrderItems(order.getUserId(), orderItems); } - /** - * 查询用户历史购买数量(只统计已支付的订单) - * - * @param userId 用户 ID - * @param spuId 商品 SPU ID - * @param limitType 限购类型 - * @return 历史购买数量 - */ - private int getHistoryPurchaseCount(Long userId, Long spuId, Integer limitType) { - PurchaseLimitTypeEnum limitTypeEnum = PurchaseLimitTypeEnum.valueOf(limitType); - if (limitTypeEnum == null || limitTypeEnum == PurchaseLimitTypeEnum.NONE) { - return 0; - } - - LocalDateTime startTime = getStartTimeByLimitType(limitTypeEnum); - if (startTime == null) { - // 终身限购:不限制开始时间 - startTime = LocalDateTime.of(2000, 1, 1, 0, 0); - } - - // 查询该时间范围内,用户已支付的订单项(订单项表 trade_order_item) - // 注意:需要关联订单表(trade_order)判断订单是否已支付(pay_status = true) - return tradeOrderItemMapper.selectPurchaseCountByUserAndSpu(userId, spuId, startTime); + private boolean isMachineOrder(TradeOrderDO order) { + return Objects.equals(order.getType(), TradeOrderTypeEnum.MACHINE.getType()) + || Objects.equals(order.getDeliveryType(), DeliveryTypeEnum.DOOR.getType()); } - /** - * 根据限购类型获取查询起始时间 - * - * @param limitType 限购类型 - * @return 起始时间 - */ - private LocalDateTime getStartTimeByLimitType(PurchaseLimitTypeEnum limitType) { - LocalDateTime now = LocalDateTime.now(); - switch (limitType) { - case DAILY: - // 今天 00:00:00 - return now.toLocalDate().atStartOfDay(); - case WEEKLY: - // 本周一 00:00:00 - return now.with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY)) - .toLocalDate().atStartOfDay(); - case LIFETIME: - // 终身限购:不限制开始时间(从很早的时间开始统计) - return null; - default: - return null; - } - } - - /** - * 获取限购类型的可读描述 - */ - private String getLimitDescription(Integer limitType) { - PurchaseLimitTypeEnum limitTypeEnum = PurchaseLimitTypeEnum.valueOf(limitType); - if (limitTypeEnum == null) { - return "限购"; - } - return limitTypeEnum.getName(); - } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index 1e0b8ab..98844f7 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -519,6 +519,10 @@ public class TradePriceCalculatorHelper { result.setPrice(new TradePriceCalculateSummaryRespBO.Price()); recountSummaryAllPrice(result); recountSummaryAllGivePoint(result); + // 售货机汇总单识别为 MACHINE 类型,触发限购等售货机专属逻辑 + if (spuList.stream().anyMatch(spu -> ProductSpuTypesEnum.ENABLE.getTypes().equals(spu.getTypes()))) { + result.setType(TradeOrderTypeEnum.MACHINE.getType()); + } return result; }