feat(优惠券): 实现优惠券状态管理功能
- 新增优惠券状态管理相关实体类字段和方法 - 实现优惠券状态自动更新和手动更新功能- 添加优惠券适用范围验证逻辑 - 新增优惠券状态查询和统计接口 - 优化数据库索引和查询性能
This commit is contained in:
173
docs/COUPON_STATUS_FIX_SUMMARY.md
Normal file
173
docs/COUPON_STATUS_FIX_SUMMARY.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# 优惠券状态管理页面错误修复总结
|
||||
|
||||
## 🐛 发现的问题
|
||||
|
||||
### 1. 代码结构错误
|
||||
**位置**: `ShopUserCouponController.java` 第70-84行
|
||||
**问题**: for循环结构不完整,缺少循环体的闭合大括号
|
||||
```java
|
||||
// 错误的代码结构
|
||||
for (ShopUserCoupon userCoupon : userCouponList) {
|
||||
couponStatusService.checkAndUpdateCouponStatus(userCoupon);
|
||||
}
|
||||
ShopCoupon coupon = couponService.getById(userCoupon.getCouponId()); // 这行代码在循环外
|
||||
```
|
||||
|
||||
### 2. 实体类字段缺失
|
||||
**位置**: `ShopCouponApplyItem.java`
|
||||
**问题**: 缺少 `goodsId` 和 `categoryId` 字段,导致优惠券适用范围验证失败
|
||||
|
||||
### 3. 服务依赖注入缺失
|
||||
**位置**: `ShopUserCouponController.java`
|
||||
**问题**: 缺少 `CouponStatusService` 的注入
|
||||
|
||||
## ✅ 修复内容
|
||||
|
||||
### 1. 修复控制器代码结构
|
||||
```java
|
||||
// 修复后的正确代码
|
||||
for (ShopUserCoupon userCoupon : userCouponList) {
|
||||
// 使用新的状态管理服务检查和更新状态
|
||||
couponStatusService.checkAndUpdateCouponStatus(userCoupon);
|
||||
|
||||
ShopCoupon coupon = couponService.getById(userCoupon.getCouponId());
|
||||
coupon.setCouponApplyCateList(couponApplyCateService.list(
|
||||
new LambdaQueryWrapper<ShopCouponApplyCate>()
|
||||
.eq(ShopCouponApplyCate::getCouponId, userCoupon.getCouponId())
|
||||
));
|
||||
coupon.setCouponApplyItemList(couponApplyItemService.list(
|
||||
new LambdaQueryWrapper<ShopCouponApplyItem>()
|
||||
.eq(ShopCouponApplyItem::getCouponId, userCoupon.getCouponId())
|
||||
));
|
||||
userCoupon.setCouponItem(coupon);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 完善实体类字段
|
||||
在 `ShopCouponApplyItem.java` 中添加了必要的字段:
|
||||
```java
|
||||
@Schema(description = "优惠券ID")
|
||||
private Integer couponId;
|
||||
|
||||
@Schema(description = "商品ID")
|
||||
private Integer goodsId;
|
||||
|
||||
@Schema(description = "分类ID")
|
||||
private Integer categoryId;
|
||||
|
||||
@Schema(description = "类型(1商品 2分类)")
|
||||
private Integer type;
|
||||
```
|
||||
|
||||
### 3. 添加服务依赖注入
|
||||
在 `ShopUserCouponController.java` 中添加:
|
||||
```java
|
||||
@Resource
|
||||
private CouponStatusService couponStatusService;
|
||||
```
|
||||
|
||||
### 4. 优化适用范围验证逻辑
|
||||
在 `CouponStatusServiceImpl.java` 中改进了验证逻辑:
|
||||
```java
|
||||
private boolean validateApplyRange(ShopUserCoupon userCoupon, List<Integer> goodsIds) {
|
||||
if (userCoupon.getApplyRange() == null || userCoupon.getApplyRange() == ShopUserCoupon.APPLY_ALL) {
|
||||
return true; // 全部商品适用
|
||||
}
|
||||
|
||||
if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_GOODS) {
|
||||
// 指定商品适用
|
||||
List<ShopCouponApplyItem> applyItems = shopCouponApplyItemService.list(
|
||||
new LambdaQueryWrapper<ShopCouponApplyItem>()
|
||||
.eq(ShopCouponApplyItem::getCouponId, userCoupon.getCouponId())
|
||||
.eq(ShopCouponApplyItem::getType, 1) // 类型1表示商品
|
||||
.isNotNull(ShopCouponApplyItem::getGoodsId)
|
||||
);
|
||||
|
||||
List<Integer> applicableGoodsIds = applyItems.stream()
|
||||
.map(ShopCouponApplyItem::getGoodsId)
|
||||
.filter(goodsId -> goodsId != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return goodsIds.stream().anyMatch(applicableGoodsIds::contains);
|
||||
}
|
||||
|
||||
if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_CATEGORY) {
|
||||
// 指定分类适用 - 暂时返回true,实际项目中需要实现商品分类查询逻辑
|
||||
log.debug("分类适用范围验证暂未实现,默认通过");
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 修复的文件列表
|
||||
|
||||
1. **src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java**
|
||||
- 修复了for循环结构错误
|
||||
- 添加了CouponStatusService依赖注入
|
||||
- 集成了新的状态管理功能
|
||||
|
||||
2. **src/main/java/com/gxwebsoft/shop/entity/ShopCouponApplyItem.java**
|
||||
- 添加了goodsId字段
|
||||
- 添加了categoryId字段
|
||||
- 完善了字段注释
|
||||
|
||||
3. **src/main/java/com/gxwebsoft/shop/service/impl/CouponStatusServiceImpl.java**
|
||||
- 优化了适用范围验证逻辑
|
||||
- 添加了空值检查
|
||||
- 改进了错误处理
|
||||
|
||||
## 🧪 测试验证
|
||||
|
||||
创建了测试类 `CouponStatusServiceTest.java` 来验证:
|
||||
- 优惠券状态常量定义
|
||||
- 状态判断方法
|
||||
- 状态更新方法
|
||||
- 批量过期处理功能
|
||||
|
||||
## 📋 后续建议
|
||||
|
||||
### 1. 数据库字段同步
|
||||
确保数据库表 `shop_coupon_apply_item` 包含以下字段:
|
||||
```sql
|
||||
ALTER TABLE shop_coupon_apply_item
|
||||
ADD COLUMN goods_id INT COMMENT '商品ID',
|
||||
ADD COLUMN category_id INT COMMENT '分类ID';
|
||||
```
|
||||
|
||||
### 2. 完善分类适用范围验证
|
||||
需要实现商品分类查询逻辑,建议:
|
||||
- 创建商品分类查询服务
|
||||
- 根据商品ID查询所属分类
|
||||
- 验证分类是否在优惠券适用范围内
|
||||
|
||||
### 3. 添加单元测试
|
||||
- 为所有新增的方法添加单元测试
|
||||
- 测试各种边界情况
|
||||
- 确保异常处理正确
|
||||
|
||||
### 4. 性能优化
|
||||
- 考虑添加缓存减少数据库查询
|
||||
- 批量处理大量优惠券状态更新
|
||||
- 优化查询条件和索引
|
||||
|
||||
## ✅ 修复验证
|
||||
|
||||
修复完成后,以下功能应该正常工作:
|
||||
|
||||
1. **优惠券列表查询** - 不再出现编译错误
|
||||
2. **状态自动更新** - 过期优惠券自动标记
|
||||
3. **适用范围验证** - 商品范围验证正常
|
||||
4. **API接口调用** - 所有新增接口可正常访问
|
||||
5. **定时任务执行** - 过期处理任务正常运行
|
||||
|
||||
## 🚀 部署建议
|
||||
|
||||
1. **备份数据库** - 在部署前备份现有数据
|
||||
2. **执行SQL脚本** - 运行数据库优化脚本
|
||||
3. **重启应用** - 确保所有新功能生效
|
||||
4. **监控日志** - 观察定时任务和API调用日志
|
||||
5. **功能测试** - 验证所有优惠券功能正常
|
||||
|
||||
修复完成!现在优惠券状态管理功能应该可以正常使用了。
|
||||
281
docs/COUPON_STATUS_MANAGEMENT.md
Normal file
281
docs/COUPON_STATUS_MANAGEMENT.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# 优惠券状态管理功能说明
|
||||
|
||||
## 📋 功能概述
|
||||
|
||||
本功能实现了完整的优惠券状态管理系统,包括可用、已使用、过期三种状态的自动管理和API接口。
|
||||
|
||||
## 🎯 核心功能
|
||||
|
||||
### 1. 状态管理
|
||||
- **可用状态 (STATUS_UNUSED = 0)**: 优惠券未使用且未过期
|
||||
- **已使用状态 (STATUS_USED = 1)**: 优惠券已在订单中使用
|
||||
- **已过期状态 (STATUS_EXPIRED = 2)**: 优惠券已过期
|
||||
|
||||
### 2. 自动状态更新
|
||||
- 定时任务自动检测和更新过期优惠券
|
||||
- 查询时实时检查优惠券状态
|
||||
- 订单使用时自动更新状态
|
||||
|
||||
### 3. 状态验证
|
||||
- 订单使用前验证优惠券可用性
|
||||
- 检查最低消费金额限制
|
||||
- 验证适用商品范围
|
||||
|
||||
## 🔧 API接口
|
||||
|
||||
### 用户优惠券查询
|
||||
|
||||
#### 获取可用优惠券
|
||||
```http
|
||||
GET /api/shop/user-coupon/my/available
|
||||
```
|
||||
|
||||
#### 获取已使用优惠券
|
||||
```http
|
||||
GET /api/shop/user-coupon/my/used
|
||||
```
|
||||
|
||||
#### 获取已过期优惠券
|
||||
```http
|
||||
GET /api/shop/user-coupon/my/expired
|
||||
```
|
||||
|
||||
#### 获取优惠券统计
|
||||
```http
|
||||
GET /api/shop/user-coupon/my/statistics
|
||||
```
|
||||
|
||||
### 优惠券状态管理
|
||||
|
||||
#### 验证优惠券可用性
|
||||
```http
|
||||
POST /api/shop/coupon-status/validate
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"userCouponId": 1,
|
||||
"totalAmount": 150.00,
|
||||
"goodsIds": [1, 2, 3]
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用优惠券
|
||||
```http
|
||||
POST /api/shop/coupon-status/use
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
userCouponId=1&orderId=123&orderNo=ORDER123456
|
||||
```
|
||||
|
||||
#### 退还优惠券
|
||||
```http
|
||||
POST /api/shop/coupon-status/return/123
|
||||
```
|
||||
|
||||
## 💻 代码使用示例
|
||||
|
||||
### 1. 检查优惠券状态
|
||||
```java
|
||||
@Autowired
|
||||
private CouponStatusService couponStatusService;
|
||||
|
||||
// 获取用户可用优惠券
|
||||
List<ShopUserCoupon> availableCoupons = couponStatusService.getAvailableCoupons(userId);
|
||||
|
||||
// 检查优惠券是否可用
|
||||
ShopUserCoupon coupon = shopUserCouponService.getById(couponId);
|
||||
if (coupon.isAvailable()) {
|
||||
// 优惠券可用
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 使用优惠券
|
||||
```java
|
||||
// 验证优惠券
|
||||
CouponValidationResult result = couponStatusService.validateCouponForOrder(
|
||||
userCouponId, totalAmount, goodsIds);
|
||||
|
||||
if (result.isValid()) {
|
||||
// 使用优惠券
|
||||
boolean success = couponStatusService.useCoupon(userCouponId, orderId, orderNo);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 实体类便捷方法
|
||||
```java
|
||||
ShopUserCoupon userCoupon = shopUserCouponService.getById(id);
|
||||
|
||||
// 判断状态
|
||||
boolean available = userCoupon.isAvailable(); // 是否可用
|
||||
boolean used = userCoupon.isUsed(); // 是否已使用
|
||||
boolean expired = userCoupon.isExpired(); // 是否已过期
|
||||
|
||||
// 获取状态描述
|
||||
String statusDesc = userCoupon.getStatusDesc(); // "可使用"、"已使用"、"已过期"
|
||||
|
||||
// 标记为已使用
|
||||
userCoupon.markAsUsed(orderId, orderNo);
|
||||
|
||||
// 标记为已过期
|
||||
userCoupon.markAsExpired();
|
||||
```
|
||||
|
||||
## ⏰ 定时任务
|
||||
|
||||
### 过期优惠券处理
|
||||
- **执行时间**: 每天凌晨2点(生产环境)
|
||||
- **功能**: 自动将过期的优惠券状态更新为已过期
|
||||
- **配置**: `coupon.expire.cron`
|
||||
|
||||
### 每小时状态检查(可选)
|
||||
- **执行时间**: 每小时整点
|
||||
- **功能**: 及时发现和处理刚过期的优惠券
|
||||
- **环境**: 仅生产环境执行
|
||||
|
||||
## 🗄️ 数据库优化
|
||||
|
||||
### 索引优化
|
||||
```sql
|
||||
-- 用户优惠券状态查询索引
|
||||
CREATE INDEX idx_user_coupon_status ON shop_user_coupon(user_id, status, expire_time);
|
||||
|
||||
-- 过期优惠券查询索引
|
||||
CREATE INDEX idx_user_coupon_expire ON shop_user_coupon(expire_time) WHERE status = 0;
|
||||
|
||||
-- 订单优惠券查询索引
|
||||
CREATE INDEX idx_user_coupon_order ON shop_user_coupon(order_id) WHERE status = 1;
|
||||
```
|
||||
|
||||
### 视图简化查询
|
||||
```sql
|
||||
-- 用户可用优惠券视图
|
||||
CREATE VIEW v_user_available_coupons AS
|
||||
SELECT uc.*, c.name as coupon_name
|
||||
FROM shop_user_coupon uc
|
||||
LEFT JOIN shop_coupon c ON uc.coupon_id = c.id
|
||||
WHERE uc.deleted = 0;
|
||||
```
|
||||
|
||||
## 📊 状态统计
|
||||
|
||||
### 优惠券状态分布
|
||||
```sql
|
||||
SELECT
|
||||
status,
|
||||
CASE
|
||||
WHEN status = 0 THEN '未使用'
|
||||
WHEN status = 1 THEN '已使用'
|
||||
WHEN status = 2 THEN '已过期'
|
||||
END as status_name,
|
||||
COUNT(*) as count
|
||||
FROM shop_user_coupon
|
||||
WHERE deleted = 0
|
||||
GROUP BY status;
|
||||
```
|
||||
|
||||
### 使用率统计
|
||||
```sql
|
||||
SELECT
|
||||
DATE(create_time) as date,
|
||||
COUNT(*) as issued_count,
|
||||
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as used_count,
|
||||
ROUND(SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as usage_rate
|
||||
FROM shop_user_coupon
|
||||
WHERE deleted = 0
|
||||
GROUP BY DATE(create_time);
|
||||
```
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### application.yml 配置
|
||||
```yaml
|
||||
# 优惠券配置
|
||||
coupon:
|
||||
expire:
|
||||
# 定时任务执行时间
|
||||
cron: "0 0 2 * * ?" # 每天凌晨2点
|
||||
status:
|
||||
# 是否启用自动状态更新
|
||||
auto-update: true
|
||||
# 批量处理大小
|
||||
batch-size: 1000
|
||||
```
|
||||
|
||||
## 🚀 部署步骤
|
||||
|
||||
### 1. 执行数据库脚本
|
||||
```bash
|
||||
mysql -u root -p < src/main/resources/sql/coupon_status_optimization.sql
|
||||
```
|
||||
|
||||
### 2. 更新应用配置
|
||||
确保 `application.yml` 中包含优惠券相关配置。
|
||||
|
||||
### 3. 重启应用
|
||||
重启应用以加载新的功能和定时任务。
|
||||
|
||||
### 4. 验证功能
|
||||
- 访问 API 文档: `http://localhost:9200/doc.html`
|
||||
- 测试优惠券状态查询接口
|
||||
- 检查定时任务日志
|
||||
|
||||
## 🐛 故障排查
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **定时任务不执行**
|
||||
- 检查 `@EnableScheduling` 注解
|
||||
- 确认 cron 表达式正确
|
||||
- 查看应用日志
|
||||
|
||||
2. **状态更新不及时**
|
||||
- 检查数据库索引
|
||||
- 确认事务配置
|
||||
- 查看错误日志
|
||||
|
||||
3. **性能问题**
|
||||
- 检查数据库索引是否生效
|
||||
- 优化查询条件
|
||||
- 考虑分页查询
|
||||
|
||||
### 日志监控
|
||||
```bash
|
||||
# 查看定时任务日志
|
||||
grep "过期优惠券处理" logs/application.log
|
||||
|
||||
# 查看状态更新日志
|
||||
grep "更新优惠券状态" logs/application.log
|
||||
```
|
||||
|
||||
## 📈 性能优化建议
|
||||
|
||||
1. **数据库层面**
|
||||
- 添加必要的索引
|
||||
- 定期清理过期数据
|
||||
- 使用分区表(大数据量时)
|
||||
|
||||
2. **应用层面**
|
||||
- 使用缓存减少数据库查询
|
||||
- 批量处理状态更新
|
||||
- 异步处理非关键操作
|
||||
|
||||
3. **监控告警**
|
||||
- 监控优惠券使用率
|
||||
- 设置过期优惠券数量告警
|
||||
- 监控定时任务执行状态
|
||||
|
||||
## 🔄 后续扩展
|
||||
|
||||
1. **消息通知**
|
||||
- 优惠券即将过期提醒
|
||||
- 优惠券使用成功通知
|
||||
|
||||
2. **数据分析**
|
||||
- 优惠券使用趋势分析
|
||||
- 用户行为分析
|
||||
- ROI 计算
|
||||
|
||||
3. **高级功能**
|
||||
- 优惠券组合使用
|
||||
- 动态优惠券推荐
|
||||
- A/B 测试支持
|
||||
Reference in New Issue
Block a user