feat(pricing): 添加查询接口并优化配置管理

- 新增多个查询接口,包括商品配置、阶梯配置和一口价配置的查询- 优化配置管理逻辑,支持 default 配置的创建和使用
- 重构部分代码,提高可维护性和可扩展性
This commit is contained in:
2025-08-15 14:54:31 +08:00
parent af5c59dc67
commit 688459d2da
13 changed files with 236 additions and 36 deletions

View File

@@ -27,6 +27,71 @@ public class PricingConfigController {
private final IPricingManagementService managementService; private final IPricingManagementService managementService;
// ==================== 查询API ====================
/**
* 获取所有商品配置
*/
@GetMapping("/products")
public ApiResponse<List<PriceProductConfig>> getAllProductConfigs() {
log.info("获取所有商品配置");
List<PriceProductConfig> configs = productConfigService.getAllProductConfigs();
return ApiResponse.success(configs);
}
/**
* 根据商品类型获取阶梯配置
*/
@GetMapping("/tiers/{productType}")
public ApiResponse<List<PriceTierConfig>> getTierConfigs(@PathVariable String productType) {
log.info("根据商品类型获取阶梯配置: {}", productType);
List<PriceTierConfig> configs = productConfigService.getTierConfigs(productType);
return ApiResponse.success(configs);
}
/**
* 根据商品类型和商品ID获取阶梯配置
*/
@GetMapping("/tiers/{productType}/{productId}")
public ApiResponse<List<PriceTierConfig>> getTierConfigs(@PathVariable String productType,
@PathVariable String productId) {
log.info("根据商品类型和ID获取阶梯配置: {}, {}", productType, productId);
List<PriceTierConfig> configs = productConfigService.getTierConfigs(productType, productId);
return ApiResponse.success(configs);
}
/**
* 根据商品类型和商品ID获取具体配置
*/
@GetMapping("/products/{productType}/{productId}")
public ApiResponse<PriceProductConfig> 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<List<PriceTierConfig>> getAllTierConfigs() {
log.info("获取所有阶梯配置");
List<PriceTierConfig> configs = productConfigService.getAllTierConfigs();
return ApiResponse.success(configs);
}
/**
* 获取所有一口价配置
*/
@GetMapping("/bundles")
public ApiResponse<List<PriceBundleConfig>> getAllBundleConfigs() {
log.info("获取所有一口价配置");
List<PriceBundleConfig> configs = bundleService.getAllBundles();
return ApiResponse.success(configs);
}
// ==================== 配置管理API(手动处理时间) ==================== // ==================== 配置管理API(手动处理时间) ====================
/** /**

View File

@@ -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;
}

View File

@@ -21,11 +21,6 @@ public class ProductItem {
*/ */
private String productId; private String productId;
/**
* 商品子类型
*/
private String productSubType;
/** /**
* 数量(如原片数量、照片数量等) * 数量(如原片数量、照片数量等)
*/ */

View File

@@ -1,10 +1,14 @@
package com.ycwl.basic.pricing.entity; package com.ycwl.basic.pricing.entity;
import com.baomidou.mybatisplus.annotation.TableName; 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.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List;
/** /**
* 一口价配置实体 * 一口价配置实体
@@ -30,14 +34,16 @@ public class PriceBundleConfig extends BaseEntity {
private BigDecimal bundlePrice; private BigDecimal bundlePrice;
/** /**
* 包含商品(JSON) * 包含商品
*/ */
private String includedProducts; @TableField(typeHandler = JacksonTypeHandler.class)
private List<BundleProductItem> includedProducts;
/** /**
* 排除商品(JSON) * 排除商品
*/ */
private String excludedProducts; @TableField(typeHandler = JacksonTypeHandler.class)
private List<BundleProductItem> excludedProducts;
/** /**
* 套餐描述 * 套餐描述

View File

@@ -41,7 +41,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
*/ */
@Insert("INSERT INTO price_bundle_config (bundle_name, scenic_id, bundle_price, included_products, excluded_products, " + @Insert("INSERT INTO price_bundle_config (bundle_name, scenic_id, bundle_price, included_products, excluded_products, " +
"description, is_active, created_time, updated_time) VALUES " + "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())") "#{description}, #{isActive}, NOW(), NOW())")
int insertBundleConfig(PriceBundleConfig config); int insertBundleConfig(PriceBundleConfig config);
@@ -49,7 +49,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
* 更新一口价配置 * 更新一口价配置
*/ */
@Update("UPDATE price_bundle_config SET bundle_name = #{bundleName}, scenic_id = #{scenicId}, bundle_price = #{bundlePrice}, " + @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}") "description = #{description}, is_active = #{isActive}, updated_time = NOW() WHERE id = #{id}")
int updateBundleConfig(PriceBundleConfig config); int updateBundleConfig(PriceBundleConfig config);

View File

@@ -34,6 +34,12 @@ public interface PriceProductConfigMapper extends BaseMapper<PriceProductConfig>
@Select("SELECT * FROM price_product_config WHERE product_type = #{productType} AND product_id = #{productId} AND is_active = 1") @Select("SELECT * FROM price_product_config WHERE product_type = #{productType} AND product_id = #{productId} AND is_active = 1")
PriceProductConfig selectByProductTypeAndId(String productType, String productId); 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);
// ==================== 管理端接口(包含禁用的配置) ==================== // ==================== 管理端接口(包含禁用的配置) ====================
/** /**

View File

@@ -49,6 +49,12 @@ public interface PriceTierConfigMapper extends BaseMapper<PriceTierConfig> {
@Select("SELECT * FROM price_tier_config WHERE is_active = 1 ORDER BY product_type ASC, sort_order ASC") @Select("SELECT * FROM price_tier_config WHERE is_active = 1 ORDER BY product_type ASC, sort_order ASC")
List<PriceTierConfig> selectAllActiveConfigs(); List<PriceTierConfig> selectAllActiveConfigs();
/**
* 检查是否存在default阶梯配置(包含禁用的)
*/
@Select("SELECT COUNT(*) FROM price_tier_config WHERE product_type = #{productType} AND product_id = 'default'")
int countDefaultTierConfigsByProductType(@Param("productType") String productType);
// ==================== 管理端接口(包含禁用的配置) ==================== // ==================== 管理端接口(包含禁用的配置) ====================
/** /**

View File

@@ -34,6 +34,13 @@ public interface IPriceBundleService {
*/ */
List<PriceBundleConfig> getActiveBundles(); List<PriceBundleConfig> getActiveBundles();
/**
* 获取所有一口价配置(仅启用的)
*
* @return 一口价配置列表
*/
List<PriceBundleConfig> getAllBundles();
// ==================== 管理端接口(包含禁用的配置) ==================== // ==================== 管理端接口(包含禁用的配置) ====================
/** /**

View File

@@ -44,6 +44,13 @@ public interface IProductConfigService {
*/ */
List<PriceProductConfig> getActiveProductConfigs(); List<PriceProductConfig> getActiveProductConfigs();
/**
* 获取所有商品配置(仅启用的)
*
* @return 商品配置列表
*/
List<PriceProductConfig> getAllProductConfigs();
/** /**
* 根据商品类型获取所有阶梯配置 * 根据商品类型获取所有阶梯配置
* *

View File

@@ -2,6 +2,7 @@ package com.ycwl.basic.pricing.service.impl;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; 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.dto.ProductItem;
import com.ycwl.basic.pricing.entity.PriceBundleConfig; import com.ycwl.basic.pricing.entity.PriceBundleConfig;
import com.ycwl.basic.pricing.enums.ProductType; import com.ycwl.basic.pricing.enums.ProductType;
@@ -76,6 +77,12 @@ public class PriceBundleServiceImpl implements IPriceBundleService {
return bundleConfigMapper.selectActiveBundles(); return bundleConfigMapper.selectActiveBundles();
} }
@Override
// @Cacheable(value = "all-bundles")
public List<PriceBundleConfig> getAllBundles() {
return bundleConfigMapper.selectActiveBundles();
}
// ==================== 管理端接口(包含禁用的配置) ==================== // ==================== 管理端接口(包含禁用的配置) ====================
@Override @Override
@@ -85,23 +92,27 @@ public class PriceBundleServiceImpl implements IPriceBundleService {
private boolean isProductsMatchBundle(Set<String> productTypes, PriceBundleConfig bundle) { private boolean isProductsMatchBundle(Set<String> productTypes, PriceBundleConfig bundle) {
try { try {
List<String> includedProducts = objectMapper.readValue( // 检查包含的商品
bundle.getIncludedProducts(), new TypeReference<List<String>>() {}); if (bundle.getIncludedProducts() != null && !bundle.getIncludedProducts().isEmpty()) {
Set<String> requiredProducts = new HashSet<>();
Set<String> requiredProducts = new HashSet<>(includedProducts); for (BundleProductItem item : bundle.getIncludedProducts()) {
requiredProducts.add(item.getType());
}
if (!productTypes.containsAll(requiredProducts)) {
return false;
}
}
// 检查排除的商品
if (bundle.getExcludedProducts() != null && !bundle.getExcludedProducts().isEmpty()) { if (bundle.getExcludedProducts() != null && !bundle.getExcludedProducts().isEmpty()) {
List<String> excludedProducts = objectMapper.readValue( for (BundleProductItem item : bundle.getExcludedProducts()) {
bundle.getExcludedProducts(), new TypeReference<List<String>>() {}); if (productTypes.contains(item.getType())) {
for (String excludedProduct : excludedProducts) {
if (productTypes.contains(excludedProduct)) {
return false; return false;
} }
} }
} }
return productTypes.containsAll(requiredProducts); return true;
} catch (Exception e) { } catch (Exception e) {
log.error("解析一口价配置失败: bundleId={}", bundle.getId(), e); log.error("解析一口价配置失败: bundleId={}", bundle.getId(), e);

View File

@@ -163,7 +163,21 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
productType, productId); 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<PriceProductConfig> configs = productConfigService.getProductConfig(productType.getCode()); List<PriceProductConfig> configs = productConfigService.getProductConfig(productType.getCode());
if (!configs.isEmpty()) { if (!configs.isEmpty()) {
PriceProductConfig baseConfig = configs.get(0); // 使用第一个配置作为默认 PriceProductConfig baseConfig = configs.get(0); // 使用第一个配置作为默认
@@ -214,7 +228,26 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
log.warn("未找到具体商品配置: productType={}, productId={}, 尝试使用通用配置", log.warn("未找到具体商品配置: productType={}, productId={}, 尝试使用通用配置",
productType, productId); productType, productId);
// 兜底:使用通用配置(向后兼容) // 兜底:使用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<PriceProductConfig> configs = productConfigService.getProductConfig(productType.getCode()); List<PriceProductConfig> configs = productConfigService.getProductConfig(productType.getCode());
if (!configs.isEmpty()) { if (!configs.isEmpty()) {
PriceProductConfig baseConfig = configs.getFirst(); // 使用第一个配置作为默认 PriceProductConfig baseConfig = configs.getFirst(); // 使用第一个配置作为默认
@@ -232,6 +265,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
} }
} }
} }
}
return new ProductPriceInfo(actualPrice, originalPrice); return new ProductPriceInfo(actualPrice, originalPrice);
} }

View File

@@ -27,6 +27,14 @@ public class PricingManagementServiceImpl implements IPricingManagementService {
@Override @Override
@Transactional @Transactional
public Long createProductConfig(PriceProductConfig config) { 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.setCreatedTime(LocalDateTime.now());
config.setUpdatedTime(LocalDateTime.now()); config.setUpdatedTime(LocalDateTime.now());
productConfigMapper.insertProductConfig(config); productConfigMapper.insertProductConfig(config);
@@ -43,6 +51,13 @@ public class PricingManagementServiceImpl implements IPricingManagementService {
@Override @Override
@Transactional @Transactional
public Long createTierConfig(PriceTierConfig config) { 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.setCreatedTime(LocalDateTime.now());
config.setUpdatedTime(LocalDateTime.now()); config.setUpdatedTime(LocalDateTime.now());
tierConfigMapper.insertTierConfig(config); tierConfigMapper.insertTierConfig(config);

View File

@@ -44,6 +44,18 @@ public class ProductConfigServiceImpl implements IProductConfigService {
// @Cacheable(value = "tier-config", key = "#productType + '_' + #productId + '_' + #quantity") // @Cacheable(value = "tier-config", key = "#productType + '_' + #productId + '_' + #quantity")
public PriceTierConfig getTierConfig(String productType, String productId, Integer quantity) { public PriceTierConfig getTierConfig(String productType, String productId, Integer quantity) {
PriceTierConfig config = tierConfigMapper.selectByProductTypeAndQuantity(productType, productId, 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) { if (config == null) {
log.warn("阶梯定价配置未找到: productType={}, productId={}, quantity={}", log.warn("阶梯定价配置未找到: productType={}, productId={}, quantity={}",
productType, productId, quantity); productType, productId, quantity);
@@ -57,6 +69,12 @@ public class ProductConfigServiceImpl implements IProductConfigService {
return productConfigMapper.selectActiveConfigs(); return productConfigMapper.selectActiveConfigs();
} }
@Override
// @Cacheable(value = "all-product-configs")
public List<PriceProductConfig> getAllProductConfigs() {
return productConfigMapper.selectActiveConfigs();
}
@Override @Override
// @Cacheable(value = "tier-configs", key = "#productType") // @Cacheable(value = "tier-configs", key = "#productType")
public List<PriceTierConfig> getTierConfigs(String productType) { public List<PriceTierConfig> getTierConfigs(String productType) {