diff --git a/src/main/java/com/ycwl/basic/order/strategy/impl/SetIdDuplicateChecker.java b/src/main/java/com/ycwl/basic/order/strategy/impl/ParentResourceDuplicateChecker.java similarity index 69% rename from src/main/java/com/ycwl/basic/order/strategy/impl/SetIdDuplicateChecker.java rename to src/main/java/com/ycwl/basic/order/strategy/impl/ParentResourceDuplicateChecker.java index 7226c0c3..63ef97e5 100644 --- a/src/main/java/com/ycwl/basic/order/strategy/impl/SetIdDuplicateChecker.java +++ b/src/main/java/com/ycwl/basic/order/strategy/impl/ParentResourceDuplicateChecker.java @@ -10,6 +10,7 @@ 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.pricing.enums.ProductType; import com.ycwl.basic.product.capability.DuplicateCheckStrategy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -18,17 +19,19 @@ import org.springframework.stereotype.Component; import java.util.List; /** - * 按套餐ID检查重复购买策略 - * 适用于:RECORDING_SET(录像集)、PHOTO_SET(照相集)商品类型 + * 父资源/套餐重复购买检查策略 + * 适用于:套餐类商品(RECORDING_SET录像集、PHOTO_SET照片集等) * * 检查逻辑: * 1. 查找用户在该景区已支付的有效订单 - * 2. 检查订单明细中是否包含相同类型的套餐 + * 2. 检查订单明细中是否包含相同product_type的商品(不关心具体productId) * 3. 如果存在,抛出重复购买异常 + * + * SQL查询: WHERE order_id = ? AND product_type = ? */ @Slf4j @Component -public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker { +public class ParentResourceDuplicateChecker implements IDuplicatePurchaseChecker { @Autowired private OrderV2Mapper orderV2Mapper; @@ -38,19 +41,19 @@ public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker { @Override public DuplicateCheckStrategy getStrategyType() { - return DuplicateCheckStrategy.CHECK_BY_SET_ID; + return DuplicateCheckStrategy.PARENT_RESOURCE; } @Override public void check(DuplicateCheckContext context) throws DuplicatePurchaseException { String userId = context.getUserId(); String scenicId = context.getScenicId(); - String productType = context.getProductType(); + String productType = context.getProductType(); // 如"RECORDING_SET"或"PHOTO_SET" // 获取人脸ID(从扩展参数中) Long faceId = context.getParam("faceId"); - log.debug("执行套餐ID重复购买检查: userId={}, faceId={}, scenicId={}, productType={}", + log.debug("执行父资源重复购买检查: userId={}, faceId={}, scenicId={}, productType={}", userId, faceId, scenicId, productType); // 构建查询条件:查找已支付的有效订单 @@ -72,22 +75,20 @@ public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker { List existingOrders = orderV2Mapper.selectList(orderQuery); for (OrderV2 order : existingOrders) { - // 检查订单明细中是否包含该类型的套餐 + // 检查订单明细中是否包含该类型的商品(仅按productType匹配) QueryWrapper itemQuery = new QueryWrapper<>(); itemQuery.eq("order_id", order.getId()) - .eq("product_type", productType); + .eq("product_type", productType); // 仅按productType匹配 long count = orderItemMapper.selectCount(itemQuery); if (count > 0) { - log.warn("检测到重复购买套餐: userId={}, faceId={}, scenicId={}, productType={}, existingOrderId={}", + 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); + ProductType productTypeEnum = ProductType.fromCode(productType); throw new DuplicatePurchaseException( - "您已购买过此类型的套餐", + String.format("您已购买过%s", productTypeEnum.getDescription()), order.getId(), order.getOrderNo(), productTypeEnum @@ -95,7 +96,7 @@ public class SetIdDuplicateChecker implements IDuplicatePurchaseChecker { } } - log.debug("套餐重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}", + 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/UniqueResourceDuplicateChecker.java similarity index 61% rename from src/main/java/com/ycwl/basic/order/strategy/impl/VideoIdDuplicateChecker.java rename to src/main/java/com/ycwl/basic/order/strategy/impl/UniqueResourceDuplicateChecker.java index ccf53589..de6f0f66 100644 --- a/src/main/java/com/ycwl/basic/order/strategy/impl/VideoIdDuplicateChecker.java +++ b/src/main/java/com/ycwl/basic/order/strategy/impl/UniqueResourceDuplicateChecker.java @@ -10,6 +10,7 @@ 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.pricing.enums.ProductType; import com.ycwl.basic.product.capability.DuplicateCheckStrategy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -18,17 +19,19 @@ import org.springframework.stereotype.Component; import java.util.List; /** - * 按视频ID检查重复购买策略 - * 适用于:VLOG_VIDEO 商品类型 + * 唯一资源重复购买检查策略 + * 适用于:独立资源类商品(PHOTO照片、VLOG_VIDEO视频、VIDEO视频片段等) * * 检查逻辑: * 1. 查找用户在该景区已支付的有效订单 - * 2. 检查订单明细中是否包含相同的视频ID + * 2. 检查订单明细中是否包含相同product_type和product_id的商品 * 3. 如果存在,抛出重复购买异常 + * + * SQL查询: WHERE order_id = ? AND product_type = ? AND product_id = ? */ @Slf4j @Component -public class VideoIdDuplicateChecker implements IDuplicatePurchaseChecker { +public class UniqueResourceDuplicateChecker implements IDuplicatePurchaseChecker { @Autowired private OrderV2Mapper orderV2Mapper; @@ -38,20 +41,21 @@ public class VideoIdDuplicateChecker implements IDuplicatePurchaseChecker { @Override public DuplicateCheckStrategy getStrategyType() { - return DuplicateCheckStrategy.CHECK_BY_VIDEO_ID; + return DuplicateCheckStrategy.UNIQUE_RESOURCE; } @Override public void check(DuplicateCheckContext context) throws DuplicatePurchaseException { String userId = context.getUserId(); String scenicId = context.getScenicId(); - String productId = context.getProductId(); + String productType = context.getProductType(); + String productId = context.getProductId(); // 唯一资源ID // 获取人脸ID(从扩展参数中) Long faceId = context.getParam("faceId"); - log.debug("执行视频ID重复购买检查: userId={}, faceId={}, scenicId={}, videoId={}", - userId, faceId, scenicId, productId); + log.debug("执行唯一资源重复购买检查: userId={}, faceId={}, scenicId={}, productType={}, productId={}", + userId, faceId, scenicId, productType, productId); // 构建查询条件:查找已支付的有效订单 QueryWrapper orderQuery = new QueryWrapper<>(); @@ -72,29 +76,30 @@ public class VideoIdDuplicateChecker implements IDuplicatePurchaseChecker { List existingOrders = orderV2Mapper.selectList(orderQuery); for (OrderV2 order : existingOrders) { - // 检查订单明细中是否包含该视频 + // 检查订单明细中是否包含该资源(按product_type和product_id双重匹配) QueryWrapper itemQuery = new QueryWrapper<>(); itemQuery.eq("order_id", order.getId()) - .eq("product_type", "VLOG_VIDEO") - .eq("product_id", productId); + .eq("product_type", productType) + .eq("product_id", productId); // 按productId精确匹配 long count = orderItemMapper.selectCount(itemQuery); if (count > 0) { - log.warn("检测到重复购买视频: userId={}, faceId={}, scenicId={}, videoId={}, existingOrderId={}", - userId, faceId, scenicId, productId, order.getId()); + log.warn("检测到重复购买唯一资源: userId={}, faceId={}, scenicId={}, productType={}, productId={}, existingOrderId={}", + userId, faceId, scenicId, productType, productId, order.getId()); + + ProductType productTypeEnum = ProductType.fromCode(productType); - // 为了兼容旧代码,需要转换为 ProductType 枚举 throw new DuplicatePurchaseException( - "您已购买过此视频", + String.format("您已购买过此%s", productTypeEnum.getDescription()), order.getId(), order.getOrderNo(), - com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO, + productTypeEnum, productId ); } } - log.debug("视频重复购买检查通过: userId={}, faceId={}, scenicId={}, videoId={}", - userId, faceId, scenicId, productId); + log.debug("唯一资源重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}, productId={}", + userId, faceId, scenicId, productType, productId); } } diff --git a/src/main/java/com/ycwl/basic/product/capability/DuplicateCheckStrategy.java b/src/main/java/com/ycwl/basic/product/capability/DuplicateCheckStrategy.java index 38a51fd5..5d37bf17 100644 --- a/src/main/java/com/ycwl/basic/product/capability/DuplicateCheckStrategy.java +++ b/src/main/java/com/ycwl/basic/product/capability/DuplicateCheckStrategy.java @@ -13,16 +13,27 @@ public enum DuplicateCheckStrategy { NO_CHECK("NO_CHECK", "不检查"), /** - * 按视频ID检查(VLOG_VIDEO) - * 同一个视频不允许重复购买 + * 检查唯一资源 + * 检查用户是否已购买过相同的独立资源 + * 查询逻辑:WHERE product_type = ? AND product_id = ? + * + * 适用商品: + * - VLOG_VIDEO:检查是否购买过同一个视频 + * - PHOTO:检查是否购买过同一张照片 + * - 未来的VIDEO片段:检查是否购买过同一段视频 */ - CHECK_BY_VIDEO_ID("CHECK_BY_VIDEO_ID", "按视频ID检查"), + UNIQUE_RESOURCE("UNIQUE_RESOURCE", "唯一资源检查"), /** - * 按套餐ID检查(RECORDING_SET, PHOTO_SET) - * 同一个套餐不允许重复购买 + * 检查父资源/套餐 + * 检查用户是否已购买过该类型的任何商品 + * 查询逻辑:WHERE product_type = ? + * + * 适用商品: + * - RECORDING_SET:购买过任意录像集后不能再购买 + * - PHOTO_SET:购买过任意照片集后不能再购买 */ - CHECK_BY_SET_ID("CHECK_BY_SET_ID", "按套餐ID检查"), + PARENT_RESOURCE("PARENT_RESOURCE", "父资源检查"), /** * 自定义策略(通过扩展实现) diff --git a/src/main/java/com/ycwl/basic/product/service/impl/ProductTypeCapabilityManagementServiceImpl.java b/src/main/java/com/ycwl/basic/product/service/impl/ProductTypeCapabilityManagementServiceImpl.java index 57fd5fec..80110bd1 100644 --- a/src/main/java/com/ycwl/basic/product/service/impl/ProductTypeCapabilityManagementServiceImpl.java +++ b/src/main/java/com/ycwl/basic/product/service/impl/ProductTypeCapabilityManagementServiceImpl.java @@ -229,17 +229,33 @@ public class ProductTypeCapabilityManagementServiceImpl implements IProductTypeC capability.setDisplayName(productType.getDescription()); capability.setCategory(productType.getCategoryCode()); - // 根据分类设置默认的定价模式 + // 根据分类设置默认的定价模式和去重策略 if (ProductCategory.PRINT == productType.getCategory()) { + // 打印类:基于数量定价,允许重复购买 capability.setPricingMode(PricingMode.QUANTITY_BASED.getCode()); capability.setSupportsTierPricing(true); capability.setAllowDuplicatePurchase(true); capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.NO_CHECK.getCode()); - } else { + } else if (ProductCategory.PHOTO == productType.getCategory() + || ProductCategory.VIDEO == productType.getCategory() + || ProductCategory.VLOG == productType.getCategory()) { + // 照片类、视频类、Vlog类:固定价格,检查唯一资源 capability.setPricingMode(PricingMode.FIXED.getCode()); capability.setSupportsTierPricing(false); capability.setAllowDuplicatePurchase(false); - capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.CHECK_BY_SET_ID.getCode()); + + // 判断是套餐类还是独立资源类 + if (productType.getCode().endsWith("_SET")) { + capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.PARENT_RESOURCE.getCode()); + } else { + capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.UNIQUE_RESOURCE.getCode()); + } + } else { + // 其他类:默认不检查 + capability.setPricingMode(PricingMode.FIXED.getCode()); + capability.setSupportsTierPricing(false); + capability.setAllowDuplicatePurchase(true); + capability.setDuplicateCheckStrategy(DuplicateCheckStrategy.NO_CHECK.getCode()); } // 优惠能力默认全部开启 diff --git a/src/test/java/com/ycwl/basic/product/service/ProductTypeCapabilityServiceTest.java b/src/test/java/com/ycwl/basic/product/service/ProductTypeCapabilityServiceTest.java index 216f6c12..fd339a19 100644 --- a/src/test/java/com/ycwl/basic/product/service/ProductTypeCapabilityServiceTest.java +++ b/src/test/java/com/ycwl/basic/product/service/ProductTypeCapabilityServiceTest.java @@ -33,7 +33,7 @@ public class ProductTypeCapabilityServiceTest { assertEquals("VIDEO", capability.getCategory()); assertEquals(PricingMode.FIXED, capability.getPricingModeEnum()); assertEquals(false, capability.getAllowDuplicatePurchase()); - assertEquals(DuplicateCheckStrategy.CHECK_BY_VIDEO_ID, capability.getDuplicateCheckStrategyEnum()); + assertEquals(DuplicateCheckStrategy.UNIQUE_RESOURCE, capability.getDuplicateCheckStrategyEnum()); } @Test