feat(pricing): 添加券码管理和使用功能

- 新增券码批次配置和券码实体
- 实现券码创建、领取、使用等接口
- 添加券码状态和优惠类型枚举
- 优化价格计算逻辑,支持券码优惠
- 新增优惠检测和应用相关功能
This commit is contained in:
2025-08-21 09:35:08 +08:00
parent e9035af542
commit eb327723cd
52 changed files with 2572 additions and 455 deletions

View File

@@ -0,0 +1,557 @@
# 价格查询系统 (Pricing Module) 开发指南
此文档为pricing包的专用开发指南,提供该模块的详细架构说明和开发最佳实践。
## 模块概览
价格查询系统 (`com.ycwl.basic.pricing`) 是一个独立的业务模块,提供商品定价、优惠券管理、券码管理和价格计算功能。采用分层架构设计,具备完整的CRUD操作、异常处理和数据统计功能。支持优惠券和券码的同时使用,券码优先级更高。
## 目录结构
```
com.ycwl.basic.pricing/
├── controller/ # REST API控制器层
│ ├── CouponManagementController.java # 优惠券管理API
│ ├── PriceCalculationController.java # 价格计算API
│ └── PricingConfigController.java # 价格配置管理API
├── dto/ # 数据传输对象
│ ├── BundleProductItem.java # 套餐商品项
│ ├── CouponInfo.java # 优惠券信息
│ ├── CouponUseRequest.java # 优惠券使用请求
│ ├── CouponUseResult.java # 优惠券使用结果
│ ├── DiscountDetail.java # 折扣详情
│ ├── PriceCalculationRequest.java # 价格计算请求
│ ├── PriceCalculationResult.java # 价格计算结果
│ ├── PriceDetails.java # 价格详情
│ ├── ProductItem.java # 商品项
│ ├── ProductPriceInfo.java # 商品价格信息
│ ├── VoucherInfo.java # 券码信息
│ ├── DiscountDetectionContext.java # 优惠检测上下文
│ ├── DiscountInfo.java # 优惠信息
│ ├── DiscountResult.java # 优惠结果
│ └── DiscountCombinationResult.java # 优惠组合结果
├── entity/ # 数据库实体类
│ ├── BaseEntity.java # 基础实体类
│ ├── PriceBundleConfig.java # 套餐配置
│ ├── PriceCouponClaimRecord.java # 优惠券领取记录
│ ├── PriceCouponConfig.java # 优惠券配置
│ ├── PriceProductConfig.java # 商品价格配置
│ ├── PriceTierConfig.java # 分层价格配置
│ ├── PriceVoucherBatchConfig.java # 券码批次配置
│ └── PriceVoucherCode.java # 券码实体
├── enums/ # 枚举类
│ ├── CouponStatus.java # 优惠券状态
│ ├── CouponType.java # 优惠券类型
│ ├── ProductType.java # 商品类型
│ ├── VoucherDiscountType.java # 券码优惠类型
│ └── VoucherCodeStatus.java # 券码状态
├── exception/ # 异常处理
│ ├── CouponInvalidException.java # 优惠券无效异常
│ ├── PriceCalculationException.java # 价格计算异常
│ ├── PricingExceptionHandler.java # 定价异常处理器
│ ├── ProductConfigNotFoundException.java # 商品配置未找到异常
│ ├── VoucherInvalidException.java # 券码无效异常
│ ├── VoucherAlreadyUsedException.java # 券码已使用异常
│ ├── VoucherNotClaimableException.java # 券码不可领取异常
│ └── DiscountDetectionException.java # 优惠检测异常
├── handler/ # 自定义处理器
│ └── BundleProductListTypeHandler.java # 套餐商品列表类型处理器
├── mapper/ # MyBatis数据访问层
│ ├── PriceBundleConfigMapper.java
│ ├── PriceCouponClaimRecordMapper.java
│ ├── PriceCouponConfigMapper.java
│ ├── PriceProductConfigMapper.java
│ ├── PriceTierConfigMapper.java
│ ├── PriceVoucherBatchConfigMapper.java
│ └── PriceVoucherCodeMapper.java
└── service/ # 业务逻辑层
├── ICouponManagementService.java # 优惠券管理服务接口
├── ICouponService.java # 优惠券服务接口
├── IPriceBundleService.java # 套餐服务接口
├── IPriceCalculationService.java # 价格计算服务接口
├── IPricingManagementService.java # 定价管理服务接口
├── IProductConfigService.java # 商品配置服务接口
├── IVoucherService.java # 券码服务接口
├── IDiscountProvider.java # 优惠提供者接口
├── IDiscountDetectionService.java # 优惠检测服务接口
└── impl/ # 服务实现类
├── CouponManagementServiceImpl.java
├── CouponServiceImpl.java
├── PriceBundleServiceImpl.java
├── PriceCalculationServiceImpl.java
├── PricingManagementServiceImpl.java
├── ProductConfigServiceImpl.java
├── VoucherServiceImpl.java
├── CouponDiscountProvider.java
├── VoucherDiscountProvider.java
└── DiscountDetectionServiceImpl.java
```
## 核心功能
### 1. 价格计算引擎
#### API端点
- `POST /api/pricing/calculate` - 执行价格计算
#### 计算流程
```java
// 价格计算核心流程
1. 验证PriceCalculationRequest请求参数
2. 加载商品基础配置 (PriceProductConfig)
3. 应用分层定价规则 (PriceTierConfig)
4. 处理套餐商品逻辑 (BundleProductItem)
5. 使用统一优惠检测系统处理券码和优惠券
6. 按优先级应用优惠券码 > 优惠券
7. 计算最终价格并返回详细结果
```
#### 关键类
- `PriceCalculationService`: 价格计算核心逻辑
- `PriceCalculationRequest`: 计算请求DTO
- `PriceCalculationResult`: 计算结果DTO
### 2. 优惠券管理系统
#### API端点
- `GET /api/pricing/admin/coupons/` - 分页查询优惠券配置
- `POST /api/pricing/admin/coupons/` - 创建优惠券配置
- `PUT /api/pricing/admin/coupons/{id}` - 更新优惠券配置
- `DELETE /api/pricing/admin/coupons/{id}` - 删除优惠券配置
- `GET /api/pricing/admin/coupons/{id}/claims` - 查询优惠券领取记录
- `GET /api/pricing/admin/coupons/{id}/stats` - 获取优惠券统计信息
#### 优惠券类型
```java
public enum CouponType {
PERCENTAGE, // 百分比折扣
FIXED_AMOUNT // 固定金额减免
}
public enum CouponStatus {
CLAIMED, // 已领取
USED, // 已使用
EXPIRED // 已过期
}
```
#### 关键特性
- **商品类型限制**: 通过`applicableProducts` JSON字段控制适用商品
- **消费限制**: 支持最小消费金额和最大折扣限制
- **时效性**: 基于时间的有效期控制
- **统计分析**: 完整的使用统计和分析功能
### 3. 商品配置管理
#### API端点
- `GET /api/pricing/config/products` - 查询商品配置
- `POST /api/pricing/config/products` - 创建商品配置
- `PUT /api/pricing/config/products/{id}` - 更新商品配置
#### 商品类型定义
```java
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. 添加新商品类型
```java
// 步骤1: 在ProductType枚举中添加新类型
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. 扩展优惠券类型
```java
// 步骤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使用
项目使用MyBatis自定义TypeHandler处理复杂JSON字段:
```java
// 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;
}
```
### 4. 异常处理模式
```java
// 自定义异常类
public class PriceCalculationException extends RuntimeException {
public PriceCalculationException(String message) {
super(message);
}
}
// 在PricingExceptionHandler中统一处理
@ExceptionHandler(PriceCalculationException.class)
public ApiResponse<String> handlePriceCalculationException(PriceCalculationException e) {
return ApiResponse.error(ErrorCode.PRICE_CALCULATION_ERROR, e.getMessage());
}
```
### 5. 分页查询实现
```java
// 使用PageHelper实现分页
@Override
public PageInfo<CouponConfig> getCouponsByPage(int pageNum, int pageSize, String status, String name) {
PageHelper.startPage(pageNum, pageSize);
QueryWrapper<CouponConfig> queryWrapper = new QueryWrapper<>();
if (StringUtils.hasText(status)) {
queryWrapper.eq("status", status);
}
if (StringUtils.hasText(name)) {
queryWrapper.like("name", name);
}
List<CouponConfig> list = couponConfigMapper.selectList(queryWrapper);
return new PageInfo<>(list);
}
```
## 测试策略
### 1. 单元测试
每个服务类都应有对应的测试类,重点测试:
- 价格计算逻辑的准确性
- 优惠券适用性验证
- 边界条件处理
- 异常场景覆盖
### 2. 集成测试
- 数据库操作测试
- JSON序列化测试
- 分页功能测试
- API端点测试
### 3. 配置验证测试
- `DefaultConfigValidationTest`: 验证default配置的完整性和正确性
- 确保所有ProductType都有对应的基础配置
## 数据库设计
### 核心表结构
- `price_product_config`: 商品价格基础配置
- `price_tier_config`: 分层定价配置
- `price_bundle_config`: 套餐配置
- `price_coupon_config`: 优惠券配置
- `price_coupon_claim_record`: 优惠券领取记录
### 关键字段设计
- JSON字段处理: `applicable_products`使用JSON存储适用商品类型列表
- 时间字段: 统一使用`LocalDateTime`类型
- 价格字段: 使用`BigDecimal`确保精度
- 状态字段: 使用枚举类型确保数据一致性
## 性能优化建议
1. **数据库查询优化**
- 为常用查询字段添加索引
- 使用分页查询避免大量数据加载
- 优化复杂的JOIN查询
2. **缓存策略**
- 对商品配置进行缓存,减少数据库访问
- 使用Redis缓存热点优惠券信息
3. **计算性能**
- 价格计算使用BigDecimal确保精度
- 批量计算时考虑并行处理
## 安全考虑
1. **输入验证**
- 严格验证所有输入参数
- 防止SQL注入和XSS攻击
2. **权限控制**
- 管理接口需要适当的权限验证
- 用户只能访问自己的优惠券记录
3. **数据完整性**
- 使用事务确保数据一致性
- 关键操作添加审计日志
## 券码管理系统 (Voucher System)
### 1. 核心特性
券码系统是从原`voucher`包迁移而来,现已完全集成到pricing包中,与优惠券系统并行工作。
#### 券码优惠类型
```java
public enum VoucherDiscountType {
FREE_ALL(0, "全场免费"), // 所有商品免费,优先级最高且不可叠加
REDUCE_PRICE(1, "商品降价"), // 每个商品减免固定金额
DISCOUNT(2, "商品打折") // 每个商品按百分比打折
}
```
#### 券码状态流转
```
UNCLAIMED(0) → CLAIMED_UNUSED(1) → USED(2)
未领取 → 已领取未使用 → 已使用
```
### 2. 数据库表结构
#### 券码批次配置表 (price_voucher_batch_config)
- 批次管理:支持按景区、推客创建券码批次
- 统计功能:实时统计已领取数、已使用数
- 状态控制:支持启用/禁用批次
#### 券码表 (price_voucher_code)
- 唯一性约束:每个券码全局唯一
- 用户限制:同一用户在同一景区只能领取一次券码
- 时间追踪:记录领取时间、使用时间
### 3. 关键业务规则
#### 领取限制
- 同一`faceId`在同一`scenicId`中只能领取一次券码
- 只有启用状态的批次才能领取券码
- 批次必须有可用券码才能成功领取
#### 使用验证
- 券码必须是`CLAIMED_UNUSED`状态才能使用
- 必须验证券码与景区的匹配关系
- 使用后自动更新批次统计数据
## 统一优惠检测系统 (Unified Discount Detection)
### 1. 架构设计
采用策略模式设计的可扩展优惠检测系统,支持多种优惠类型的统一管理和自动优化组合。
#### 核心接口
```java
// 优惠提供者接口
public interface IDiscountProvider {
String getProviderType(); // 提供者类型
int getPriority(); // 优先级(数字越大越高)
List<DiscountInfo> detectAvailableDiscounts(); // 检测可用优惠
DiscountResult applyDiscount(); // 应用优惠
}
// 优惠检测服务接口
public interface IDiscountDetectionService {
DiscountCombinationResult calculateOptimalCombination(); // 计算最优组合
DiscountCombinationResult previewOptimalCombination(); // 预览优惠组合
}
```
### 2. 优惠提供者实现
#### VoucherDiscountProvider (优先级: 100)
- 处理券码优惠逻辑
- 支持用户主动输入券码或自动选择最优券码
- 全场免费券码不可与其他优惠叠加
#### CouponDiscountProvider (优先级: 80)
- 处理优惠券优惠逻辑
- 自动选择最优优惠券
- 可与券码叠加使用(除全场免费券码外)
### 3. 优惠应用策略
#### 优先级规则
```
券码优惠 (Priority: 100) → 优惠券优惠 (Priority: 80)
```
#### 叠加逻辑
```java
原价 应用券码优惠 应用优惠券优惠 最终价格
特殊情况
- 全场免费券码最终价格直接为0不再应用其他优惠
- 其他券码类型可与优惠券叠加使用
```
#### 显示顺序
```
1. 券码优惠 (sortOrder: 1)
2. 限时立减 (sortOrder: 2)
3. 优惠券优惠 (sortOrder: 3)
4. 一口价优惠 (sortOrder: 4)
```
### 4. 扩展支持
#### 添加新优惠类型
```java
@Component
public class FlashSaleDiscountProvider implements IDiscountProvider {
@Override
public String getProviderType() { return "FLASH_SALE"; }
@Override
public int getPriority() { return 90; } // 介于券码和优惠券之间
// 实现其他方法...
}
```
#### 动态注册
```java
// 系统启动时自动扫描所有IDiscountProvider实现类
// 按优先级排序并注册到DiscountDetectionService中
```
## API接口扩展
### 1. 价格计算接口扩展
#### 新增请求参数
```java
public class PriceCalculationRequest {
// 原有字段...
private String voucherCode; // 用户输入的券码
private Long scenicId; // 景区ID
private Long faceId; // 用户faceId
private Boolean autoUseVoucher; // 是否自动使用券码
private Boolean previewOnly; // 是否仅预览优惠
}
```
#### 新增响应字段
```java
public class PriceCalculationResult {
// 原有字段...
private VoucherInfo usedVoucher; // 使用的券码信息
private List<DiscountInfo> availableDiscounts; // 可用优惠列表(预览模式)
}
```
### 2. 券码管理接口
#### 移动端接口
- `POST /api/pricing/mobile/voucher/claim` - 领取券码
- `GET /api/pricing/mobile/voucher/my-codes` - 我的券码列表
#### 管理端接口
- `POST /api/pricing/admin/voucher/batch/create` - 创建券码批次
- `GET /api/pricing/admin/voucher/batch/list` - 批次列表查询
- `GET /api/pricing/admin/voucher/codes` - 券码列表查询
## 开发最佳实践更新
### 1. 优惠检测开发
```java
// 检测上下文构建
DiscountDetectionContext context = new DiscountDetectionContext();
context.setUserId(userId);
context.setFaceId(faceId);
context.setScenicId(scenicId);
context.setProducts(products);
context.setCurrentAmount(amount);
context.setVoucherCode(voucherCode);
// 使用统一服务检测优惠
DiscountCombinationResult result = discountDetectionService
.calculateOptimalCombination(context);
```
### 2. 券码服务使用
```java
// 验证券码
VoucherInfo voucherInfo = voucherService.validateAndGetVoucherInfo(
voucherCode, faceId, scenicId);
// 计算券码优惠
BigDecimal discount = voucherService.calculateVoucherDiscount(
voucherInfo, context);
// 标记券码已使用
voucherService.markVoucherAsUsed(voucherCode, "订单使用");
```
### 3. 异常处理扩展
```java
// 券码相关异常
try {
// 券码操作
} catch (VoucherInvalidException e) {
// 券码无效
} catch (VoucherAlreadyUsedException e) {
// 券码已使用
} catch (VoucherNotClaimableException e) {
// 券码不可领取
}
```
## 数据库扩展
### 新增表结构
- `price_voucher_batch_config`: 券码批次配置表
- `price_voucher_code`: 券码表
### 索引优化
```sql
-- 券码查询优化
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);
```
### 性能考虑
- 券码表可能数据量较大,考虑按景区分表
- 定期清理已删除的过期数据
- 使用数据完整性检查SQL验证统计数据准确性