From 688459d2dacccb4f3c3da428831eb5ba1523c8c1 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 15 Aug 2025 14:54:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(pricing):=20=E6=B7=BB=E5=8A=A0=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=8E=A5=E5=8F=A3=E5=B9=B6=E4=BC=98=E5=8C=96=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增多个查询接口,包括商品配置、阶梯配置和一口价配置的查询- 优化配置管理逻辑,支持 default 配置的创建和使用 - 重构部分代码,提高可维护性和可扩展性 --- .../controller/PricingConfigController.java | 65 +++++++++++++++++++ .../basic/pricing/dto/BundleProductItem.java | 30 +++++++++ .../ycwl/basic/pricing/dto/ProductItem.java | 7 +- .../pricing/entity/PriceBundleConfig.java | 14 ++-- .../mapper/PriceBundleConfigMapper.java | 4 +- .../mapper/PriceProductConfigMapper.java | 6 ++ .../pricing/mapper/PriceTierConfigMapper.java | 6 ++ .../pricing/service/IPriceBundleService.java | 7 ++ .../service/IProductConfigService.java | 7 ++ .../service/impl/PriceBundleServiceImpl.java | 31 ++++++--- .../impl/PriceCalculationServiceImpl.java | 62 ++++++++++++++---- .../impl/PricingManagementServiceImpl.java | 15 +++++ .../impl/ProductConfigServiceImpl.java | 18 +++++ 13 files changed, 236 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/pricing/dto/BundleProductItem.java diff --git a/src/main/java/com/ycwl/basic/pricing/controller/PricingConfigController.java b/src/main/java/com/ycwl/basic/pricing/controller/PricingConfigController.java index 35d9d6b..74125f0 100644 --- a/src/main/java/com/ycwl/basic/pricing/controller/PricingConfigController.java +++ b/src/main/java/com/ycwl/basic/pricing/controller/PricingConfigController.java @@ -27,6 +27,71 @@ public class PricingConfigController { private final IPricingManagementService managementService; + // ==================== 查询API ==================== + + /** + * 获取所有商品配置 + */ + @GetMapping("/products") + public ApiResponse> getAllProductConfigs() { + log.info("获取所有商品配置"); + List configs = productConfigService.getAllProductConfigs(); + return ApiResponse.success(configs); + } + + /** + * 根据商品类型获取阶梯配置 + */ + @GetMapping("/tiers/{productType}") + public ApiResponse> getTierConfigs(@PathVariable String productType) { + log.info("根据商品类型获取阶梯配置: {}", productType); + List configs = productConfigService.getTierConfigs(productType); + return ApiResponse.success(configs); + } + + /** + * 根据商品类型和商品ID获取阶梯配置 + */ + @GetMapping("/tiers/{productType}/{productId}") + public ApiResponse> getTierConfigs(@PathVariable String productType, + @PathVariable String productId) { + log.info("根据商品类型和ID获取阶梯配置: {}, {}", productType, productId); + List configs = productConfigService.getTierConfigs(productType, productId); + return ApiResponse.success(configs); + } + + /** + * 根据商品类型和商品ID获取具体配置 + */ + @GetMapping("/products/{productType}/{productId}") + public ApiResponse getProductConfig(@PathVariable String productType, + @PathVariable String productId) { + log.info("根据商品类型和ID获取商品配置: {}, {}", productType, productId); + PriceProductConfig config = productConfigService.getProductConfig(productType, productId); + return ApiResponse.success(config); + } + + /** + * 获取所有阶梯配置 + */ + @GetMapping("/tiers") + public ApiResponse> getAllTierConfigs() { + log.info("获取所有阶梯配置"); + List configs = productConfigService.getAllTierConfigs(); + return ApiResponse.success(configs); + } + + /** + * 获取所有一口价配置 + */ + @GetMapping("/bundles") + public ApiResponse> getAllBundleConfigs() { + log.info("获取所有一口价配置"); + List configs = bundleService.getAllBundles(); + return ApiResponse.success(configs); + } + + // ==================== 配置管理API(手动处理时间) ==================== /** diff --git a/src/main/java/com/ycwl/basic/pricing/dto/BundleProductItem.java b/src/main/java/com/ycwl/basic/pricing/dto/BundleProductItem.java new file mode 100644 index 0000000..fe5a9fe --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/BundleProductItem.java @@ -0,0 +1,30 @@ +package com.ycwl.basic.pricing.dto; + +import lombok.Data; + +/** + * 一口价套餐商品项DTO + */ +@Data +public class BundleProductItem { + + /** + * 商品类型 + */ + private String type; + + /** + * 商品子类型(可选) + */ + private String subType; + + /** + * 商品ID(可选) + */ + private String productId; + + /** + * 数量 + */ + private Integer quantity; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/dto/ProductItem.java b/src/main/java/com/ycwl/basic/pricing/dto/ProductItem.java index 6ce3d6c..5bb0444 100644 --- a/src/main/java/com/ycwl/basic/pricing/dto/ProductItem.java +++ b/src/main/java/com/ycwl/basic/pricing/dto/ProductItem.java @@ -20,12 +20,7 @@ public class ProductItem { * 具体商品ID:vlog视频为具体视频ID,录像集/照相集为景区ID,打印为景区ID */ private String productId; - - /** - * 商品子类型 - */ - private String productSubType; - + /** * 数量(如原片数量、照片数量等) */ diff --git a/src/main/java/com/ycwl/basic/pricing/entity/PriceBundleConfig.java b/src/main/java/com/ycwl/basic/pricing/entity/PriceBundleConfig.java index 0d1ec9f..9be8864 100644 --- a/src/main/java/com/ycwl/basic/pricing/entity/PriceBundleConfig.java +++ b/src/main/java/com/ycwl/basic/pricing/entity/PriceBundleConfig.java @@ -1,10 +1,14 @@ package com.ycwl.basic.pricing.entity; import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.baomidou.mybatisplus.annotation.TableField; +import com.ycwl.basic.pricing.dto.BundleProductItem; import lombok.Data; import lombok.EqualsAndHashCode; import java.math.BigDecimal; +import java.util.List; /** * 一口价配置实体 @@ -30,14 +34,16 @@ public class PriceBundleConfig extends BaseEntity { private BigDecimal bundlePrice; /** - * 包含商品(JSON) + * 包含商品 */ - private String includedProducts; + @TableField(typeHandler = JacksonTypeHandler.class) + private List includedProducts; /** - * 排除商品(JSON) + * 排除商品 */ - private String excludedProducts; + @TableField(typeHandler = JacksonTypeHandler.class) + private List excludedProducts; /** * 套餐描述 diff --git a/src/main/java/com/ycwl/basic/pricing/mapper/PriceBundleConfigMapper.java b/src/main/java/com/ycwl/basic/pricing/mapper/PriceBundleConfigMapper.java index 08a7199..a0dfaf8 100644 --- a/src/main/java/com/ycwl/basic/pricing/mapper/PriceBundleConfigMapper.java +++ b/src/main/java/com/ycwl/basic/pricing/mapper/PriceBundleConfigMapper.java @@ -41,7 +41,7 @@ public interface PriceBundleConfigMapper extends BaseMapper { */ @Insert("INSERT INTO price_bundle_config (bundle_name, scenic_id, bundle_price, included_products, excluded_products, " + "description, is_active, created_time, updated_time) VALUES " + - "(#{bundleName}, #{scenicId}, #{bundlePrice}, #{includedProducts}, #{excludedProducts}, " + + "(#{bundleName}, #{scenicId}, #{bundlePrice}, #{includedProducts,typeHandler=com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler}, #{excludedProducts,typeHandler=com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler}, " + "#{description}, #{isActive}, NOW(), NOW())") int insertBundleConfig(PriceBundleConfig config); @@ -49,7 +49,7 @@ public interface PriceBundleConfigMapper extends BaseMapper { * 更新一口价配置 */ @Update("UPDATE price_bundle_config SET bundle_name = #{bundleName}, scenic_id = #{scenicId}, bundle_price = #{bundlePrice}, " + - "included_products = #{includedProducts}, excluded_products = #{excludedProducts}, " + + "included_products = #{includedProducts,typeHandler=com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler}, excluded_products = #{excludedProducts,typeHandler=com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler}, " + "description = #{description}, is_active = #{isActive}, updated_time = NOW() WHERE id = #{id}") int updateBundleConfig(PriceBundleConfig config); diff --git a/src/main/java/com/ycwl/basic/pricing/mapper/PriceProductConfigMapper.java b/src/main/java/com/ycwl/basic/pricing/mapper/PriceProductConfigMapper.java index 0f77fcb..d200921 100644 --- a/src/main/java/com/ycwl/basic/pricing/mapper/PriceProductConfigMapper.java +++ b/src/main/java/com/ycwl/basic/pricing/mapper/PriceProductConfigMapper.java @@ -34,6 +34,12 @@ public interface PriceProductConfigMapper extends BaseMapper @Select("SELECT * FROM price_product_config WHERE product_type = #{productType} AND product_id = #{productId} AND is_active = 1") PriceProductConfig selectByProductTypeAndId(String productType, String productId); + /** + * 检查是否存在default配置(包含禁用的) + */ + @Select("SELECT COUNT(*) FROM price_product_config WHERE product_type = #{productType} AND product_id = 'default'") + int countDefaultConfigsByProductType(@Param("productType") String productType); + // ==================== 管理端接口(包含禁用的配置) ==================== /** diff --git a/src/main/java/com/ycwl/basic/pricing/mapper/PriceTierConfigMapper.java b/src/main/java/com/ycwl/basic/pricing/mapper/PriceTierConfigMapper.java index 5421f65..8921078 100644 --- a/src/main/java/com/ycwl/basic/pricing/mapper/PriceTierConfigMapper.java +++ b/src/main/java/com/ycwl/basic/pricing/mapper/PriceTierConfigMapper.java @@ -49,6 +49,12 @@ public interface PriceTierConfigMapper extends BaseMapper { @Select("SELECT * FROM price_tier_config WHERE is_active = 1 ORDER BY product_type ASC, sort_order ASC") List selectAllActiveConfigs(); + /** + * 检查是否存在default阶梯配置(包含禁用的) + */ + @Select("SELECT COUNT(*) FROM price_tier_config WHERE product_type = #{productType} AND product_id = 'default'") + int countDefaultTierConfigsByProductType(@Param("productType") String productType); + // ==================== 管理端接口(包含禁用的配置) ==================== /** diff --git a/src/main/java/com/ycwl/basic/pricing/service/IPriceBundleService.java b/src/main/java/com/ycwl/basic/pricing/service/IPriceBundleService.java index 2e66341..7ff94e7 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/IPriceBundleService.java +++ b/src/main/java/com/ycwl/basic/pricing/service/IPriceBundleService.java @@ -34,6 +34,13 @@ public interface IPriceBundleService { */ List getActiveBundles(); + /** + * 获取所有一口价配置(仅启用的) + * + * @return 一口价配置列表 + */ + List getAllBundles(); + // ==================== 管理端接口(包含禁用的配置) ==================== /** diff --git a/src/main/java/com/ycwl/basic/pricing/service/IProductConfigService.java b/src/main/java/com/ycwl/basic/pricing/service/IProductConfigService.java index 11a31b8..c1d27cb 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/IProductConfigService.java +++ b/src/main/java/com/ycwl/basic/pricing/service/IProductConfigService.java @@ -44,6 +44,13 @@ public interface IProductConfigService { */ List getActiveProductConfigs(); + /** + * 获取所有商品配置(仅启用的) + * + * @return 商品配置列表 + */ + List getAllProductConfigs(); + /** * 根据商品类型获取所有阶梯配置 * diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/PriceBundleServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/PriceBundleServiceImpl.java index f0ba3eb..5101323 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/PriceBundleServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/PriceBundleServiceImpl.java @@ -2,6 +2,7 @@ package com.ycwl.basic.pricing.service.impl; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.ycwl.basic.pricing.dto.BundleProductItem; import com.ycwl.basic.pricing.dto.ProductItem; import com.ycwl.basic.pricing.entity.PriceBundleConfig; import com.ycwl.basic.pricing.enums.ProductType; @@ -76,6 +77,12 @@ public class PriceBundleServiceImpl implements IPriceBundleService { return bundleConfigMapper.selectActiveBundles(); } + @Override +// @Cacheable(value = "all-bundles") + public List getAllBundles() { + return bundleConfigMapper.selectActiveBundles(); + } + // ==================== 管理端接口(包含禁用的配置) ==================== @Override @@ -85,23 +92,27 @@ public class PriceBundleServiceImpl implements IPriceBundleService { private boolean isProductsMatchBundle(Set productTypes, PriceBundleConfig bundle) { try { - List includedProducts = objectMapper.readValue( - bundle.getIncludedProducts(), new TypeReference>() {}); - - Set requiredProducts = new HashSet<>(includedProducts); + // 检查包含的商品 + if (bundle.getIncludedProducts() != null && !bundle.getIncludedProducts().isEmpty()) { + Set requiredProducts = new HashSet<>(); + for (BundleProductItem item : bundle.getIncludedProducts()) { + requiredProducts.add(item.getType()); + } + if (!productTypes.containsAll(requiredProducts)) { + return false; + } + } + // 检查排除的商品 if (bundle.getExcludedProducts() != null && !bundle.getExcludedProducts().isEmpty()) { - List excludedProducts = objectMapper.readValue( - bundle.getExcludedProducts(), new TypeReference>() {}); - - for (String excludedProduct : excludedProducts) { - if (productTypes.contains(excludedProduct)) { + for (BundleProductItem item : bundle.getExcludedProducts()) { + if (productTypes.contains(item.getType())) { return false; } } } - return productTypes.containsAll(requiredProducts); + return true; } catch (Exception e) { log.error("解析一口价配置失败: bundleId={}", bundle.getId(), e); 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 1cf1490..59795ee 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 @@ -163,7 +163,21 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { productType, productId); } - // 兜底:使用通用配置(向后兼容) + // 兜底:使用default配置 + try { + PriceProductConfig defaultConfig = productConfigService.getProductConfig(productType.getCode(), "default"); + if (defaultConfig != null) { + if (productType == ProductType.PHOTO_PRINT || productType == ProductType.MACHINE_PRINT) { + return defaultConfig.getBasePrice().multiply(BigDecimal.valueOf(product.getQuantity())); + } else { + return defaultConfig.getBasePrice(); + } + } + } catch (Exception e) { + log.warn("未找到default配置: productType={}", productType.getCode()); + } + + // 最后兜底:使用通用配置(向后兼容) List configs = productConfigService.getProductConfig(productType.getCode()); if (!configs.isEmpty()) { PriceProductConfig baseConfig = configs.get(0); // 使用第一个配置作为默认 @@ -214,21 +228,41 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService { log.warn("未找到具体商品配置: productType={}, productId={}, 尝试使用通用配置", productType, productId); - // 兜底:使用通用配置(向后兼容) - List configs = productConfigService.getProductConfig(productType.getCode()); - if (!configs.isEmpty()) { - PriceProductConfig baseConfig = configs.getFirst(); // 使用第一个配置作为默认 - actualPrice = baseConfig.getBasePrice(); - originalPrice = baseConfig.getOriginalPrice(); - - if (productType == ProductType.PHOTO_PRINT || productType == ProductType.MACHINE_PRINT) { - actualPrice = actualPrice.multiply(BigDecimal.valueOf(product.getQuantity())); - if (originalPrice != null) { - originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity())); + // 兜底:使用default配置 + try { + PriceProductConfig defaultConfig = productConfigService.getProductConfig(productType.getCode(), "default"); + if (defaultConfig != null) { + actualPrice = defaultConfig.getBasePrice(); + originalPrice = defaultConfig.getOriginalPrice(); + + if (productType == ProductType.PHOTO_PRINT || productType == ProductType.MACHINE_PRINT) { + actualPrice = actualPrice.multiply(BigDecimal.valueOf(product.getQuantity())); + if (originalPrice != null) { + originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity())); + } } + } else { + throw new PriceCalculationException("无法找到default配置"); + } + } catch (Exception defaultEx) { + log.warn("未找到default配置: productType={}", productType.getCode()); + + // 最后兜底:使用通用配置(向后兼容) + List configs = productConfigService.getProductConfig(productType.getCode()); + if (!configs.isEmpty()) { + PriceProductConfig baseConfig = configs.getFirst(); // 使用第一个配置作为默认 + actualPrice = baseConfig.getBasePrice(); + originalPrice = baseConfig.getOriginalPrice(); + + if (productType == ProductType.PHOTO_PRINT || productType == ProductType.MACHINE_PRINT) { + actualPrice = actualPrice.multiply(BigDecimal.valueOf(product.getQuantity())); + if (originalPrice != null) { + originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity())); + } + } + } else { + throw new PriceCalculationException("无法计算商品价格: " + productType.getDescription() + ", productId: " + productId); } - } else { - throw new PriceCalculationException("无法计算商品价格: " + productType.getDescription() + ", productId: " + productId); } } } diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/PricingManagementServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/PricingManagementServiceImpl.java index 0a70889..12bfec9 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/PricingManagementServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/PricingManagementServiceImpl.java @@ -27,6 +27,14 @@ public class PricingManagementServiceImpl implements IPricingManagementService { @Override @Transactional public Long createProductConfig(PriceProductConfig config) { + // 校验:如果是default配置,确保该商品类型只能有一个default配置 + if ("default".equals(config.getProductId())) { + int existingCount = productConfigMapper.countDefaultConfigsByProductType(config.getProductType()); + if (existingCount > 0) { + throw new IllegalArgumentException("商品类型 " + config.getProductType() + " 的default配置已存在,每种商品类型只能有一个default配置"); + } + } + config.setCreatedTime(LocalDateTime.now()); config.setUpdatedTime(LocalDateTime.now()); productConfigMapper.insertProductConfig(config); @@ -43,6 +51,13 @@ public class PricingManagementServiceImpl implements IPricingManagementService { @Override @Transactional public Long createTierConfig(PriceTierConfig config) { + // 校验:如果是default配置,检查是否可以创建 + if ("default".equals(config.getProductId())) { + // 对于阶梯配置,可以有多个default配置(不同数量区间),不需要限制 + log.info("创建default阶梯配置: productType={}, quantity range: {}-{}", + config.getProductType(), config.getMinQuantity(), config.getMaxQuantity()); + } + config.setCreatedTime(LocalDateTime.now()); config.setUpdatedTime(LocalDateTime.now()); tierConfigMapper.insertTierConfig(config); diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/ProductConfigServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/ProductConfigServiceImpl.java index 9103e2b..86fb376 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/ProductConfigServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/ProductConfigServiceImpl.java @@ -44,6 +44,18 @@ public class ProductConfigServiceImpl implements IProductConfigService { // @Cacheable(value = "tier-config", key = "#productType + '_' + #productId + '_' + #quantity") public PriceTierConfig getTierConfig(String productType, String productId, Integer quantity) { PriceTierConfig config = tierConfigMapper.selectByProductTypeAndQuantity(productType, productId, quantity); + + // 如果没有找到特定商品的阶梯配置,尝试使用default配置 + if (config == null && !"default".equals(productId)) { + log.warn("阶梯定价配置未找到: productType={}, productId={}, quantity={}, 尝试使用default配置", + productType, productId, quantity); + config = tierConfigMapper.selectByProductTypeAndQuantity(productType, "default", quantity); + if (config != null) { + log.debug("使用default阶梯配置: productType={}, quantity={}, price={}", + productType, quantity, config.getPrice()); + } + } + if (config == null) { log.warn("阶梯定价配置未找到: productType={}, productId={}, quantity={}", productType, productId, quantity); @@ -57,6 +69,12 @@ public class ProductConfigServiceImpl implements IProductConfigService { return productConfigMapper.selectActiveConfigs(); } + @Override +// @Cacheable(value = "all-product-configs") + public List getAllProductConfigs() { + return productConfigMapper.selectActiveConfigs(); + } + @Override // @Cacheable(value = "tier-configs", key = "#productType") public List getTierConfigs(String productType) {