一、功能概述
美团买菜系统的优惠券通用功能需要支持多种类型的优惠券(满减券、折扣券、无门槛券等),能够在不同商品类别、不同业务场景下灵活使用,同时保证系统的性能和安全性。
二、系统架构设计
1. 核心模块划分
- 优惠券模板管理:定义优惠券规则和属性
- 优惠券发放中心:管理优惠券的生成和分发
- 优惠券使用引擎:处理优惠券的核销逻辑
- 用户优惠券管理:管理用户领取和持有的优惠券
- 统计与分析模块:监控优惠券使用情况
2. 数据库设计
```
优惠券模板表(coupon_template)
- id: 主键
- name: 优惠券名称
- type: 类型(满减/折扣/无门槛)
- discount_amount: 折扣金额(满减用)
- discount_rate: 折扣率(折扣券用)
- min_order_amount: 最低订单金额
- valid_days: 有效天数
- start_time: 生效时间
- end_time: 失效时间
- scope: 使用范围(全平台/特定品类/特定商品)
- status: 状态(有效/失效)
用户优惠券表(user_coupon)
- id: 主键
- user_id: 用户ID
- coupon_template_id: 模板ID
- coupon_code: 优惠券码
- status: 状态(未使用/已使用/已过期)
- get_time: 领取时间
- use_time: 使用时间
- order_id: 关联订单ID
```
三、核心功能实现
1. 优惠券发放实现
```java
// 优惠券发放服务接口
public interface CouponDistributionService {
/
* 发放优惠券给用户
* @param userId 用户ID
* @param templateId 优惠券模板ID
* @param quantity 发放数量
* @return 发放结果
*/
DistributionResult distributeCoupon(Long userId, Long templateId, int quantity);
}
// 实现类示例
@Service
public class CouponDistributionServiceImpl implements CouponDistributionService {
@Autowired
private CouponTemplateRepository templateRepository;
@Autowired
private UserCouponRepository userCouponRepository;
@Override
public DistributionResult distributeCoupon(Long userId, Long templateId, int quantity) {
// 1. 验证模板是否存在且有效
CouponTemplate template = templateRepository.findById(templateId)
.orElseThrow(() -> new RuntimeException("优惠券模板不存在"));
if (!template.getStatus().equals(CouponStatus.ACTIVE)) {
throw new RuntimeException("优惠券模板已失效");
}
// 2. 生成用户优惠券记录
List coupons = new ArrayList<>();
for (int i = 0; i < quantity; i++) {
UserCoupon coupon = new UserCoupon();
coupon.setUserId(userId);
coupon.setCouponTemplateId(templateId);
coupon.setCouponCode(generateCouponCode());
coupon.setStatus(CouponStatus.UNUSED);
coupons.add(coupon);
}
// 3. 批量保存
userCouponRepository.saveAll(coupons);
return new DistributionResult(coupons.size(), "发放成功");
}
private String generateCouponCode() {
// 生成唯一优惠券码逻辑
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
}
}
```
2. 优惠券使用引擎实现
```java
// 优惠券使用服务
public interface CouponUsageService {
/
* 检查优惠券是否可用
* @param userId 用户ID
* @param couponCode 优惠券码
* @param orderAmount 订单金额
* @param itemIds 商品ID列表
* @return 可用性检查结果
*/
CouponAvailabilityCheckResult checkCouponAvailability(Long userId, String couponCode,
BigDecimal orderAmount, List itemIds);
/
* 使用优惠券
* @param userId 用户ID
* @param couponCode 优惠券码
* @param orderId 订单ID
* @return 使用结果
*/
CouponUsageResult useCoupon(Long userId, String couponCode, Long orderId);
}
// 实现类示例
@Service
public class CouponUsageServiceImpl implements CouponUsageService {
@Autowired
private UserCouponRepository userCouponRepository;
@Autowired
private CouponTemplateRepository templateRepository;
@Autowired
private ItemService itemService;
@Override
public CouponAvailabilityCheckResult checkCouponAvailability(Long userId, String couponCode,
BigDecimal orderAmount, List itemIds) {
// 1. 查找用户优惠券
UserCoupon userCoupon = userCouponRepository.findByUserIdAndCouponCodeAndStatus(
userId, couponCode, CouponStatus.UNUSED)
.orElseThrow(() -> new RuntimeException("优惠券不存在或不可用"));
// 2. 获取优惠券模板
CouponTemplate template = templateRepository.findById(userCoupon.getCouponTemplateId())
.orElseThrow(() -> new RuntimeException("优惠券模板不存在"));
// 3. 检查有效期
if (LocalDateTime.now().isBefore(template.getStartTime()) ||
LocalDateTime.now().isAfter(template.getEndTime())) {
return new CouponAvailabilityCheckResult(false, "优惠券已过期");
}
// 4. 检查最低消费金额
if (template.getMinOrderAmount().compareTo(orderAmount) > 0) {
return new CouponAvailabilityCheckResult(false,
"订单金额需满" + template.getMinOrderAmount() + "元");
}
// 5. 检查商品范围(如果有限制)
if (template.getScope() == CouponScope.SPECIFIC_ITEMS) {
List validItemIds = itemService.getValidItemIdsForCoupon(template.getId());
if (!validItemIds.containsAll(itemIds)) {
return new CouponAvailabilityCheckResult(false, "部分商品不可用此优惠券");
}
}
return new CouponAvailabilityCheckResult(true, "可用");
}
@Override
@Transactional
public CouponUsageResult useCoupon(Long userId, String couponCode, Long orderId) {
// 1. 检查可用性(可复用上面的检查逻辑)
CouponAvailabilityCheckResult checkResult = checkCouponAvailability(userId, couponCode,
null, null); // 实际使用时需要传入正确参数
if (!checkResult.isAvailable()) {
throw new RuntimeException(checkResult.getMessage());
}
// 2. 查找用户优惠券
UserCoupon userCoupon = userCouponRepository.findByUserIdAndCouponCodeAndStatus(
userId, couponCode, CouponStatus.UNUSED)
.orElseThrow(() -> new RuntimeException("优惠券不存在或不可用"));
// 3. 更新优惠券状态
userCoupon.setStatus(CouponStatus.USED);
userCoupon.setUseTime(LocalDateTime.now());
userCoupon.setOrderId(orderId);
userCouponRepository.save(userCoupon);
return new CouponUsageResult(userCoupon.getId(), "使用成功");
}
}
```
3. 优惠券计算引擎
```java
// 优惠券计算服务
public interface CouponCalculationService {
/
* 计算订单优惠金额
* @param orderAmount 订单金额
* @param couponId 优惠券ID
* @return 优惠金额
*/
BigDecimal calculateDiscount(BigDecimal orderAmount, Long couponId);
}
@Service
public class CouponCalculationServiceImpl implements CouponCalculationService {
@Autowired
private CouponTemplateRepository templateRepository;
@Override
public BigDecimal calculateDiscount(BigDecimal orderAmount, Long couponId) {
CouponTemplate template = templateRepository.findById(couponId)
.orElseThrow(() -> new RuntimeException("优惠券不存在"));
switch (template.getType()) {
case FIXED_AMOUNT:
return template.getDiscountAmount();
case PERCENTAGE:
return orderAmount.multiply(template.getDiscountRate())
.setScale(2, RoundingMode.HALF_UP);
case NO_THRESHOLD:
return template.getDiscountAmount();
default:
throw new RuntimeException("不支持的优惠券类型");
}
}
}
```
四、关键业务规则实现
1. 优惠券使用范围控制
```java
public enum CouponScope {
ALL, // 全平台通用
CATEGORY, // 特定品类
SPECIFIC_ITEMS // 特定商品
}
// 在模板中定义使用范围
public class CouponTemplate {
private CouponScope scope;
private List categoryIds; // 当scope=CATEGORY时使用
private List itemIds; // 当scope=SPECIFIC_ITEMS时使用
// 其他字段...
}
```
2. 优惠券叠加使用规则
```java
public class CouponStackingRule {
private boolean allowStacking; // 是否允许叠加使用
private int maxStackCount; // 最大叠加数量
private List allowedTypes; // 允许叠加的优惠券类型
public boolean canStackWith(CouponTemplate newCoupon, List existingCoupons) {
if (!allowStacking) {
return false;
}
if (existingCoupons.size() >= maxStackCount) {
return false;
}
if (!allowedTypes.contains(newCoupon.getType())) {
return false;
}
// 其他叠加规则检查...
return true;
}
}
```
3. 优惠券有效期管理
```java
public class CouponValidityManager {
public boolean isCouponValid(CouponTemplate template) {
LocalDateTime now = LocalDateTime.now();
return now.isAfter(template.getStartTime())
&& now.isBefore(template.getEndTime())
&& template.getStatus() == CouponStatus.ACTIVE;
}
public long getRemainingValidity(CouponTemplate template) {
if (template.getValidityType() == ValidityType.FIXED_PERIOD) {
return ChronoUnit.DAYS.between(LocalDateTime.now(), template.getEndTime());
} else { // 领取后N天内有效
// 需要结合用户领取时间计算
return -1; // 实际实现需要查询用户优惠券记录
}
}
}
```
五、性能优化措施
1. 缓存策略:
- 使用Redis缓存热门优惠券模板信息
- 缓存用户可用优惠券列表
2. 异步处理:
- 优惠券发放采用消息队列异步处理
- 优惠券使用统计异步写入数据库
3. 数据库优化:
- 优惠券模板表按状态和使用范围建立索引
- 用户优惠券表按用户ID和状态建立复合索引
4. 限流措施:
- 对优惠券领取接口实施限流
- 对高并发场景下的优惠券使用进行排队处理
六、安全考虑
1. 防刷机制:
- 同一用户领取同种优惠券的频率限制
- IP地址限制
2. 数据校验:
- 优惠券码格式校验
- 订单金额防篡改校验
3. 审计日志:
- 记录优惠券的发放、使用、核销等关键操作
- 保留操作日志至少6个月
七、测试用例示例
1. 正常场景:
- 用户领取满减券并在符合条件的订单中使用
- 用户使用折扣券购买商品
2. 异常场景:
- 尝试使用已过期的优惠券
- 订单金额不满足最低消费要求时使用优惠券
- 使用范围不符的优惠券(如生鲜券用于日用品)
3. 边界场景:
- 优惠券刚好满足最低消费金额
- 优惠券使用截止时间最后一分钟使用
- 叠加使用多张优惠券
八、部署与监控
1. 监控指标:
- 优惠券发放成功率
- 优惠券使用率
- 优惠券系统响应时间
2. 告警规则:
- 优惠券发放失败率超过1%
- 系统响应时间超过500ms
- 优惠券库存不足预警
3. 日志收集:
- 收集优惠券领取、使用、核销等操作日志
- 记录优惠券使用失败的原因分布
通过以上设计,美团买菜系统可以实现一个灵活、高效、安全的优惠券通用功能,支持各种营销活动场景,同时保证系统稳定性和用户体验。