diff --git a/CLAUDE.md b/CLAUDE.md
index 63c9ce5..6c92571 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,6 +1,6 @@
# CLAUDE.md
-本文件为 Claude Code (claude.ai/code) 在此代码仓库中工作时提供指导。
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 构建和开发命令
@@ -120,4 +120,71 @@ mvn test -DskipTests=false
1. 在 `com.ycwl.basic.task` 包中创建类
2. 添加 `@Component` 和 `@Profile("prod")` 注解
3. 使用 `@Scheduled` 进行基于 cron 的执行
-4. 遵循现有的错误处理和日志记录模式
\ No newline at end of file
+4. 遵循现有的错误处理和日志记录模式
+
+## 价格查询系统 (Pricing Module)
+
+### 核心架构
+价格查询系统是一个独立的业务模块,位于 `com.ycwl.basic.pricing` 包中,提供商品定价、优惠券管理和价格计算功能。
+
+#### 关键组件
+- **PriceCalculationController** (`/api/pricing/calculate`):价格计算API
+- **CouponManagementController** (`/api/pricing/admin/coupons/`):优惠券管理API
+- **PricingConfigController** (`/api/pricing/config/`):价格配置管理API
+
+#### 商品类型支持
+```java
+ProductType枚举定义了支持的商品类型:
+- VLOG_VIDEO: Vlog视频
+- RECORDING_SET: 录像集
+- PHOTO_SET: 照相集
+- PHOTO_PRINT: 照片打印
+- MACHINE_PRINT: 一体机打印
+```
+
+#### 价格计算流程
+1. 接收PriceCalculationRequest(包含商品列表和用户ID)
+2. 查找商品基础配置和分层定价
+3. 处理套餐商品(BundleProductItem)
+4. 自动应用最优优惠券
+5. 返回PriceCalculationResult(包含原价、最终价格、优惠详情)
+
+#### 优惠券系统
+- **CouponType**: PERCENTAGE(百分比)、FIXED_AMOUNT(固定金额)
+- **CouponStatus**: CLAIMED(已领取)、USED(已使用)、EXPIRED(已过期)
+- 支持商品类型限制 (`applicableProducts` JSON字段)
+- 最小消费金额和最大折扣限制
+- 时间有效期控制
+
+#### 分页查询功能
+所有管理接口都支持分页查询,使用PageHelper实现:
+- 优惠券配置分页:支持按状态、名称筛选
+- 领取记录分页:支持按用户、优惠券、状态、时间范围筛选
+
+#### 统计功能
+- 基础统计:领取数、使用数、可用数
+- 详细统计:使用率、平均使用天数
+- 时间范围统计:指定时间段的整体数据分析
+
+### 开发模式
+
+#### 添加新商品类型
+1. 在ProductType枚举中添加新类型
+2. 在PriceProductConfig表中配置default配置
+3. 根据需要添加分层定价(PriceTierConfig)
+4. 更新前端产品类型映射
+
+#### 添加新优惠券类型
+1. 在CouponType枚举中添加类型
+2. 在CouponServiceImpl中实现计算逻辑
+3. 更新applicableProducts验证规则
+
+#### 自定义TypeHandler使用
+项目使用自定义TypeHandler处理复杂JSON字段:
+- `BundleProductListTypeHandler`:处理套餐商品列表JSON序列化
+
+### 测试策略
+- 单元测试:每个服务类都有对应测试类
+- 配置验证测试:DefaultConfigValidationTest验证default配置
+- JSON序列化测试:验证复杂对象的数据库存储
+- 分页功能测试:验证PageHelper集成
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index c2aac13..d839403 100644
--- a/pom.xml
+++ b/pom.xml
@@ -181,6 +181,10 @@
com.fasterxml.jackson.core
jackson-annotations
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
diff --git a/src/main/java/com/ycwl/basic/config/JacksonConfiguration.java b/src/main/java/com/ycwl/basic/config/JacksonConfiguration.java
index ba2892a..70711a4 100644
--- a/src/main/java/com/ycwl/basic/config/JacksonConfiguration.java
+++ b/src/main/java/com/ycwl/basic/config/JacksonConfiguration.java
@@ -1,10 +1,16 @@
package com.ycwl.basic.config;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
@Configuration
public class JacksonConfiguration {
@@ -13,6 +19,16 @@ public class JacksonConfiguration {
return builder -> {
// 把 Long 类型序列化为 String
builder.serializerByType(Long.class, ToStringSerializer.instance);
+
+ // 添加 JavaTimeModule 以支持 Java 8 时间类型
+ builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+ builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
};
}
-}
+
+ @Bean
+ public JavaTimeModule javaTimeModule() {
+ return new JavaTimeModule();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppVoucherController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppVoucherController.java
new file mode 100644
index 0000000..7fcd369
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/controller/mobile/AppVoucherController.java
@@ -0,0 +1,75 @@
+package com.ycwl.basic.controller.mobile;
+
+import com.ycwl.basic.constant.BaseContextHandler;
+import com.ycwl.basic.exception.BaseException;
+import com.ycwl.basic.model.pc.face.entity.FaceEntity;
+import com.ycwl.basic.pricing.dto.req.VoucherClaimReq;
+import com.ycwl.basic.pricing.dto.req.VoucherPrintReq;
+import com.ycwl.basic.pricing.dto.resp.VoucherCodeResp;
+import com.ycwl.basic.pricing.dto.resp.VoucherPrintResp;
+import com.ycwl.basic.pricing.service.VoucherCodeService;
+import com.ycwl.basic.pricing.service.VoucherPrintService;
+import com.ycwl.basic.repository.FaceRepository;
+import com.ycwl.basic.utils.ApiResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@Slf4j
+@RestController
+@RequestMapping("/api/mobile/voucher/v1")
+public class AppVoucherController {
+
+ @Autowired
+ private VoucherPrintService voucherPrintService;
+ @Autowired
+ private VoucherCodeService voucherCodeService;
+ @Autowired
+ private FaceRepository faceRepository;
+
+ /**
+ * 打印小票
+ * @param request 打印请求
+ * @return 打印结果
+ */
+ @PostMapping("/print")
+ public ApiResponse printVoucherTicket(@RequestBody VoucherPrintReq request) {
+ log.info("收到打印小票请求: faceId={}, brokerId={}, scenicId={}",
+ request.getFaceId(), request.getBrokerId(), request.getScenicId());
+
+ VoucherPrintResp response = voucherPrintService.printVoucherTicket(request);
+
+ log.info("打印小票完成: code={}, voucherCode={}, status={}",
+ response.getCode(), response.getVoucherCode(), response.getPrintStatus());
+
+ return ApiResponse.success(response);
+ }
+
+ @GetMapping("/printed")
+ public ApiResponse queryPrintedVoucher(
+ @RequestParam Long faceId
+ ) {
+ return ApiResponse.success(voucherPrintService.queryPrintedVoucher(faceId));
+ }
+
+ @PostMapping("/claim")
+ public ApiResponse claimVoucher(@RequestBody VoucherClaimReq req) {
+ FaceEntity face = faceRepository.getFace(req.getFaceId());
+ if (face == null) {
+ throw new BaseException("请选择人脸");
+ }
+ if (!face.getMemberId().equals(Long.valueOf(BaseContextHandler.getUserId()))) {
+ throw new BaseException("自动领取失败");
+ }
+ req.setScenicId(face.getScenicId());
+ VoucherCodeResp result = voucherCodeService.claimVoucher(req);
+ return ApiResponse.success(result);
+ }
+
+}
diff --git a/src/main/java/com/ycwl/basic/pricing/CLAUDE.md b/src/main/java/com/ycwl/basic/pricing/CLAUDE.md
new file mode 100644
index 0000000..c1c0ebf
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/pricing/CLAUDE.md
@@ -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> {
+ // JSON序列化/反序列化逻辑
+}
+
+// 在实体类中使用
+public class SomeEntity {
+ @TableField(typeHandler = BundleProductListTypeHandler.class)
+ private List bundleProducts;
+}
+```
+
+### 4. 异常处理模式
+
+```java
+// 自定义异常类
+public class PriceCalculationException extends RuntimeException {
+ public PriceCalculationException(String message) {
+ super(message);
+ }
+}
+
+// 在PricingExceptionHandler中统一处理
+@ExceptionHandler(PriceCalculationException.class)
+public ApiResponse handlePriceCalculationException(PriceCalculationException e) {
+ return ApiResponse.error(ErrorCode.PRICE_CALCULATION_ERROR, e.getMessage());
+}
+```
+
+### 5. 分页查询实现
+
+```java
+// 使用PageHelper实现分页
+@Override
+public PageInfo getCouponsByPage(int pageNum, int pageSize, String status, String name) {
+ PageHelper.startPage(pageNum, pageSize);
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ if (StringUtils.hasText(status)) {
+ queryWrapper.eq("status", status);
+ }
+ if (StringUtils.hasText(name)) {
+ queryWrapper.like("name", name);
+ }
+
+ List 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 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 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验证统计数据准确性
\ No newline at end of file
diff --git a/src/main/java/com/ycwl/basic/pricing/controller/CouponManagementController.java b/src/main/java/com/ycwl/basic/pricing/controller/CouponManagementController.java
new file mode 100644
index 0000000..c061981
--- /dev/null
+++ b/src/main/java/com/ycwl/basic/pricing/controller/CouponManagementController.java
@@ -0,0 +1,233 @@
+package com.ycwl.basic.pricing.controller;
+
+import com.github.pagehelper.PageInfo;
+import com.ycwl.basic.pricing.entity.PriceCouponClaimRecord;
+import com.ycwl.basic.pricing.entity.PriceCouponConfig;
+import com.ycwl.basic.pricing.enums.CouponStatus;
+import com.ycwl.basic.pricing.service.ICouponManagementService;
+import com.ycwl.basic.utils.ApiResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 优惠券管理控制器(管理端)
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/pricing/admin/coupons")
+@RequiredArgsConstructor
+public class CouponManagementController {
+
+ private final ICouponManagementService couponManagementService;
+
+ // ==================== 优惠券配置管理 ====================
+
+ /**
+ * 创建优惠券配置
+ */
+ @PostMapping("/configs")
+ public ApiResponse createCouponConfig(@RequestBody PriceCouponConfig config) {
+ log.info("创建优惠券配置: {}", config.getCouponName());
+ Long id = couponManagementService.createCouponConfig(config);
+ return ApiResponse.success(id);
+ }
+
+ /**
+ * 更新优惠券配置
+ */
+ @PutMapping("/configs/{id}")
+ public ApiResponse updateCouponConfig(@PathVariable Long id, @RequestBody PriceCouponConfig config) {
+ log.info("更新优惠券配置: id={}, name={}", id, config.getCouponName());
+ config.setId(id);
+ boolean success = couponManagementService.updateCouponConfig(config);
+ return ApiResponse.success(success);
+ }
+
+ /**
+ * 删除优惠券配置
+ */
+ @DeleteMapping("/configs/{id}")
+ public ApiResponse deleteCouponConfig(@PathVariable Long id) {
+ log.info("删除优惠券配置: id={}", id);
+ boolean success = couponManagementService.deleteCouponConfig(id);
+ return ApiResponse.success(success);
+ }
+
+ /**
+ * 启用/禁用优惠券配置
+ */
+ @PutMapping("/configs/{id}/status")
+ public ApiResponse updateCouponConfigStatus(@PathVariable Long id, @RequestParam Boolean isActive) {
+ log.info("修改优惠券配置状态: id={}, isActive={}", id, isActive);
+ boolean success = couponManagementService.updateCouponConfigStatus(id, isActive);
+ return ApiResponse.success(success);
+ }
+
+ /**
+ * 查询所有优惠券配置(包含禁用的)
+ */
+ @GetMapping("/configs")
+ public ApiResponse> getAllCouponConfigs() {
+ log.info("管理端获取所有优惠券配置");
+ List configs = couponManagementService.getAllCouponConfigs();
+ return ApiResponse.success(configs);
+ }
+
+ /**
+ * 分页查询优惠券配置
+ */
+ @GetMapping("/configs/page")
+ public ApiResponse> getCouponConfigsPage(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "10") Integer pageSize,
+ @RequestParam(required = false) Boolean isActive,
+ @RequestParam(required = false) String couponName,
+ @RequestParam(required = false) String scenicId) {
+ log.info("分页查询优惠券配置: pageNum={}, pageSize={}, isActive={}, couponName={}, scenicId={}",
+ pageNum, pageSize, isActive, couponName, scenicId);
+ PageInfo pageInfo = couponManagementService.getCouponConfigsPage(
+ pageNum, pageSize, isActive, couponName, scenicId);
+ return ApiResponse.success(pageInfo);
+ }
+
+ /**
+ * 根据状态查询优惠券配置
+ */
+ @GetMapping("/configs/status/{isActive}")
+ public ApiResponse> getCouponConfigsByStatus(@PathVariable Boolean isActive) {
+ log.info("根据状态查询优惠券配置: {}", isActive);
+ List configs = couponManagementService.getCouponConfigsByStatus(isActive);
+ return ApiResponse.success(configs);
+ }
+
+ /**
+ * 根据ID查询优惠券配置
+ */
+ @GetMapping("/configs/{id}")
+ public ApiResponse getCouponConfigById(@PathVariable Long id) {
+ log.info("根据ID查询优惠券配置: {}", id);
+ PriceCouponConfig config = couponManagementService.getCouponConfigById(id);
+ return ApiResponse.success(config);
+ }
+
+ // ==================== 优惠券领取记录查询 ====================
+
+ /**
+ * 查询所有优惠券领取记录
+ */
+ @GetMapping("/claim-records")
+ public ApiResponse> getAllClaimRecords() {
+ log.info("查询所有优惠券领取记录");
+ List records = couponManagementService.getAllClaimRecords();
+ return ApiResponse.success(records);
+ }
+
+ /**
+ * 分页查询优惠券领取记录
+ */
+ @GetMapping("/claim-records/page")
+ public ApiResponse> getClaimRecordsPage(
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "10") Integer pageSize,
+ @RequestParam(required = false) Long userId,
+ @RequestParam(required = false) Long couponId,
+ @RequestParam(required = false) CouponStatus status,
+ @RequestParam(required = false) String startTime,
+ @RequestParam(required = false) String endTime,
+ @RequestParam(required = false) String scenicId) {
+ log.info("分页查询优惠券领取记录: pageNum={}, pageSize={}, userId={}, couponId={}, status={}, startTime={}, endTime={}, scenicId={}",
+ pageNum, pageSize, userId, couponId, status, startTime, endTime, scenicId);
+ PageInfo pageInfo = couponManagementService.getClaimRecordsPage(
+ pageNum, pageSize, userId, couponId, status, startTime, endTime, scenicId);
+ return ApiResponse.success(pageInfo);
+ }
+
+ /**
+ * 根据用户ID查询优惠券领取记录
+ */
+ @GetMapping("/claim-records/user/{userId}")
+ public ApiResponse> getClaimRecordsByUserId(@PathVariable Long userId) {
+ log.info("根据用户ID查询优惠券领取记录: {}", userId);
+ List records = couponManagementService.getClaimRecordsByUserId(userId);
+ return ApiResponse.success(records);
+ }
+
+ /**
+ * 根据优惠券ID查询领取记录
+ */
+ @GetMapping("/claim-records/coupon/{couponId}")
+ public ApiResponse> getClaimRecordsByCouponId(@PathVariable Long couponId) {
+ log.info("根据优惠券ID查询领取记录: {}", couponId);
+ List records = couponManagementService.getClaimRecordsByCouponId(couponId);
+ return ApiResponse.success(records);
+ }
+
+ /**
+ * 根据状态查询领取记录
+ */
+ @GetMapping("/claim-records/status/{status}")
+ public ApiResponse> getClaimRecordsByStatus(@PathVariable CouponStatus status) {
+ log.info("根据状态查询领取记录: {}", status);
+ List records = couponManagementService.getClaimRecordsByStatus(status);
+ return ApiResponse.success(records);
+ }
+
+ // ==================== 统计功能 ====================
+
+ /**
+ * 查询优惠券使用统计
+ */
+ @GetMapping("/stats/{couponId}")
+ public ApiResponse