You've already forked FrameTour-BE
- 添加打包购买优惠信息类 BundleDiscountInfo - 实现打包购买优惠提供者 BundleDiscountProvider - 添加打包购买优惠服务接口 IBundleDiscountService 及其实现类 BundleDiscountServiceImpl - 在 DiscountInfo 中添加 bundleDiscountInfo 字段以支持打包优惠 - 更新 CLAUDE.md 文档,详细说明打包购买优惠系统的设计和实现
23 KiB
23 KiB
价格查询系统 (Pricing Module) 开发指南
此文档为 pricing 包的专用开发指南,基于当前代码实际结构进行说明与示例。
模块概览
com.ycwl.basic.pricing
提供商品定价、分层/套餐与一口价配置、优惠券管理、券码管理与使用记录、统一优惠检测与价格计算等能力。采用分层架构(controller/dto/entity/mapper/service),同时整合了 PageHelper 与 MyBatis‑Plus 的分页能力。优惠叠加采用统一检测与排序:券码 > 优惠券 > 一口价(受配置限制)。
目录结构(已对齐当前代码)
com.ycwl.basic.pricing/
├── controller/ # REST API 控制器层
│ ├── PriceCalculationController.java # 价格计算 + 用户可用券查询
│ ├── PricingConfigController.java # 产品/阶梯/套餐(一口价)配置管理(含 admin 查询)
│ ├── CouponManagementController.java # 优惠券配置/领取记录/统计(admin)
│ ├── OnePricePurchaseController.java # 一口价配置管理(admin)
│ ├── VoucherManagementController.java # 券码批次/券码/移动端领取与查询
│ └── VoucherUsageController.java # 券码使用记录与统计查询
├── dto/ # 数据传输对象
│ ├── PriceCalculationRequest.java # 价格计算请求(含voucherCode/faceId/scenicId...)
│ ├── PriceCalculationResult.java # 价格计算结果(含usedCoupon/usedVoucher/availableDiscounts)
│ ├── ProductItem.java, ProductPriceInfo.java, PriceDetails.java
│ ├── DiscountDetectionContext.java, DiscountInfo.java, DiscountDetail.java,
│ │ DiscountResult.java, DiscountCombinationResult.java
│ ├── BundleProductItem.java, MobilePriceCalculationRequest.java
│ ├── OnePriceConfigRequest.java, OnePriceInfo.java
│ ├── BundleDiscountInfo.java # 打包购买优惠信息
│ ├── req/ # 券码管理请求DTO
│ │ ├── VoucherBatchCreateReq(.java|V2)
│ │ ├── VoucherBatchQueryReq.java, VoucherCodeQueryReq.java, VoucherClaimReq.java
│ │ └── VoucherUsageHistoryReq.java
│ └── resp/ # 券码管理响应DTO
│ ├── VoucherBatchResp.java, VoucherBatchStatsResp.java
│ ├── VoucherCodeResp.java, VoucherValidationResp.java
│ ├── VoucherUsageRecordResp.java, VoucherUsageStatsResp.java, VoucherUsageSummaryResp.java
│ └── VoucherPrintResp.java
├── entity/ # 数据库实体类(MyBatis‑Plus)
│ ├── PriceProductConfig.java, PriceTierConfig.java, PriceBundleConfig.java
│ ├── PriceCouponConfig.java, PriceCouponClaimRecord.java
│ ├── PriceVoucherBatchConfig.java, PriceVoucherCode.java, PriceVoucherUsageRecord.java
│ ├── PriceOnePriceConfig.java # 一口价配置
│ └── VoucherPrintRecord.java # 券码打印记录
├── enums/ # 枚举类
│ ├── ProductType.java, CouponType.java, CouponStatus.java
│ ├── VoucherDiscountType.java, VoucherCodeStatus.java
├── exception/ # 统一异常与业务异常
│ ├── PricingExceptionHandler.java, PriceCalculationException.java
│ ├── ProductConfigNotFoundException.java, DiscountDetectionException.java
│ ├── CouponInvalidException.java, VoucherInvalidException.java,
│ ├── VoucherAlreadyUsedException.java, VoucherNotClaimableException.java
├── handler/ # MyBatis 类型处理器
│ ├── BundleProductListTypeHandler.java # 套餐商品列表 JSON ↔ 对象列表
│ └── ProductTypeListTypeHandler.java # 商品类型列表 JSON ↔ 枚举列表
├── mapper/ # 数据访问接口(多为 MyBatis‑Plus Mapper)
│ ├── PriceProductConfigMapper.java, PriceTierConfigMapper.java, PriceBundleConfigMapper.java
│ ├── PriceCouponConfigMapper.java, PriceCouponClaimRecordMapper.java
│ ├── PriceVoucherBatchConfigMapper.java, PriceVoucherCodeMapper.java, PriceVoucherUsageRecordMapper.java
│ └── PriceOnePriceConfigMapper.java, VoucherPrintRecordMapper.java
└── service/ # 业务层接口与实现
├── IPriceCalculationService.java, IDiscountDetectionService.java, IDiscountProvider.java
├── IBundleDiscountService.java # 打包购买优惠服务接口
├── IProductConfigService.java, IPricingManagementService.java, IPriceBundleService.java
├── ICouponService.java, ICouponManagementService.java
├── IOnePricePurchaseService.java, IVoucherService.java, IVoucherUsageService.java
├── VoucherBatchService.java, VoucherCodeService.java, VoucherPrintService.java
└── impl/
├── PriceCalculationServiceImpl.java, DiscountDetectionServiceImpl.java
├── ProductConfigServiceImpl.java, PricingManagementServiceImpl.java, PriceBundleServiceImpl.java
├── CouponServiceImpl.java, CouponManagementServiceImpl.java, CouponDiscountProvider.java
├── VoucherServiceImpl.java, VoucherDiscountProvider.java,
├── VoucherBatchServiceImpl.java, VoucherCodeServiceImpl.java, VoucherPrintServiceImpl.java,
├── VoucherUsageServiceImpl.java,
├── OnePricePurchaseServiceImpl.java, OnePricePurchaseDiscountProvider.java
└── BundleDiscountServiceImpl.java, BundleDiscountProvider.java # 打包购买优惠实现
核心功能
1. 价格计算引擎
API端点
POST /api/pricing/calculate
— 执行价格计算(预览模式默认开启)GET /api/pricing/coupons/my-coupons
— 查询用户可用优惠券
计算流程
// 价格计算核心流程(IPriceCalculationService / PriceCalculationServiceImpl)
1. 校验 PriceCalculationRequest(包含 voucherCode / scenicId / faceId / autoUseXXX)
2. 加载商品基础配置 (PriceProductConfig),匹配分层规则 (PriceTierConfig)、套餐等
3. 构造 DiscountDetectionContext,统一检测可用优惠(券码/优惠券/一口价)
4. 按优先级与可叠加规则应用优惠:券码 > 优惠券 > 一口价(受 canUseCoupon/canUseVoucher 控制)
5. 汇总 DiscountDetail,计算 finalAmount / discountAmount 并返回 PriceCalculationResult
关键类
IPriceCalculationService
/PriceCalculationServiceImpl
: 价格计算核心逻辑PriceCalculationRequest
: 计算请求DTOPriceCalculationResult
: 计算结果DTOIDiscountDetectionService
: 统一优惠检测与组合
2. 优惠券管理系统
API端点(管理端,见 CouponManagementController
)
POST /api/pricing/admin/coupons/configs
— 创建配置PUT /api/pricing/admin/coupons/configs/{id}
— 更新配置DELETE /api/pricing/admin/coupons/configs/{id}
— 删除配置PUT /api/pricing/admin/coupons/configs/{id}/status
— 启用/禁用GET /api/pricing/admin/coupons/configs
— 全量配置(含禁用)GET /api/pricing/admin/coupons/configs/page
— 分页查询GET /api/pricing/admin/coupons/claim-records[/*]
— 领取记录列表/分页/按条件查询GET /api/pricing/admin/coupons/stats/{couponId}[/*]
— 使用统计/明细/概览
枚举定义(简述)
// 实际枚举包含 code/description 字段,并提供 fromCode 工具方法
public enum CouponType { PERCENTAGE("percentage", ...), FIXED_AMOUNT("fixed_amount", ...) }
public enum CouponStatus { CLAIMED("claimed", ...), USED("used", ...), EXPIRED("expired", ...) }
关键特性
- 商品类型限制:通过 JSON 字段(结合
ProductTypeListTypeHandler
)控制适用商品 - 消费限制:支持最小消费金额、最大折扣限制
- 时效性:基于时间的有效期控制
- 统计分析:完整的使用统计与分析能力
3. 商品配置管理
API端点(摘)
GET /api/pricing/config/products
— 查询商品配置POST /api/pricing/config/products
— 创建商品配置PUT /api/pricing/config/products/{id}
— 更新商品配置GET /api/pricing/config/tiers[/*]
、/bundles[/*]
— 阶梯/一口价配置查询
商品类型定义(对齐实际)
// 实际定义(带 code/description,并提供 fromCode):
public enum ProductType {
VLOG_VIDEO("VLOG_VIDEO", "Vlog视频"),
RECORDING_SET("RECORDING_SET", "录像集"),
PHOTO_SET("PHOTO_SET", "照相集"),
PHOTO_PRINT("PHOTO_PRINT", "照片打印"),
MACHINE_PRINT("MACHINE_PRINT", "一体机打印");
}
分层定价
支持基于数量的分层定价策略,通过 PriceTierConfig
配置不同数量区间的单价。
开发最佳实践
1. 添加新商品类型
// 步骤1: 在 ProductType 枚举中添加新类型(含 code/description)
public enum ProductType {
// 现有类型...
NEW_PRODUCT("NEW_PRODUCT", "新商品类型");
}
// 步骤2: 在数据库中添加 default 配置
INSERT INTO price_product_config (product_type, base_price, ...)
VALUES ('NEW_PRODUCT', 100.00, ...);
// 步骤3: 添加分层定价配置(可选)
INSERT INTO price_tier_config (product_type, min_quantity, max_quantity, unit_price, ...)
VALUES ('NEW_PRODUCT', 1, 10, 95.00, ...);
// 步骤4: 更新前端产品类型映射
2. 扩展优惠券类型
// 步骤1: 在 CouponType 中添加新类型
public enum CouponType {
PERCENTAGE,
FIXED_AMOUNT,
NEW_COUPON_TYPE // 新增类型
}
// 步骤2: 在 CouponServiceImpl 中实现计算逻辑
@Override
public BigDecimal calculateDiscount(CouponConfig coupon, BigDecimal originalPrice) {
return switch (coupon.getCouponType()) {
case PERCENTAGE -> calculatePercentageDiscount(coupon, originalPrice);
case FIXED_AMOUNT -> calculateFixedAmountDiscount(coupon, originalPrice);
case NEW_COUPON_TYPE -> calculateNewTypeDiscount(coupon, originalPrice);
};
}
// 步骤3: 更新 applicableProducts 验证规则(如有)
3. 自定义 TypeHandler 使用
// BundleProductListTypeHandler 处理套餐商品列表
@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class BundleProductListTypeHandler extends BaseTypeHandler<List<BundleProductItem>> {
// JSON 序列化/反序列化逻辑
}
// 在实体类中使用
public class SomeEntity {
@TableField(typeHandler = BundleProductListTypeHandler.class)
private List<BundleProductItem> bundleProducts;
}
// ProductTypeListTypeHandler 处理商品类型列表(券码可用商品类型)
public class ProductTypeListTypeHandler extends BaseTypeHandler<List<ProductType>> { /* ... */ }
4. 异常处理模式
// 自定义异常类
public class PriceCalculationException extends RuntimeException {
public PriceCalculationException(String message) { super(message); }
}
// 在 PricingExceptionHandler 中统一处理
@ExceptionHandler(PriceCalculationException.class)
public ApiResponse<String> handlePriceCalculationException(PriceCalculationException e) {
return ApiResponse.fail(ErrorCode.PRICE_CALCULATION_ERROR, e.getMessage());
}
5. 分页查询实现
// 使用 PageHelper 实现分页(优惠券相关)
@Override
public PageInfo<CouponConfig> getCouponsByPage(int pageNum, int pageSize, String status, String name) {
PageHelper.startPage(pageNum, pageSize);
// ...
}
// 使用 MyBatis‑Plus 的 Page(券码相关)
Page<VoucherBatchResp> page = voucherBatchService.queryBatchList(req);
Page<VoucherCodeResp> page = voucherCodeService.queryCodeList(req);
券码管理系统 (Voucher System)
1. 核心特性
券码系统由原独立模块迁移并完全集成到 pricing 包中,与优惠券系统并行工作。
券码优惠类型(简述)
public enum VoucherDiscountType {
FREE_ALL(0, "全场免费"), // 优先级最高,且不可叠加
REDUCE_PRICE(1, "商品降价"), // 每个商品减免固定金额
DISCOUNT(2, "商品打折"); // 每个商品按百分比打折
}
券码状态(简述)
UNCLAIMED(0) → CLAIMED_AVAILABLE/CLAIMED_UNUSED(1) → USED(2) → CLAIMED_EXHAUSTED(3) → EXPIRED(4)
2. 数据库表结构
券码批次配置表 (price_voucher_batch_config)
- 批次管理:支持按景区、推客创建券码批次
- 统计功能:实时统计已领取数、已使用数
- 状态控制:支持启用/禁用批次
券码表 (price_voucher_code)
- 唯一性约束:每个券码全局唯一
- 用户限制:同一用户在同一景区只能领取一次券码
- 时间追踪:记录领取时间、使用时间
3. API 概览(对齐当前控制器)
管理端/服务端:VoucherManagementController
POST /api/pricing/voucher/batch/create
、/batch/create/v2
— 创建券码批次POST /api/pricing/voucher/batch/list
— 批次分页列表GET /api/pricing/voucher/batch/{id}
— 批次详情GET /api/pricing/voucher/batch/{id}/stats
— 批次统计PUT /api/pricing/voucher/batch/{id}/status
— 批次启用/禁用POST /api/pricing/voucher/codes
— 券码分页列表PUT /api/pricing/voucher/code/{id}/use
— 标记某券码为已使用GET /api/pricing/voucher/scenic/{scenicId}/users
— 景区用户券码概览
移动端:VoucherManagementController
POST /api/pricing/voucher/mobile/claim
— 领取券码GET /api/pricing/voucher/mobile/my-codes
— 我的券码列表
使用记录:VoucherUsageController
POST /api/pricing/voucher/usage/history
— 使用记录分页GET /api/pricing/voucher/usage/{voucherCode}/records
— 按券码查询记录GET /api/pricing/voucher/usage/{voucherCode}/stats
— 券码统计GET /api/pricing/voucher/usage/user/{faceId}/scenic/{scenicId}
— 用户在景区的记录GET /api/pricing/voucher/usage/batch/{batchId}/stats
— 批次使用统计
4. 关键业务规则
领取限制
- 同一
faceId
在同一scenicId
中只能领取一次券码 - 只有启用状态的批次才能领取券码
- 批次必须有可用券码才能成功领取
使用验证
- 券码必须为可用状态(CLAIMED_AVAILABLE/CLAIMED_UNUSED)
- 必须验证券码与景区的匹配关系
- 使用后自动更新批次统计数据
统一优惠检测系统 (Unified Discount Detection)
1. 架构设计
采用策略模式的可扩展优惠检测系统,统一管理并自动组合多种优惠类型。
核心接口
// 优惠提供者接口
public interface IDiscountProvider {
String getProviderType(); // 提供者类型
int getPriority(); // 优先级(数字越大越高)
List<DiscountInfo> detectAvailableDiscounts(); // 检测可用优惠
DiscountResult applyDiscount(); // 应用优惠
}
// 优惠检测服务接口
public interface IDiscountDetectionService {
DiscountCombinationResult calculateOptimalCombination(); // 计算最优组合
DiscountCombinationResult previewOptimalCombination(); // 预览优惠组合
}
2. 优惠提供者实现(当前实现与优先级)
OnePricePurchaseDiscountProvider (优先级: 120)
- 处理一口价优惠逻辑(景区级统一价格)
- 最高优先级,优先于所有其他优惠类型
- 仅当一口价小于当前金额时产生优惠;是否可与券码/优惠券叠加由配置
canUseCoupon/canUseVoucher
决定
BundleDiscountProvider (优先级: 100)
- 处理打包购买优惠逻辑(多商品组合优惠)
- 支持多种优惠类型:固定减免、百分比折扣、固定价格
- 可配置叠加规则(与优惠券、券码、一口价的组合限制)
- 自动检测购物车中符合条件的商品组合
VoucherDiscountProvider (优先级: 80)
- 处理券码优惠逻辑
- 支持用户主动输入券码或自动选择最优券码
- 全场免费券码不可与其他优惠叠加
CouponDiscountProvider (优先级: 60)
- 处理优惠券优惠逻辑
- 最低优先级,在所有其他优惠之后应用
- 自动选择最优优惠券
3. 优惠应用策略
优先级规则
一口价 (120) → 打包购买 (100) → 券码 (80) → 优惠券 (60)
叠加逻辑
原价 → 一口价 → 打包购买 → 券码 → 优惠券 → 最终价格
特殊情况:
- 全场免费券码:直接最终价=0,停止后续优惠
- 一口价:可叠加性由配置 canUseCoupon / canUseVoucher 控制
扩展支持
添加新优惠类型
@Component
public class FlashSaleDiscountProvider implements IDiscountProvider {
@Override
public String getProviderType() { return "LIMITED_TIME"; }
@Override
public int getPriority() { return 90; } // 示例:介于券码和优惠券之间
// 实现其他方法...
}
动态注册
// 系统启动时自动扫描所有 IDiscountProvider 实现类
// 按优先级排序并注册到 DiscountDetectionService 中
打包购买优惠系统 (Bundle Discount System)
1. 核心特性
打包购买优惠系统是新增的优惠类型,支持多商品组合优惠策略,具有第二高优先级(仅次于一口价)。
优惠类型支持
- FIXED_DISCOUNT: 固定减免金额(如满2件减50元)
- PERCENTAGE_DISCOUNT: 百分比折扣(如多商品组合9折)
- FIXED_PRICE: 固定套餐价格(如照片+视频套餐199元)
触发条件
- 商品数量要求: 最低购买数量限制
- 商品金额要求: 最低购买金额限制
- 商品类型组合: 特定商品类型的组合(如照片+视频)
2. 业务规则
自动检测规则
// 多商品类型组合优惠
- 条件:购买不同类型商品 >= 2种
- 优惠:9折优惠
- 可叠加:可与优惠券、券码叠加,不可与一口价叠加
// 大批量购买优惠
- 条件:总数量 >= 10件 且 总金额 >= 500元
- 优惠:减免50元
- 可叠加:可与优惠券、券码叠加,不可与一口价叠加
// 特定组合套餐
- 条件:同时购买照片集和Vlog视频
- 优惠:套餐价199元
- 可叠加:不可与其他优惠叠加
叠加规则配置
每个打包优惠规则都可以独立配置与其他优惠的叠加关系:
canUseWithCoupon
: 是否可与优惠券叠加canUseWithVoucher
: 是否可与券码叠加canUseWithOnePrice
: 是否可与一口价叠加
3. 核心接口
IBundleDiscountService
// 检测可用的打包优惠
List<BundleDiscountInfo> detectAvailableBundleDiscounts(DiscountDetectionContext context);
// 计算打包优惠金额
BigDecimal calculateBundleDiscount(BundleDiscountInfo bundleDiscount, List<ProductItem> products);
// 获取最优的打包优惠组合
BundleDiscountInfo getBestBundleDiscount(List<ProductItem> products, Long scenicId);
BundleDiscountProvider
- 实现
IDiscountProvider
接口 - 优先级:100(第二高,仅次于一口价的120)
- 自动集成到统一优惠检测系统
4. 扩展开发
添加新的打包规则
// 在 BundleDiscountServiceImpl 中添加新规则
private BundleDiscountInfo createNewBundleRule() {
BundleDiscountInfo bundle = new BundleDiscountInfo();
bundle.setBundleConfigId(4L);
bundle.setBundleName("新打包规则");
bundle.setDiscountType("PERCENTAGE_DISCOUNT");
bundle.setDiscountValue(new BigDecimal("0.85")); // 8.5折
bundle.setMinQuantity(5);
bundle.setMinAmount(new BigDecimal("300"));
// 配置叠加规则...
return bundle;
}
数据库配置支持
后续可以扩展为从数据库加载打包规则配置,替换当前的硬编码规则。
API 接口扩展
1. 价格计算接口扩展
新增请求参数(已存在)
public class PriceCalculationRequest {
// 原有字段...
private String voucherCode; // 用户输入的券码
private Long scenicId; // 景区ID
private Long faceId; // 用户faceId
private Boolean autoUseVoucher; // 是否自动使用券码
private Boolean previewOnly; // 是否仅预览优惠
}
新增响应字段(已存在)
public class PriceCalculationResult {
// 原有字段...
private VoucherInfo usedVoucher; // 使用的券码信息
private List<DiscountInfo> availableDiscounts; // 可用优惠列表(预览模式)
}
2. 一口价配置管理(OnePrice)
OnePricePurchaseController
(管理端):
GET /api/pricing/admin/one-price/
— 分页查询GET /api/pricing/admin/one-price/all
— 全量查询GET /api/pricing/admin/one-price/{id}
— 详情POST /api/pricing/admin/one-price/
— 创建PUT /api/pricing/admin/one-price/{id}
— 更新DELETE /api/pricing/admin/one-price/{id}
— 删除PUT /api/pricing/admin/one-price/{id}/status
— 启用/禁用GET /api/pricing/admin/one-price/scenic/{scenicId}
— 按景区查询启用配置GET /api/pricing/admin/one-price/check/{scenicId}
— 景区是否适用一口价
测试策略
1. 单元测试
建议覆盖:
- 价格计算核心流程与边界
- 优惠券/券码/一口价适用性与叠加规则
- 异常场景与异常处理器
2. 集成测试
- 数据库读写与分页
- JSON 序列化/反序列化(TypeHandler)
- API 端点的入参/出参校验
3. 配置校验
- 校验各 ProductType 的默认配置完整性
- 关键枚举与配置代码路径的兼容性
数据库设计
核心表结构(摘)
price_product_config
: 商品价格基础配置price_tier_config
: 分层定价配置price_bundle_config
: 套餐配置price_coupon_config
: 优惠券配置price_coupon_claim_record
: 优惠券领取记录
新增表结构
price_voucher_batch_config
: 券码批次配置表price_voucher_code
: 券码表price_voucher_usage_record
: 券码使用记录表voucher_print_record
: 券码打印记录表price_one_price_config
: 一口价配置表
索引优化(示例)
-- 券码查询优化
CREATE INDEX idx_voucher_code ON price_voucher_code(code);
CREATE INDEX idx_face_scenic ON price_voucher_code(face_id, scenic_id);
-- 批次查询优化
CREATE INDEX idx_scenic_broker ON price_voucher_batch_config(scenic_id, broker_id);
-- 使用记录与打印记录查询优化(示例)
CREATE INDEX idx_usage_code ON price_voucher_usage_record(voucher_code);
CREATE INDEX idx_usage_face_scenic ON price_voucher_usage_record(face_id, scenic_id);
CREATE INDEX idx_print_face_scenic ON voucher_print_record(face_id, scenic_id);
性能考虑
- 券码表可能数据量较大,考虑按景区维度分表或归档
- 定期清理已删除的过期数据
- 使用数据完整性检查 SQL 验证统计数据准确性
兼容性与注意事项
- 本模块使用 PageHelper(优惠券相关)与 MyBatis‑Plus(券码/一口价等)并存,请根据对应 Service/Mapper 选择分页与查询方式。
- 优惠优先级及叠加规则以各 Provider 与业务配置为准,避免在外层重复实现优先级判断逻辑。
- 若扩展新的优惠类型,务必实现
IDiscountProvider
并在IDiscountDetectionService
中完成注册(当前实现通过组件扫描自动注册并排序)。