功能概述
美团买菜系统的优惠券通用功能需要支持多种类型的优惠券(满减、折扣、无门槛等),适用于不同商品类别和用户群体,同时保证系统性能和用户体验。
核心功能模块设计
1. 优惠券类型管理
- 满减券:满X元减Y元
- 折扣券:按比例折扣(如8折)
- 无门槛券:直接减免固定金额
- 品类专用券:仅限特定品类使用
- 新人专享券:仅限新用户使用
- 限时券:指定时间段内有效
2. 优惠券生命周期管理
- 创建:支持批量生成和单张生成
- 发放:系统自动发放、用户领取、分享获得
- 使用:下单时自动匹配可用优惠券
- 失效:过期自动失效、手动作废
- 统计:使用情况统计和分析
3. 优惠券规则引擎
```java
// 规则引擎示例伪代码
public class CouponRuleEngine {
public boolean isApplicable(Coupon coupon, Order order, User user) {
// 检查用户资格
if (!checkUserEligibility(coupon, user)) {
return false;
}
// 检查商品适用性
if (!checkProductApplicability(coupon, order.getItems())) {
return false;
}
// 检查金额门槛
if (!checkAmountThreshold(coupon, order.getSubtotal())) {
return false;
}
// 检查时间有效性
if (!checkTimeValidity(coupon)) {
return false;
}
return true;
}
// 其他检查方法...
}
```
数据库设计
优惠券表(coupon)
```sql
CREATE TABLE coupon (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
type ENUM(full_reduction, discount, no_threshold) NOT NULL,
discount_rate DECIMAL(5,2) COMMENT 折扣率,如0.8表示8折,
reduction_amount DECIMAL(10,2) COMMENT 减免金额,
min_order_amount DECIMAL(10,2) COMMENT 最低订单金额,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
total_count INT DEFAULT 0 COMMENT 总发放量,
remaining_count INT DEFAULT 0 COMMENT 剩余量,
status TINYINT DEFAULT 1 COMMENT 1-有效 0-无效,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
);
```
用户优惠券表(user_coupon)
```sql
CREATE TABLE user_coupon (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
coupon_id BIGINT NOT NULL,
status TINYINT DEFAULT 0 COMMENT 0-未使用 1-已使用 2-已过期,
order_id BIGINT COMMENT 关联订单ID,
obtained_at DATETIME NOT NULL,
used_at DATETIME,
expired_at DATETIME NOT NULL,
FOREIGN KEY (coupon_id) REFERENCES coupon(id)
);
```
优惠券适用商品表(coupon_product)
```sql
CREATE TABLE coupon_product (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
coupon_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
product_category_id BIGINT COMMENT 商品分类ID,
FOREIGN KEY (coupon_id) REFERENCES coupon(id)
);
```
核心业务逻辑实现
1. 优惠券发放服务
```java
public class CouponDistributionService {
// 系统自动发放
public void distributeSystemCoupon(Long userId, Long couponId) {
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new RuntimeException("Coupon not found"));
UserCoupon userCoupon = new UserCoupon();
userCoupon.setUserId(userId);
userCoupon.setCouponId(couponId);
userCoupon.setStatus(0); // 未使用
userCoupon.setObtainedAt(new Date());
userCoupon.setExpiredAt(calculateExpiryDate(coupon));
userCouponRepository.save(userCoupon);
// 更新优惠券剩余数量
coupon.setRemainingCount(coupon.getRemainingCount() - 1);
couponRepository.save(coupon);
}
private Date calculateExpiryDate(Coupon coupon) {
// 根据优惠券类型计算过期时间
// ...
}
}
```
2. 优惠券使用服务
```java
public class CouponUsageService {
public CouponApplyResult applyCoupon(Long userId, Long orderId, Long couponId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new RuntimeException("Order not found"));
UserCoupon userCoupon = userCouponRepository.findByUserIdAndCouponIdAndStatus(
userId, couponId, 0) // 未使用
.orElseThrow(() -> new RuntimeException("Coupon not available"));
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new RuntimeException("Coupon not found"));
// 验证优惠券是否可用
if (!isCouponApplicable(coupon, order)) {
return CouponApplyResult.fail("优惠券不可用");
}
// 计算优惠金额
BigDecimal discountAmount = calculateDiscount(coupon, order);
// 更新订单信息
order.setCouponId(couponId);
order.setDiscountAmount(discountAmount);
order.setFinalAmount(order.getSubtotal().subtract(discountAmount));
orderRepository.save(order);
// 更新用户优惠券状态
userCoupon.setStatus(1); // 已使用
userCoupon.setUsedAt(new Date());
userCouponRepository.save(userCoupon);
return CouponApplyResult.success(discountAmount);
}
private boolean isCouponApplicable(Coupon coupon, Order order) {
// 实现各种验证逻辑
// 1. 时间有效性
// 2. 商品适用性
// 3. 金额门槛
// 4. 用户资格
// ...
}
private BigDecimal calculateDiscount(Coupon coupon, Order order) {
// 根据优惠券类型计算优惠金额
switch (coupon.getType()) {
case FULL_REDUCTION:
return coupon.getReductionAmount();
case DISCOUNT:
return order.getSubtotal().multiply(new BigDecimal(coupon.getDiscountRate()))
.setScale(2, RoundingMode.HALF_UP);
case NO_THRESHOLD:
return coupon.getReductionAmount();
default:
return BigDecimal.ZERO;
}
}
}
```
3. 优惠券匹配服务(下单时自动推荐最优券)
```java
public class CouponMatchingService {
public List matchAvailableCoupons(Long userId, Order order) {
List availableCoupons = userCouponRepository.findByUserIdAndStatus(
userId, 0); // 未使用的优惠券
List results = new ArrayList<>();
for (UserCoupon userCoupon : availableCoupons) {
Coupon coupon = couponRepository.findById(userCoupon.getCouponId()).get();
if (isCouponApplicable(coupon, order)) {
BigDecimal discount = calculateDiscount(coupon, order);
results.add(new CouponMatchResult(
userCoupon.getId(),
coupon.getName(),
discount,
calculateSavingsRate(order.getSubtotal(), discount)
));
}
}
// 按优惠力度排序(从高到低)
results.sort((r1, r2) -> r2.getDiscountAmount().compareTo(r1.getDiscountAmount()));
return results;
}
private BigDecimal calculateSavingsRate(BigDecimal originalAmount, BigDecimal discount) {
if (originalAmount.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
return discount.divide(originalAmount, 4, RoundingMode.HALF_UP)
.multiply(new BigDecimal("100"))
.setScale(2, RoundingMode.HALF_UP);
}
}
```
前端交互设计
1. 优惠券领取页面
- 展示可用优惠券列表
- 显示优惠券详情(使用条件、有效期等)
- "立即领取"按钮
2. 下单页面优惠券选择
- 自动匹配可用优惠券
- 显示最优优惠券预选
- 允许用户手动选择/取消选择优惠券
- 实时显示使用优惠券后的优惠金额和实付金额
3. 我的优惠券页面
- 分类展示(未使用、已使用、已过期)
- 优惠券详情查看
- 即将过期优惠券提醒
性能优化策略
1. 缓存策略:
- 缓存常用优惠券规则
- 缓存用户可用优惠券列表
2. 异步处理:
- 优惠券发放采用异步队列处理
- 优惠券使用后更新订单金额采用最终一致性
3. 数据库优化:
- 优惠券表按状态分区
- 用户优惠券表按用户ID分表
4. 预计算:
- 预计算用户可用优惠券
- 预计算商品适用优惠券
安全考虑
1. 防刷机制:
- 限制单个用户领取数量
- 限制领取频率
2. 数据校验:
- 校验优惠券有效性
- 校验订单金额是否满足条件
3. 防篡改:
- 优惠券ID使用加密传输
- 关键操作记录日志
测试用例示例
1. 正常场景:
- 用户领取并使用满减券
- 用户领取并使用折扣券
- 用户使用无门槛券
2. 边界场景:
- 订单金额刚好满足满减条件
- 订单金额略低于满减条件
- 优惠券在过期前最后一分钟使用
3. 异常场景:
- 使用已过期的优惠券
- 使用已使用的优惠券
- 使用不适用商品的优惠券
4. 并发场景:
- 多用户同时领取限量优惠券
- 用户同时使用多张优惠券
部署与监控
1. 部署方案:
- 优惠券服务独立部署
- 使用消息队列解耦发放和使用流程
2. 监控指标:
- 优惠券发放成功率
- 优惠券使用率
- 系统响应时间
- 错误率
3. 告警机制:
- 发放失败告警
- 使用异常告警
- 库存不足预警
通过以上设计,美团买菜系统的优惠券通用功能可以实现灵活、高效、安全的优惠券管理,提升用户购物体验和平台营销效果。