一、功能概述
美团买菜系统的优惠券通用功能需要支持多种类型的优惠券(满减券、折扣券、无门槛券等)在多个业务场景(商品购买、配送费减免、新用户专享等)下的通用使用。
二、数据库设计
1. 优惠券表(coupon)
```sql
CREATE TABLE `coupon` (
`id` bigint NOT NULL AUTO_INCREMENT,
`coupon_name` varchar(100) NOT NULL COMMENT 优惠券名称,
`coupon_type` tinyint NOT NULL COMMENT 1-满减券 2-折扣券 3-无门槛券,
`discount_amount` decimal(10,2) DEFAULT NULL COMMENT 满减金额/折扣金额,
`discount_rate` decimal(5,2) DEFAULT NULL COMMENT 折扣率(0-100),
`min_order_amount` decimal(10,2) DEFAULT NULL COMMENT 最低订单金额,
`valid_start_time` datetime NOT NULL COMMENT 有效期开始时间,
`valid_end_time` datetime NOT NULL COMMENT 有效期结束时间,
`total_count` int DEFAULT NULL COMMENT 发放总量,
`remaining_count` int DEFAULT NULL COMMENT 剩余数量,
`status` tinyint NOT NULL DEFAULT 1 COMMENT 1-有效 0-无效,
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
```
2. 用户优惠券表(user_coupon)
```sql
CREATE TABLE `user_coupon` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 用户ID,
`coupon_id` bigint NOT NULL COMMENT 优惠券ID,
`status` tinyint NOT NULL DEFAULT 1 COMMENT 1-可用 0-已使用 2-已过期,
`get_time` datetime NOT NULL COMMENT 领取时间,
`use_time` datetime DEFAULT NULL COMMENT 使用时间,
`order_id` bigint DEFAULT NULL COMMENT 关联订单ID,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_coupon` (`user_id`,`coupon_id`)
) ENGINE=InnoDB;
```
3. 优惠券范围表(coupon_scope)
```sql
CREATE TABLE `coupon_scope` (
`id` bigint NOT NULL AUTO_INCREMENT,
`coupon_id` bigint NOT NULL COMMENT 优惠券ID,
`scope_type` tinyint NOT NULL COMMENT 1-商品分类 2-商品SKU 3-全场,
`scope_id` varchar(50) DEFAULT NULL COMMENT 分类ID或SKU_ID,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
```
三、核心功能实现
1. 优惠券发放服务
```java
public class CouponService {
@Autowired
private CouponRepository couponRepository;
@Autowired
private UserCouponRepository userCouponRepository;
/
* 发放优惠券给用户
*/
public boolean grantCoupon(Long userId, Long couponId) {
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new RuntimeException("优惠券不存在"));
if (coupon.getStatus() != 1) {
throw new RuntimeException("优惠券不可用");
}
// 检查用户是否已领取过该优惠券(根据业务需求决定是否允许重复领取)
if (userCouponRepository.existsByUserIdAndCouponIdAndStatus(userId, couponId, 1)) {
return false; // 或根据业务需求抛出异常
}
UserCoupon userCoupon = new UserCoupon();
userCoupon.setUserId(userId);
userCoupon.setCouponId(couponId);
userCoupon.setStatus(1);
userCoupon.setGetTime(new Date());
userCouponRepository.save(userCoupon);
return true;
}
}
```
2. 优惠券可用性检查
```java
public class CouponValidator {
@Autowired
private CouponRepository couponRepository;
@Autowired
private UserCouponRepository userCouponRepository;
@Autowired
private CouponScopeService couponScopeService;
/
* 检查优惠券是否可用
*/
public boolean isCouponAvailable(Long userId, Long couponId,
BigDecimal orderAmount,
List skuIds) {
// 1. 检查用户是否拥有该优惠券且有效
UserCoupon userCoupon = userCouponRepository.findByUserIdAndCouponIdAndStatus(userId, couponId, 1)
.orElseThrow(() -> new RuntimeException("优惠券不可用"));
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new RuntimeException("优惠券不存在"));
// 2. 检查优惠券是否在有效期内
Date now = new Date();
if (now.before(coupon.getValidStart()) || now.after(coupon.getValidEnd())) {
return false;
}
// 3. 检查订单金额是否满足最低要求
if (coupon.getMinOrderAmount() != null &&
orderAmount.compareTo(coupon.getMinOrderAmount()) < 0) {
return false;
}
// 4. 检查商品是否在优惠券适用范围内
if (!couponScopeService.isSkuInScope(couponId, skuIds)) {
return false;
}
return true;
}
}
```
3. 优惠券使用服务
```java
public class CouponUsageService {
@Autowired
private CouponValidator couponValidator;
@Autowired
private OrderService orderService;
@Autowired
private UserCouponRepository userCouponRepository;
/
* 使用优惠券
*/
public Order useCoupon(Long userId, Long couponId, Long orderId) {
// 1. 获取订单信息
Order order = orderService.getOrderById(orderId);
if (!order.getUserId().equals(userId)) {
throw new RuntimeException("订单不属于当前用户");
}
// 2. 验证优惠券是否可用
if (!couponValidator.isCouponAvailable(userId, couponId, order.getTotalAmount(), order.getItems())) {
throw new RuntimeException("优惠券不可用");
}
// 3. 获取优惠券详情
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new RuntimeException("优惠券不存在"));
// 4. 计算折扣后的金额
BigDecimal discountedAmount = calculateDiscountedAmount(order.getTotalAmount(), coupon);
// 5. 更新订单金额
order.setTotalAmount(discountedAmount);
order.setCouponId(couponId);
order.setDiscountAmount(order.getTotalAmount().subtract(discountedAmount));
// 6. 更新用户优惠券状态
UserCoupon userCoupon = userCouponRepository.findByUserIdAndCouponId(userId, couponId)
.orElseThrow(() -> new RuntimeException("用户优惠券记录不存在"));
userCoupon.setStatus(2); // 已使用
userCoupon.setUsedTime(new Date());
userCouponRepository.save(userCoupon);
// 7. 保存订单
return orderService.saveOrder(order);
}
private BigDecimal calculateDiscount(BigDecimal amount, Coupon coupon) {
switch (coupon.getCouponType()) {
case 1: // 满减券
return amount.subtract(coupon.getDiscountAmount());
case 2: // 折扣券
return amount.multiply(coupon.getDiscountRate()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
case 3: // 无门槛券
return amount.subtract(coupon.getDiscountAmount());
default:
return amount;
}
}
}
```
四、API接口设计
1. 领取优惠券接口
```
POST /api/coupon/receive
参数:
- userId: 用户ID
- couponId: 优惠券ID
返回:
{
"code": 200,
"message": "领取成功",
"data": null
}
```
2. 查询可用优惠券接口
```
GET /api/coupon/available
参数:
- userId: 用户ID
- orderAmount: 订单金额(可选)
- skuIds: 商品ID列表(可选)
返回:
{
"code": 200,
"message": "成功",
"data": [
{
"couponId": 123,
"couponName": "满100减20",
"couponType": 1,
"discountAmount": 20.00,
"minOrderAmount": 100.00
},
...
]
}
```
3. 使用优惠券接口
```
POST /api/coupon/use
参数:
- userId: 用户ID
- couponId: 优惠券ID
- orderId: 订单ID
返回:
{
"code": 200,
"message": "使用成功",
"data": {
"orderId": 456,
"finalAmount": 80.00,
"discountAmount": 20.00
}
}
```
五、关键业务规则
1. 优惠券有效期:优惠券必须在有效期内使用,系统需自动过滤过期优惠券
2. 最低消费金额:满减券和折扣券需满足最低消费金额才能使用
3. 优惠券叠加规则:
- 默认不允许叠加使用
- 可配置是否允许与其他优惠券叠加
- 可配置是否允许与促销活动叠加
4. 使用范围限制:
- 可限制特定商品类别
- 可限制特定商品
- 可限制新用户/老用户
5. 退款处理:
- 订单部分退款时,已使用的优惠券通常不予退还
- 订单全额退款时,可配置是否返还优惠券
六、性能优化考虑
1. 优惠券查询缓存:对用户可用优惠券列表进行缓存,减少数据库查询
2. 异步处理:优惠券发放可采用消息队列异步处理,避免高峰期系统压力
3. 分布式锁:在高并发场景下,优惠券使用需加分布式锁防止超发
4. 批量操作:优惠券发放支持批量操作,提高效率
七、测试要点
1. 边界值测试:测试刚好满足/不满足优惠券使用条件的场景
2. 并发测试:测试高并发下优惠券的发放和使用
3. 异常场景测试:测试优惠券过期、已使用、库存不足等场景
4. 组合测试:测试优惠券与促销活动、会员折扣等组合使用场景
以上是美团买菜系统优惠券通用功能的基本实现方案,可根据实际业务需求进行调整和扩展。