From 3ce3972875da2508ae7dfbd174efd525cc84bb62 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 27 Nov 2025 09:34:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor(order):=20=E9=87=8D=E6=9E=84=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E8=B4=AD=E4=B9=B0=E6=A3=80=E6=9F=A5=E5=92=8C=E5=AE=9A?= =?UTF-8?q?=E4=BB=B7=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入商品类型能力配置,替代硬编码的商品类型判断 - 实现策略模式处理不同商品类型的重复购买检查 - 抽象定价模式,支持固定价格和数量计价等不同方式 - 新增策略工厂自动注册各类检查器实现 - 添加缓存机制提升商品类型配置查询性能 - 解耦订单服务与具体商品类型的紧耦合关系 - 提高代码可维护性和扩展性,便于新增商品类型 --- .../DuplicatePurchaseCheckerFactory.java | 68 +++++++ .../order/service/impl/OrderServiceImpl.java | 186 ++++++------------ .../order/strategy/DuplicateCheckContext.java | 65 ++++++ .../strategy/IDuplicatePurchaseChecker.java | 43 ++++ .../impl/NoCheckDuplicateChecker.java | 31 +++ .../strategy/impl/SetIdDuplicateChecker.java | 101 ++++++++++ .../impl/VideoIdDuplicateChecker.java | 100 ++++++++++ .../impl/PriceCalculationServiceImpl.java | 31 +-- .../capability/DuplicateCheckStrategy.java | 63 ++++++ .../basic/product/capability/PricingMode.java | 57 ++++++ .../capability/ProductTypeCapability.java | 156 +++++++++++++++ .../mapper/ProductTypeCapabilityMapper.java | 21 ++ .../IProductTypeCapabilityService.java | 70 +++++++ .../ProductTypeCapabilityServiceImpl.java | 162 +++++++++++++++ .../mapper/ProductTypeCapabilityMapper.xml | 47 +++++ 15 files changed, 1062 insertions(+), 139 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/order/factory/DuplicatePurchaseCheckerFactory.java create mode 100644 src/main/java/com/ycwl/basic/order/strategy/DuplicateCheckContext.java create mode 100644 src/main/java/com/ycwl/basic/order/strategy/IDuplicatePurchaseChecker.java create mode 100644 src/main/java/com/ycwl/basic/order/strategy/impl/NoCheckDuplicateChecker.java create mode 100644 src/main/java/com/ycwl/basic/order/strategy/impl/SetIdDuplicateChecker.java create mode 100644 src/main/java/com/ycwl/basic/order/strategy/impl/VideoIdDuplicateChecker.java create mode 100644 src/main/java/com/ycwl/basic/product/capability/DuplicateCheckStrategy.java create mode 100644 src/main/java/com/ycwl/basic/product/capability/PricingMode.java create mode 100644 src/main/java/com/ycwl/basic/product/capability/ProductTypeCapability.java create mode 100644 src/main/java/com/ycwl/basic/product/mapper/ProductTypeCapabilityMapper.java create mode 100644 src/main/java/com/ycwl/basic/product/service/IProductTypeCapabilityService.java create mode 100644 src/main/java/com/ycwl/basic/product/service/impl/ProductTypeCapabilityServiceImpl.java create mode 100644 src/main/resources/mapper/ProductTypeCapabilityMapper.xml diff --git a/src/main/java/com/ycwl/basic/order/factory/DuplicatePurchaseCheckerFactory.java b/src/main/java/com/ycwl/basic/order/factory/DuplicatePurchaseCheckerFactory.java new file mode 100644 index 00000000..fcf77045 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/factory/DuplicatePurchaseCheckerFactory.java @@ -0,0 +1,68 @@ +package com.ycwl.basic.order.factory; + +import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker; +import com.ycwl.basic.product.capability.DuplicateCheckStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 重复购买检查策略工厂 + * + * 设计原则: + * 1. 自动注册:Spring 自动注入所有策略实现并注册 + * 2. 类型安全:根据枚举类型查找策略 + * 3. 失败快速:找不到策略时抛出异常 + */ +@Slf4j +@Service +public class DuplicatePurchaseCheckerFactory { + + private final Map checkerMap = new HashMap<>(); + + /** + * 构造函数:自动注册所有策略实现 + */ + @Autowired + public DuplicatePurchaseCheckerFactory(List checkers) { + for (IDuplicatePurchaseChecker checker : checkers) { + DuplicateCheckStrategy strategy = checker.getStrategyType(); + checkerMap.put(strategy, checker); + log.info("注册重复购买检查策略: {} -> {}", strategy, checker.getClass().getSimpleName()); + } + log.info("重复购买检查策略工厂初始化完成,共注册 {} 个策略", checkerMap.size()); + } + + /** + * 根据策略类型获取检查器 + * + * @param strategy 策略类型 + * @return 对应的检查器实现 + * @throws IllegalArgumentException 如果找不到对应的策略实现 + */ + public IDuplicatePurchaseChecker getChecker(DuplicateCheckStrategy strategy) { + IDuplicatePurchaseChecker checker = checkerMap.get(strategy); + if (checker == null) { + throw new IllegalArgumentException("未找到重复购买检查策略: " + strategy); + } + return checker; + } + + /** + * 检查是否支持指定策略 + */ + public boolean supportsStrategy(DuplicateCheckStrategy strategy) { + return checkerMap.containsKey(strategy); + } + + /** + * 获取所有已注册的策略类型 + */ + public Map getAllCheckers() { + return new HashMap<>(checkerMap); + } +} diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java index f2fdb9cb..9e21ade4 100644 --- a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -21,6 +21,13 @@ import com.ycwl.basic.order.mapper.OrderV2Mapper; import com.ycwl.basic.order.mapper.OrderRefundMapper; import com.ycwl.basic.order.service.IOrderService; import com.ycwl.basic.order.event.*; +import com.ycwl.basic.order.strategy.DuplicateCheckContext; +import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker; +import com.ycwl.basic.order.factory.DuplicatePurchaseCheckerFactory; +import com.ycwl.basic.product.capability.DuplicateCheckStrategy; +import com.ycwl.basic.product.capability.PricingMode; +import com.ycwl.basic.product.capability.ProductTypeCapability; +import com.ycwl.basic.product.service.IProductTypeCapabilityService; import com.ycwl.basic.pricing.dto.DiscountDetail; import com.ycwl.basic.pricing.dto.PriceCalculationResult; import com.ycwl.basic.pricing.dto.ProductItem; @@ -69,6 +76,8 @@ public class OrderServiceImpl implements IOrderService { private final ICouponService couponService; private final IVoucherService voucherService; private final IProductConfigService productConfigService; + private final IProductTypeCapabilityService productTypeCapabilityService; + private final DuplicatePurchaseCheckerFactory duplicatePurchaseCheckerFactory; @Override @Transactional(rollbackFor = Exception.class) @@ -767,16 +776,10 @@ public class OrderServiceImpl implements IOrderService { /** * 获取商品类型中文名称 + * 重构: 从配置驱动替代硬编码 */ private String getProductTypeName(String productType) { - return switch (productType) { - case "VLOG_VIDEO" -> "Vlog视频"; - case "RECORDING_SET" -> "录像集"; - case "PHOTO_SET" -> "照相集"; - case "PHOTO_PRINT" -> "照片打印"; - case "MACHINE_PRINT" -> "一体机打印"; - default -> "景区商品"; - }; + return productTypeCapabilityService.getDisplayName(productType); } /** @@ -850,14 +853,17 @@ public class OrderServiceImpl implements IOrderService { : getProductTypeName(product.getProductType().name()); // 3. 处理按数量计价的商品类型 - if (product.getProductType() == com.ycwl.basic.pricing.enums.ProductType.PHOTO_PRINT || - product.getProductType() == com.ycwl.basic.pricing.enums.ProductType.MACHINE_PRINT) { - if (product.getQuantity() != null && product.getQuantity() > 0) { - unitPrice = unitPrice.multiply(BigDecimal.valueOf(product.getQuantity())); - if (originalPrice != null) { - originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity())); - } + // 重构: 使用商品类型能力配置替代硬编码判断 + ProductTypeCapability capability = productTypeCapabilityService.getCapability(productTypeCode); + if (capability.getPricingModeEnum() == PricingMode.QUANTITY_BASED) { + Integer quantity = product.getQuantity() != null && product.getQuantity() > 0 + ? product.getQuantity() : 1; + unitPrice = unitPrice.multiply(BigDecimal.valueOf(quantity)); + if (originalPrice != null) { + originalPrice = originalPrice.multiply(BigDecimal.valueOf(quantity)); } + log.debug("按数量计价: productType={}, quantity={}, unitPrice={}", + productTypeCode, quantity, unitPrice); } } @@ -894,7 +900,8 @@ public class OrderServiceImpl implements IOrderService { /** * 检查重复购买 * 防止用户重复购买相同内容 - * + * 重构: 使用策略模式替代硬编码的 switch-case + * * @param userId 用户ID * @param faceId 人脸ID * @param scenicId 景区ID @@ -903,117 +910,44 @@ public class OrderServiceImpl implements IOrderService { */ private void checkDuplicatePurchase(Long userId, Long faceId, Long scenicId, List products) { for (ProductItem product : products) { - switch (product.getProductType()) { - case VLOG_VIDEO: - checkVideoAlreadyPurchased(userId, faceId, scenicId, product.getProductId()); - break; - case RECORDING_SET: - case PHOTO_SET: - checkSetAlreadyPurchased(userId, faceId, scenicId, product.getProductType()); - break; - case PHOTO_PRINT: - case PHOTO_PRINT_MU: - case PHOTO_PRINT_FX: - case MACHINE_PRINT: - // 打印类商品允许重复购买,跳过检查 - log.debug("跳过打印类商品重复购买检查: productType={}, productId={}", - product.getProductType(), product.getProductId()); - break; - default: - log.warn("未知的商品类型,跳过重复购买检查: productType={}", product.getProductType()); - break; + String productType = product.getProductType().getCode(); + + // 获取商品类型能力配置 + ProductTypeCapability capability = productTypeCapabilityService.getCapability(productType); + + // 如果允许重复购买,直接跳过 + if (Boolean.TRUE.equals(capability.getAllowDuplicatePurchase())) { + log.debug("商品类型允许重复购买,跳过检查: productType={}, productId={}", + productType, product.getProductId()); + continue; + } + + // 获取检查策略并执行 + DuplicateCheckStrategy strategy = capability.getDuplicateCheckStrategyEnum(); + if (strategy != null && strategy != DuplicateCheckStrategy.NO_CHECK) { + try { + IDuplicatePurchaseChecker checker = duplicatePurchaseCheckerFactory.getChecker(strategy); + + // 构建检查上下文 + DuplicateCheckContext context = new DuplicateCheckContext(); + context.setUserId(String.valueOf(userId)); + context.setScenicId(String.valueOf(scenicId)); + context.setProductType(productType); + context.setProductId(product.getProductId()); + context.setProducts(products); + context.addParam("faceId", faceId); + + // 执行检查 + checker.check(context); + } catch (DuplicatePurchaseException e) { + // 重新抛出重复购买异常 + throw e; + } catch (Exception e) { + log.error("重复购买检查失败,策略: {}, productType: {}", strategy, productType, e); + // 检查失败时为了安全起见,默认拒绝 + throw new BaseException("重复购买检查失败,请稍后重试"); + } } } } - - /** - * 检查视频是否已经购买 - * - * @param userId 用户ID - * @param faceId 人脸ID - * @param scenicId 景区ID - * @param videoId 视频ID - * @throws DuplicatePurchaseException 如果已购买 - */ - private void checkVideoAlreadyPurchased(Long userId, Long faceId, Long scenicId, String videoId) { - // 构建查询条件:查找已支付的有效订单中包含该视频的订单 - QueryWrapper orderQuery = new QueryWrapper<>(); - orderQuery.eq("member_id", userId) - .eq("face_id", faceId) - .eq("scenic_id", scenicId) - .eq("payment_status", PaymentStatus.PAID.getCode()) - .in("order_status", OrderStatus.PAID.getCode(), OrderStatus.PROCESSING.getCode(), OrderStatus.COMPLETED.getCode()) - .eq("deleted", 0); - - List existingOrders = orderV2Mapper.selectList(orderQuery); - - for (OrderV2 order : existingOrders) { - // 检查订单明细中是否包含该视频 - QueryWrapper itemQuery = new QueryWrapper<>(); - itemQuery.eq("order_id", order.getId()) - .eq("product_type", com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO.name()) - .eq("product_id", videoId); - - long count = orderItemMapper.selectCount(itemQuery); - if (count > 0) { - log.warn("检测到重复购买视频: userId={}, faceId={}, scenicId={}, videoId={}, existingOrderId={}", - userId, faceId, scenicId, videoId, order.getId()); - throw new DuplicatePurchaseException( - "您已购买过此视频", - order.getId(), - order.getOrderNo(), - com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO, - videoId - ); - } - } - - log.debug("视频重复购买检查通过: userId={}, faceId={}, scenicId={}, videoId={}", - userId, faceId, scenicId, videoId); - } - - /** - * 检查套餐(录像集/照相集)是否已经购买 - * - * @param userId 用户ID - * @param faceId 人脸ID - * @param scenicId 景区ID - * @param productType 商品类型 - * @throws DuplicatePurchaseException 如果已购买 - */ - private void checkSetAlreadyPurchased(Long userId, Long faceId, Long scenicId, - com.ycwl.basic.pricing.enums.ProductType productType) { - // 构建查询条件:查找已支付的有效订单中包含该类型套餐的订单 - QueryWrapper orderQuery = new QueryWrapper<>(); - orderQuery.eq("member_id", userId) - .eq("face_id", faceId) - .eq("scenic_id", scenicId) - .eq("payment_status", PaymentStatus.PAID.getCode()) - .in("order_status", OrderStatus.PAID.getCode(), OrderStatus.PROCESSING.getCode(), OrderStatus.COMPLETED.getCode()) - .eq("deleted", 0); - - List existingOrders = orderV2Mapper.selectList(orderQuery); - - for (OrderV2 order : existingOrders) { - // 检查订单明细中是否包含该类型的套餐 - QueryWrapper itemQuery = new QueryWrapper<>(); - itemQuery.eq("order_id", order.getId()) - .eq("product_type", productType.name()); - - long count = orderItemMapper.selectCount(itemQuery); - if (count > 0) { - log.warn("检测到重复购买套餐: userId={}, faceId={}, scenicId={}, productType={}, existingOrderId={}", - userId, faceId, scenicId, productType, order.getId()); - throw new DuplicatePurchaseException( - "您已购买过此类型的套餐", - order.getId(), - order.getOrderNo(), - productType - ); - } - } - - log.debug("套餐重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}", - userId, faceId, scenicId, productType); - } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/strategy/DuplicateCheckContext.java b/src/main/java/com/ycwl/basic/order/strategy/DuplicateCheckContext.java new file mode 100644 index 00000000..430552e6 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/strategy/DuplicateCheckContext.java @@ -0,0 +1,65 @@ +package com.ycwl.basic.order.strategy; + +import com.ycwl.basic.pricing.dto.ProductItem; +import lombok.Data; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 重复购买检查上下文 + * 封装检查所需的所有参数 + */ +@Data +public class DuplicateCheckContext { + + /** + * 用户ID + */ + private String userId; + + /** + * 景区ID + */ + private String scenicId; + + /** + * 商品类型代码 + */ + private String productType; + + /** + * 商品ID(视频ID、套餐ID等) + */ + private String productId; + + /** + * 当前购物车中的所有商品 + */ + private List products; + + /** + * 额外参数(扩展用) + */ + private Map additionalParams; + + public DuplicateCheckContext() { + this.additionalParams = new HashMap<>(); + } + + /** + * 添加额外参数 + */ + public void addParam(String key, Object value) { + this.additionalParams.put(key, value); + } + + /** + * 获取额外参数 + */ + @SuppressWarnings("unchecked") + public T getParam(String key) { + return (T) this.additionalParams.get(key); + } +} diff --git a/src/main/java/com/ycwl/basic/order/strategy/IDuplicatePurchaseChecker.java b/src/main/java/com/ycwl/basic/order/strategy/IDuplicatePurchaseChecker.java new file mode 100644 index 00000000..038bc2a6 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/strategy/IDuplicatePurchaseChecker.java @@ -0,0 +1,43 @@ +package com.ycwl.basic.order.strategy; + +import com.ycwl.basic.order.exception.DuplicatePurchaseException; +import com.ycwl.basic.product.capability.DuplicateCheckStrategy; + +/** + * 重复购买检查策略接口 + * + * 设计原则: + * 1. 单一职责: 每个策略只负责一种检查逻辑 + * 2. 开放扩展: 通过实现接口添加新策略 + * 3. 自动注册: Spring 自动扫描并注册所有实现类 + */ +public interface IDuplicatePurchaseChecker { + + /** + * 获取策略类型 + * 用于策略工厂的注册和查找 + * + * @return 对应的 DuplicateCheckStrategy 枚举值 + */ + DuplicateCheckStrategy getStrategyType(); + + /** + * 执行重复购买检查 + * + * @param context 检查上下文,包含用户ID、商品信息等 + * @throws DuplicatePurchaseException 如果检测到重复购买 + */ + void check(DuplicateCheckContext context) throws DuplicatePurchaseException; + + /** + * 是否支持该商品类型 + * 默认实现:所有策略都支持所有商品类型 + * 可在具体实现中覆盖以限制适用范围 + * + * @param productType 商品类型代码 + * @return true-支持, false-不支持 + */ + default boolean supports(String productType) { + return true; + } +} diff --git a/src/main/java/com/ycwl/basic/order/strategy/impl/NoCheckDuplicateChecker.java b/src/main/java/com/ycwl/basic/order/strategy/impl/NoCheckDuplicateChecker.java new file mode 100644 index 00000000..56587199 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/strategy/impl/NoCheckDuplicateChecker.java @@ -0,0 +1,31 @@ +package com.ycwl.basic.order.strategy.impl; + +import com.ycwl.basic.order.strategy.DuplicateCheckContext; +import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker; +import com.ycwl.basic.product.capability.DuplicateCheckStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 不检查重复购买策略 + * 适用于:打印类商品等允许重复购买的商品类型 + * + * 检查逻辑: + * 不执行任何检查,直接通过 + */ +@Slf4j +@Component +public class NoCheckDuplicateChecker implements IDuplicatePurchaseChecker { + + @Override + public DuplicateCheckStrategy getStrategyType() { + return DuplicateCheckStrategy.NO_CHECK; + } + + @Override + public void check(DuplicateCheckContext context) { + // 不检查,直接通过 + log.debug("跳过重复购买检查: productType={}, productId={}", + context.getProductType(), context.getProductId()); + } +} diff --git a/src/main/java/com/ycwl/basic/order/strategy/impl/SetIdDuplicateChecker.java b/src/main/java/com/ycwl/basic/order/strategy/impl/SetIdDuplicateChecker.java new file mode 100644 index 00000000..7226c0c3 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/strategy/impl/SetIdDuplicateChecker.java @@ -0,0 +1,101 @@ +package com.ycwl.basic.order.strategy.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.ycwl.basic.order.entity.OrderItemV2; +import com.ycwl.basic.order.entity.OrderV2; +import com.ycwl.basic.order.enums.OrderStatus; +import com.ycwl.basic.order.enums.PaymentStatus; +import com.ycwl.basic.order.exception.DuplicatePurchaseException; +import com.ycwl.basic.order.mapper.OrderItemMapper; +import com.ycwl.basic.order.mapper.OrderV2Mapper; +import com.ycwl.basic.order.strategy.DuplicateCheckContext; +import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker; +import com.ycwl.basic.product.capability.DuplicateCheckStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 按套餐ID检查重复购买策略 + * 适用于:RECORDING_SET(录像集)、PHOTO_SET(照相集)商品类型 + * + * 检查逻辑: + * 1. 查找用户在该景区已支付的有效订单 + * 2. 检查订单明细中是否包含相同类型的套餐 + * 3. 如果存在,抛出重复购买异常 + */ +@Slf4j +@Component +public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker { + + @Autowired + private OrderV2Mapper orderV2Mapper; + + @Autowired + private OrderItemMapper orderItemMapper; + + @Override + public DuplicateCheckStrategy getStrategyType() { + return DuplicateCheckStrategy.CHECK_BY_SET_ID; + } + + @Override + public void check(DuplicateCheckContext context) throws DuplicatePurchaseException { + String userId = context.getUserId(); + String scenicId = context.getScenicId(); + String productType = context.getProductType(); + + // 获取人脸ID(从扩展参数中) + Long faceId = context.getParam("faceId"); + + log.debug("执行套餐ID重复购买检查: userId={}, faceId={}, scenicId={}, productType={}", + userId, faceId, scenicId, productType); + + // 构建查询条件:查找已支付的有效订单 + QueryWrapper orderQuery = new QueryWrapper<>(); + orderQuery.eq("member_id", userId) + .eq("scenic_id", scenicId) + .eq("payment_status", PaymentStatus.PAID.getCode()) + .in("order_status", + OrderStatus.PAID.getCode(), + OrderStatus.PROCESSING.getCode(), + OrderStatus.COMPLETED.getCode()) + .eq("deleted", 0); + + // 如果提供了人脸ID,也作为查询条件 + if (faceId != null) { + orderQuery.eq("face_id", faceId); + } + + List existingOrders = orderV2Mapper.selectList(orderQuery); + + for (OrderV2 order : existingOrders) { + // 检查订单明细中是否包含该类型的套餐 + QueryWrapper itemQuery = new QueryWrapper<>(); + itemQuery.eq("order_id", order.getId()) + .eq("product_type", productType); + + long count = orderItemMapper.selectCount(itemQuery); + if (count > 0) { + log.warn("检测到重复购买套餐: userId={}, faceId={}, scenicId={}, productType={}, existingOrderId={}", + userId, faceId, scenicId, productType, order.getId()); + + // 为了兼容旧代码,需要转换为 ProductType 枚举 + com.ycwl.basic.pricing.enums.ProductType productTypeEnum = + com.ycwl.basic.pricing.enums.ProductType.fromCode(productType); + + throw new DuplicatePurchaseException( + "您已购买过此类型的套餐", + order.getId(), + order.getOrderNo(), + productTypeEnum + ); + } + } + + log.debug("套餐重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}", + userId, faceId, scenicId, productType); + } +} diff --git a/src/main/java/com/ycwl/basic/order/strategy/impl/VideoIdDuplicateChecker.java b/src/main/java/com/ycwl/basic/order/strategy/impl/VideoIdDuplicateChecker.java new file mode 100644 index 00000000..ccf53589 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/strategy/impl/VideoIdDuplicateChecker.java @@ -0,0 +1,100 @@ +package com.ycwl.basic.order.strategy.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.ycwl.basic.order.entity.OrderItemV2; +import com.ycwl.basic.order.entity.OrderV2; +import com.ycwl.basic.order.enums.OrderStatus; +import com.ycwl.basic.order.enums.PaymentStatus; +import com.ycwl.basic.order.exception.DuplicatePurchaseException; +import com.ycwl.basic.order.mapper.OrderItemMapper; +import com.ycwl.basic.order.mapper.OrderV2Mapper; +import com.ycwl.basic.order.strategy.DuplicateCheckContext; +import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker; +import com.ycwl.basic.product.capability.DuplicateCheckStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 按视频ID检查重复购买策略 + * 适用于:VLOG_VIDEO 商品类型 + * + * 检查逻辑: + * 1. 查找用户在该景区已支付的有效订单 + * 2. 检查订单明细中是否包含相同的视频ID + * 3. 如果存在,抛出重复购买异常 + */ +@Slf4j +@Component +public class VideoIdDuplicateChecker implements IDuplicatePurchaseChecker { + + @Autowired + private OrderV2Mapper orderV2Mapper; + + @Autowired + private OrderItemMapper orderItemMapper; + + @Override + public DuplicateCheckStrategy getStrategyType() { + return DuplicateCheckStrategy.CHECK_BY_VIDEO_ID; + } + + @Override + public void check(DuplicateCheckContext context) throws DuplicatePurchaseException { + String userId = context.getUserId(); + String scenicId = context.getScenicId(); + String productId = context.getProductId(); + + // 获取人脸ID(从扩展参数中) + Long faceId = context.getParam("faceId"); + + log.debug("执行视频ID重复购买检查: userId={}, faceId={}, scenicId={}, videoId={}", + userId, faceId, scenicId, productId); + + // 构建查询条件:查找已支付的有效订单 + QueryWrapper orderQuery = new QueryWrapper<>(); + orderQuery.eq("member_id", userId) + .eq("scenic_id", scenicId) + .eq("payment_status", PaymentStatus.PAID.getCode()) + .in("order_status", + OrderStatus.PAID.getCode(), + OrderStatus.PROCESSING.getCode(), + OrderStatus.COMPLETED.getCode()) + .eq("deleted", 0); + + // 如果提供了人脸ID,也作为查询条件 + if (faceId != null) { + orderQuery.eq("face_id", faceId); + } + + List existingOrders = orderV2Mapper.selectList(orderQuery); + + for (OrderV2 order : existingOrders) { + // 检查订单明细中是否包含该视频 + QueryWrapper itemQuery = new QueryWrapper<>(); + itemQuery.eq("order_id", order.getId()) + .eq("product_type", "VLOG_VIDEO") + .eq("product_id", productId); + + long count = orderItemMapper.selectCount(itemQuery); + if (count > 0) { + log.warn("检测到重复购买视频: userId={}, faceId={}, scenicId={}, videoId={}, existingOrderId={}", + userId, faceId, scenicId, productId, order.getId()); + + // 为了兼容旧代码,需要转换为 ProductType 枚举 + throw new DuplicatePurchaseException( + "您已购买过此视频", + order.getId(), + order.getOrderNo(), + com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO, + productId + ); + } + } + + log.debug("视频重复购买检查通过: userId={}, faceId={}, scenicId={}, videoId={}", + userId, faceId, scenicId, productId); + } +} diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/PriceCalculationServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/PriceCalculationServiceImpl.java index 969b9f58..ac263ae4 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/PriceCalculationServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/PriceCalculationServiceImpl.java @@ -6,6 +6,9 @@ import com.ycwl.basic.pricing.entity.PriceTierConfig; import com.ycwl.basic.pricing.enums.ProductType; import com.ycwl.basic.pricing.exception.PriceCalculationException; import com.ycwl.basic.pricing.service.*; +import com.ycwl.basic.product.capability.PricingMode; +import com.ycwl.basic.product.capability.ProductTypeCapability; +import com.ycwl.basic.product.service.IProductTypeCapabilityService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -29,16 +32,18 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { private final IPriceBundleService bundleService; private final IDiscountDetectionService discountDetectionService; private final IVoucherService voucherService; + private final IProductTypeCapabilityService productTypeCapabilityService; /** - * 判断是否为打印类商品 - * 打印类商品的价格计算方式为:单价 × 数量 + * 判断是否为按数量计价的商品 + * 重构: 使用商品类型能力配置替代硬编码 + * + * @param productType 商品类型代码 + * @return true-按数量计价, false-固定价格 */ - private boolean isPrintProduct(ProductType productType) { - return productType == ProductType.PHOTO_PRINT - || productType == ProductType.PHOTO_PRINT_MU - || productType == ProductType.PHOTO_PRINT_FX - || productType == ProductType.MACHINE_PRINT; + private boolean isQuantityBasedPricing(String productType) { + ProductTypeCapability capability = productTypeCapabilityService.getCapability(productType); + return capability.getPricingModeEnum() == PricingMode.QUANTITY_BASED; } @Override @@ -201,7 +206,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { try { PriceProductConfig baseConfig = productConfigService.getProductConfig(productType.getCode(), productId); if (baseConfig != null) { - if (isPrintProduct(productType)) { + if (isQuantityBasedPricing(productType.getCode())) { return baseConfig.getBasePrice().multiply(BigDecimal.valueOf(product.getQuantity())); } else { return baseConfig.getBasePrice(); @@ -216,7 +221,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { try { PriceProductConfig defaultConfig = productConfigService.getProductConfig(productType.getCode(), "default"); if (defaultConfig != null) { - if (isPrintProduct(productType)) { + if (isQuantityBasedPricing(productType.getCode())) { return defaultConfig.getBasePrice().multiply(BigDecimal.valueOf(product.getQuantity())); } else { return defaultConfig.getBasePrice(); @@ -230,7 +235,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { List configs = productConfigService.getProductConfig(productType.getCode()); if (!configs.isEmpty()) { PriceProductConfig baseConfig = configs.get(0); // 使用第一个配置作为默认 - if (isPrintProduct(productType)) { + if (isQuantityBasedPricing(productType.getCode())) { return baseConfig.getBasePrice().multiply(BigDecimal.valueOf(product.getQuantity())); } else { return baseConfig.getBasePrice(); @@ -264,7 +269,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { actualPrice = baseConfig.getBasePrice(); originalPrice = baseConfig.getOriginalPrice(); - if (isPrintProduct(productType)) { + if (isQuantityBasedPricing(productType.getCode())) { actualPrice = actualPrice.multiply(BigDecimal.valueOf(product.getQuantity())); if (originalPrice != null) { originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity())); @@ -284,7 +289,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { actualPrice = defaultConfig.getBasePrice(); originalPrice = defaultConfig.getOriginalPrice(); - if (isPrintProduct(productType)) { + if (isQuantityBasedPricing(productType.getCode())) { actualPrice = actualPrice.multiply(BigDecimal.valueOf(product.getQuantity())); if (originalPrice != null) { originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity())); @@ -303,7 +308,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { actualPrice = baseConfig.getBasePrice(); originalPrice = baseConfig.getOriginalPrice(); - if (isPrintProduct(productType)) { + if (isQuantityBasedPricing(productType.getCode())) { actualPrice = actualPrice.multiply(BigDecimal.valueOf(product.getQuantity())); if (originalPrice != null) { originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity())); diff --git a/src/main/java/com/ycwl/basic/product/capability/DuplicateCheckStrategy.java b/src/main/java/com/ycwl/basic/product/capability/DuplicateCheckStrategy.java new file mode 100644 index 00000000..38a51fd5 --- /dev/null +++ b/src/main/java/com/ycwl/basic/product/capability/DuplicateCheckStrategy.java @@ -0,0 +1,63 @@ +package com.ycwl.basic.product.capability; + +/** + * 重复购买检查策略枚举 + * 定义不同商品类型的重复购买检查规则 + */ +public enum DuplicateCheckStrategy { + + /** + * 不检查(如打印类商品) + * 允许重复购买 + */ + NO_CHECK("NO_CHECK", "不检查"), + + /** + * 按视频ID检查(VLOG_VIDEO) + * 同一个视频不允许重复购买 + */ + CHECK_BY_VIDEO_ID("CHECK_BY_VIDEO_ID", "按视频ID检查"), + + /** + * 按套餐ID检查(RECORDING_SET, PHOTO_SET) + * 同一个套餐不允许重复购买 + */ + CHECK_BY_SET_ID("CHECK_BY_SET_ID", "按套餐ID检查"), + + /** + * 自定义策略(通过扩展实现) + * 预留扩展点,用于未来更复杂的检查逻辑 + */ + CUSTOM("CUSTOM", "自定义策略"); + + private final String code; + private final String description; + + DuplicateCheckStrategy(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据代码获取枚举值 + */ + public static DuplicateCheckStrategy fromCode(String code) { + if (code == null) { + return null; + } + for (DuplicateCheckStrategy strategy : DuplicateCheckStrategy.values()) { + if (strategy.code.equals(code)) { + return strategy; + } + } + throw new IllegalArgumentException("未知的重复检查策略: " + code); + } +} diff --git a/src/main/java/com/ycwl/basic/product/capability/PricingMode.java b/src/main/java/com/ycwl/basic/product/capability/PricingMode.java new file mode 100644 index 00000000..20ffef0d --- /dev/null +++ b/src/main/java/com/ycwl/basic/product/capability/PricingMode.java @@ -0,0 +1,57 @@ +package com.ycwl.basic.product.capability; + +/** + * 定价模式枚举 + * 定义商品的价格计算方式 + */ +public enum PricingMode { + + /** + * 固定价格(不受数量影响) + * 适用于:视频类、照片集类商品 + */ + FIXED("FIXED", "固定价格"), + + /** + * 基于数量(价格 = 单价 × 数量) + * 适用于:打印类商品 + */ + QUANTITY_BASED("QUANTITY_BASED", "基于数量"), + + /** + * 分层定价(根据数量区间) + * 适用于:支持阶梯定价的商品 + */ + TIERED("TIERED", "分层定价"); + + private final String code; + private final String description; + + PricingMode(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据代码获取枚举值 + */ + public static PricingMode fromCode(String code) { + if (code == null) { + return null; + } + for (PricingMode mode : PricingMode.values()) { + if (mode.code.equals(code)) { + return mode; + } + } + throw new IllegalArgumentException("未知的定价模式: " + code); + } +} diff --git a/src/main/java/com/ycwl/basic/product/capability/ProductTypeCapability.java b/src/main/java/com/ycwl/basic/product/capability/ProductTypeCapability.java new file mode 100644 index 00000000..0c8754fd --- /dev/null +++ b/src/main/java/com/ycwl/basic/product/capability/ProductTypeCapability.java @@ -0,0 +1,156 @@ +package com.ycwl.basic.product.capability; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 商品类型能力配置实体 + * 统一管理商品类型的各项能力和行为特征 + * + * 设计目标: + * 1. 消除代码中的硬编码,将商品类型相关的业务规则配置化 + * 2. 提供统一的商品类型能力查询接口 + * 3. 支持通过配置快速扩展新商品类型 + */ +@Data +@TableName(value = "product_type_capability", autoResultMap = true) +public class ProductTypeCapability { + + /** + * 主键ID + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 商品类型代码(唯一) + * 如:VLOG_VIDEO, PHOTO_PRINT 等 + */ + private String productType; + + /** + * 显示名称 + * 用于前端展示和支付页面 + */ + private String displayName; + + /** + * 商品分类 + * 如:VIDEO(视频类), PRINT(打印类), PHOTO_SET(照片集类) + */ + private String category; + + // ========== 定价相关 ========== + + /** + * 定价模式 + * FIXED: 固定价格 + * QUANTITY_BASED: 基于数量(价格 = 单价 × 数量) + * TIERED: 分层定价 + */ + private String pricingMode; + + /** + * 是否支持阶梯定价 + */ + private Boolean supportsTierPricing; + + // ========== 购买限制 ========== + + /** + * 是否允许重复购买 + */ + private Boolean allowDuplicatePurchase; + + /** + * 重复购买检查策略 + * NO_CHECK: 不检查 + * CHECK_BY_VIDEO_ID: 按视频ID检查 + * CHECK_BY_SET_ID: 按套餐ID检查 + * CUSTOM: 自定义策略 + */ + private String duplicateCheckStrategy; + + // ========== 优惠能力 ========== + + /** + * 是否可使用优惠券 + */ + private Boolean canUseCoupon; + + /** + * 是否可使用券码 + */ + private Boolean canUseVoucher; + + /** + * 是否可使用一口价优惠 + */ + private Boolean canUseOnePrice; + + /** + * 是否可参与打包优惠 + */ + private Boolean canUseBundle; + + // ========== 扩展属性 ========== + + /** + * 扩展属性(JSON 格式) + * 用于存储特定商品类型的额外配置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map metadata; + + /** + * 是否启用 + */ + private Boolean isActive; + + /** + * 创建时间 + */ + private LocalDateTime createdAt; + + /** + * 更新时间 + */ + private LocalDateTime updatedAt; + + // ========== 便捷方法 ========== + + /** + * 获取定价模式枚举 + */ + public PricingMode getPricingModeEnum() { + return PricingMode.fromCode(this.pricingMode); + } + + /** + * 获取重复检查策略枚举 + */ + public DuplicateCheckStrategy getDuplicateCheckStrategyEnum() { + return DuplicateCheckStrategy.fromCode(this.duplicateCheckStrategy); + } + + /** + * 设置定价模式枚举 + */ + public void setPricingModeEnum(PricingMode mode) { + this.pricingMode = mode != null ? mode.getCode() : null; + } + + /** + * 设置重复检查策略枚举 + */ + public void setDuplicateCheckStrategyEnum(DuplicateCheckStrategy strategy) { + this.duplicateCheckStrategy = strategy != null ? strategy.getCode() : null; + } +} diff --git a/src/main/java/com/ycwl/basic/product/mapper/ProductTypeCapabilityMapper.java b/src/main/java/com/ycwl/basic/product/mapper/ProductTypeCapabilityMapper.java new file mode 100644 index 00000000..baee9c5e --- /dev/null +++ b/src/main/java/com/ycwl/basic/product/mapper/ProductTypeCapabilityMapper.java @@ -0,0 +1,21 @@ +package com.ycwl.basic.product.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.product.capability.ProductTypeCapability; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 商品类型能力配置 Mapper + */ +@Mapper +public interface ProductTypeCapabilityMapper extends BaseMapper { + + /** + * 根据商品类型代码查询能力配置 + * + * @param productType 商品类型代码 + * @return 能力配置,不存在时返回 null + */ + ProductTypeCapability selectByProductType(@Param("productType") String productType); +} diff --git a/src/main/java/com/ycwl/basic/product/service/IProductTypeCapabilityService.java b/src/main/java/com/ycwl/basic/product/service/IProductTypeCapabilityService.java new file mode 100644 index 00000000..a22aa5fd --- /dev/null +++ b/src/main/java/com/ycwl/basic/product/service/IProductTypeCapabilityService.java @@ -0,0 +1,70 @@ +package com.ycwl.basic.product.service; + +import com.ycwl.basic.product.capability.DuplicateCheckStrategy; +import com.ycwl.basic.product.capability.PricingMode; +import com.ycwl.basic.product.capability.ProductTypeCapability; + +/** + * 商品类型能力服务接口 + * 提供商品类型能力配置的查询和管理功能 + * + * 设计原则: + * 1. 缓存优先:所有查询都支持缓存,提高性能 + * 2. 降级兜底:查询失败时返回安全的默认配置 + * 3. 接口简洁:提供便捷方法,简化调用方代码 + */ +public interface IProductTypeCapabilityService { + + /** + * 获取商品类型能力配置(支持缓存) + * + * @param productType 商品类型代码(如 VLOG_VIDEO) + * @return 商品类型能力配置,查询失败时返回默认配置 + */ + ProductTypeCapability getCapability(String productType); + + /** + * 获取商品显示名称 + * + * @param productType 商品类型代码 + * @return 显示名称,查询失败时返回"景区商品" + */ + String getDisplayName(String productType); + + /** + * 判断是否允许重复购买 + * + * @param productType 商品类型代码 + * @return true-允许重复购买, false-不允许 + */ + boolean allowDuplicatePurchase(String productType); + + /** + * 获取定价模式 + * + * @param productType 商品类型代码 + * @return 定价模式枚举 + */ + PricingMode getPricingMode(String productType); + + /** + * 获取重复检查策略 + * + * @param productType 商品类型代码 + * @return 重复检查策略枚举 + */ + DuplicateCheckStrategy getDuplicateCheckStrategy(String productType); + + /** + * 刷新缓存 + * 用于配置更新后手动触发缓存刷新 + */ + void refreshCache(); + + /** + * 刷新指定商品类型的缓存 + * + * @param productType 商品类型代码 + */ + void refreshCache(String productType); +} diff --git a/src/main/java/com/ycwl/basic/product/service/impl/ProductTypeCapabilityServiceImpl.java b/src/main/java/com/ycwl/basic/product/service/impl/ProductTypeCapabilityServiceImpl.java new file mode 100644 index 00000000..9920e65b --- /dev/null +++ b/src/main/java/com/ycwl/basic/product/service/impl/ProductTypeCapabilityServiceImpl.java @@ -0,0 +1,162 @@ +package com.ycwl.basic.product.service.impl; + +import com.ycwl.basic.product.capability.DuplicateCheckStrategy; +import com.ycwl.basic.product.capability.PricingMode; +import com.ycwl.basic.product.capability.ProductTypeCapability; +import com.ycwl.basic.product.mapper.ProductTypeCapabilityMapper; +import com.ycwl.basic.product.service.IProductTypeCapabilityService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +/** + * 商品类型能力服务实现 + * + * 缓存策略: + * - 使用 Spring Cache 进行缓存 + * - 缓存名称:productTypeCapability + * - 缓存key:商品类型代码 + * - 缓存失效:手动调用 refreshCache 方法 + */ +@Slf4j +@Service +@CacheConfig(cacheNames = "productTypeCapability") +public class ProductTypeCapabilityServiceImpl implements IProductTypeCapabilityService { + + @Autowired + private ProductTypeCapabilityMapper mapper; + + @Cacheable(key = "#productType") + @Override + public ProductTypeCapability getCapability(String productType) { + if (productType == null || productType.trim().isEmpty()) { + log.warn("商品类型代码为空,返回默认配置"); + return getDefaultCapability(null); + } + + try { + ProductTypeCapability capability = mapper.selectByProductType(productType); + if (capability == null) { + log.warn("未找到商品类型能力配置: {}, 使用默认配置", productType); + return getDefaultCapability(productType); + } + + // 验证配置完整性 + if (!isValidCapability(capability)) { + log.warn("商品类型能力配置不完整: {}, 使用默认配置", productType); + return getDefaultCapability(productType); + } + + return capability; + } catch (Exception e) { + log.error("查询商品类型能力配置失败: {}, 降级到默认配置", productType, e); + return getDefaultCapability(productType); + } + } + + @Override + public String getDisplayName(String productType) { + ProductTypeCapability capability = getCapability(productType); + return capability.getDisplayName(); + } + + @Override + public boolean allowDuplicatePurchase(String productType) { + ProductTypeCapability capability = getCapability(productType); + return Boolean.TRUE.equals(capability.getAllowDuplicatePurchase()); + } + + @Override + public PricingMode getPricingMode(String productType) { + ProductTypeCapability capability = getCapability(productType); + return capability.getPricingModeEnum(); + } + + @Override + public DuplicateCheckStrategy getDuplicateCheckStrategy(String productType) { + ProductTypeCapability capability = getCapability(productType); + return capability.getDuplicateCheckStrategyEnum(); + } + + @CacheEvict(allEntries = true) + @Override + public void refreshCache() { + log.info("刷新所有商品类型能力缓存"); + } + + @CacheEvict(key = "#productType") + @Override + public void refreshCache(String productType) { + log.info("刷新商品类型能力缓存: {}", productType); + } + + /** + * 获取默认能力配置(兜底方案) + * 设计原则:安全第一,宁可限制也不放开 + * + * @param productType 商品类型代码(可为null) + * @return 安全的默认配置 + */ + private ProductTypeCapability getDefaultCapability(String productType) { + ProductTypeCapability defaultCap = new ProductTypeCapability(); + defaultCap.setProductType(productType); + defaultCap.setDisplayName("景区商品"); + defaultCap.setCategory("GENERAL"); + + // 定价相关:默认固定价格 + defaultCap.setPricingMode(PricingMode.FIXED.getCode()); + defaultCap.setSupportsTierPricing(false); + + // 购买限制:默认不允许重复购买(安全策略) + defaultCap.setAllowDuplicatePurchase(false); + defaultCap.setDuplicateCheckStrategy(DuplicateCheckStrategy.NO_CHECK.getCode()); + + // 优惠能力:默认全部允许 + defaultCap.setCanUseCoupon(true); + defaultCap.setCanUseVoucher(true); + defaultCap.setCanUseOnePrice(true); + defaultCap.setCanUseBundle(true); + + defaultCap.setIsActive(true); + + return defaultCap; + } + + /** + * 验证配置完整性 + */ + private boolean isValidCapability(ProductTypeCapability capability) { + if (capability == null) { + return false; + } + + // 必填字段检查 + if (capability.getProductType() == null || capability.getProductType().trim().isEmpty()) { + return false; + } + + if (capability.getDisplayName() == null || capability.getDisplayName().trim().isEmpty()) { + return false; + } + + if (capability.getPricingMode() == null) { + return false; + } + + // 验证枚举值有效性 + try { + PricingMode.fromCode(capability.getPricingMode()); + if (capability.getDuplicateCheckStrategy() != null) { + DuplicateCheckStrategy.fromCode(capability.getDuplicateCheckStrategy()); + } + } catch (IllegalArgumentException e) { + log.error("商品类型能力配置包含无效的枚举值: {}", capability.getProductType(), e); + return false; + } + + return true; + } +} diff --git a/src/main/resources/mapper/ProductTypeCapabilityMapper.xml b/src/main/resources/mapper/ProductTypeCapabilityMapper.xml new file mode 100644 index 00000000..4e1699dd --- /dev/null +++ b/src/main/resources/mapper/ProductTypeCapabilityMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, product_type, display_name, category, + pricing_mode, supports_tier_pricing, + allow_duplicate_purchase, duplicate_check_strategy, + can_use_coupon, can_use_voucher, can_use_one_price, can_use_bundle, + metadata, is_active, created_at, updated_at + + + + + +