You've already forked FrameTour-BE
feat(pricing): 添加查询接口并优化配置管理
- 新增多个查询接口,包括商品配置、阶梯配置和一口价配置的查询- 优化配置管理逻辑,支持 default 配置的创建和使用 - 重构部分代码,提高可维护性和可扩展性
This commit is contained in:
@@ -27,6 +27,71 @@ public class PricingConfigController {
|
||||
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(手动处理时间) ====================
|
||||
|
||||
/**
|
||||
|
@@ -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;
|
||||
}
|
@@ -21,11 +21,6 @@ public class ProductItem {
|
||||
*/
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 商品子类型
|
||||
*/
|
||||
private String productSubType;
|
||||
|
||||
/**
|
||||
* 数量(如原片数量、照片数量等)
|
||||
*/
|
||||
|
@@ -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<BundleProductItem> includedProducts;
|
||||
|
||||
/**
|
||||
* 排除商品(JSON)
|
||||
* 排除商品
|
||||
*/
|
||||
private String excludedProducts;
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<BundleProductItem> excludedProducts;
|
||||
|
||||
/**
|
||||
* 套餐描述
|
||||
|
@@ -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, " +
|
||||
"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<PriceBundleConfig> {
|
||||
* 更新一口价配置
|
||||
*/
|
||||
@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);
|
||||
|
||||
|
@@ -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")
|
||||
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);
|
||||
|
||||
// ==================== 管理端接口(包含禁用的配置) ====================
|
||||
|
||||
/**
|
||||
|
@@ -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")
|
||||
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);
|
||||
|
||||
// ==================== 管理端接口(包含禁用的配置) ====================
|
||||
|
||||
/**
|
||||
|
@@ -34,6 +34,13 @@ public interface IPriceBundleService {
|
||||
*/
|
||||
List<PriceBundleConfig> getActiveBundles();
|
||||
|
||||
/**
|
||||
* 获取所有一口价配置(仅启用的)
|
||||
*
|
||||
* @return 一口价配置列表
|
||||
*/
|
||||
List<PriceBundleConfig> getAllBundles();
|
||||
|
||||
// ==================== 管理端接口(包含禁用的配置) ====================
|
||||
|
||||
/**
|
||||
|
@@ -44,6 +44,13 @@ public interface IProductConfigService {
|
||||
*/
|
||||
List<PriceProductConfig> getActiveProductConfigs();
|
||||
|
||||
/**
|
||||
* 获取所有商品配置(仅启用的)
|
||||
*
|
||||
* @return 商品配置列表
|
||||
*/
|
||||
List<PriceProductConfig> getAllProductConfigs();
|
||||
|
||||
/**
|
||||
* 根据商品类型获取所有阶梯配置
|
||||
*
|
||||
|
@@ -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<PriceBundleConfig> getAllBundles() {
|
||||
return bundleConfigMapper.selectActiveBundles();
|
||||
}
|
||||
|
||||
// ==================== 管理端接口(包含禁用的配置) ====================
|
||||
|
||||
@Override
|
||||
@@ -85,23 +92,27 @@ public class PriceBundleServiceImpl implements IPriceBundleService {
|
||||
|
||||
private boolean isProductsMatchBundle(Set<String> productTypes, PriceBundleConfig bundle) {
|
||||
try {
|
||||
List<String> includedProducts = objectMapper.readValue(
|
||||
bundle.getIncludedProducts(), new TypeReference<List<String>>() {});
|
||||
|
||||
Set<String> requiredProducts = new HashSet<>(includedProducts);
|
||||
// 检查包含的商品
|
||||
if (bundle.getIncludedProducts() != null && !bundle.getIncludedProducts().isEmpty()) {
|
||||
Set<String> 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<String> excludedProducts = objectMapper.readValue(
|
||||
bundle.getExcludedProducts(), new TypeReference<List<String>>() {});
|
||||
|
||||
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);
|
||||
|
@@ -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<PriceProductConfig> configs = productConfigService.getProductConfig(productType.getCode());
|
||||
if (!configs.isEmpty()) {
|
||||
PriceProductConfig baseConfig = configs.get(0); // 使用第一个配置作为默认
|
||||
@@ -214,7 +228,26 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
||||
log.warn("未找到具体商品配置: 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());
|
||||
if (!configs.isEmpty()) {
|
||||
PriceProductConfig baseConfig = configs.getFirst(); // 使用第一个配置作为默认
|
||||
@@ -232,6 +265,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ProductPriceInfo(actualPrice, originalPrice);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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<PriceProductConfig> getAllProductConfigs() {
|
||||
return productConfigMapper.selectActiveConfigs();
|
||||
}
|
||||
|
||||
@Override
|
||||
// @Cacheable(value = "tier-configs", key = "#productType")
|
||||
public List<PriceTierConfig> getTierConfigs(String productType) {
|
||||
|
Reference in New Issue
Block a user