Files
FrameTour-BE/src/main/java/com/ycwl/basic/pricing/CLAUDE.md
Jerry Yan e9102e8e58 feat(pricing): 新增打包购买优惠功能
- 添加打包购买优惠信息类 BundleDiscountInfo
- 实现打包购买优惠提供者 BundleDiscountProvider
- 添加打包购买优惠服务接口 IBundleDiscountService 及其实现类 BundleDiscountServiceImpl
- 在 DiscountInfo 中添加 bundleDiscountInfo 字段以支持打包优惠
- 更新 CLAUDE.md 文档,详细说明打包购买优惠系统的设计和实现
2025-09-18 11:37:23 +08:00

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: 计算请求DTO
  • PriceCalculationResult: 计算结果DTO
  • IDiscountDetectionService: 统一优惠检测与组合

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 中完成注册(当前实现通过组件扫描自动注册并排序)。