一、功能概述
美团买菜系统的优惠券通用功能需要支持多种类型的优惠券(满减券、折扣券、无门槛券等)在多个业务场景(商品购买、配送费减免、特定品类优惠等)下的通用使用。
二、系统架构设计
1. 优惠券服务模块
- 优惠券核心服务:处理优惠券的创建、发放、查询、核销等核心逻辑
- 优惠券规则引擎:动态解析和执行优惠券使用规则
- 优惠券状态管理:跟踪优惠券的生命周期(未使用、已使用、已过期等)
2. 数据库设计
```sql
-- 优惠券模板表
CREATE TABLE coupon_template (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
type TINYINT NOT NULL COMMENT 1-满减券 2-折扣券 3-无门槛券,
discount_type TINYINT COMMENT 1-金额 2-比例,
discount_value DECIMAL(10,2) NOT NULL,
min_order_amount DECIMAL(10,2) DEFAULT 0,
valid_days INT COMMENT 有效期天数,
valid_start_time DATETIME,
valid_end_time DATETIME,
user_limit INT DEFAULT 1 COMMENT 每人限领数量,
total_count INT NOT NULL,
remaining_count INT NOT NULL,
product_scope TINYINT COMMENT 1-全品类 2-指定品类 3-指定商品,
product_ids TEXT COMMENT 指定商品ID列表,
category_ids TEXT COMMENT 指定品类ID列表,
status TINYINT DEFAULT 1 COMMENT 1-有效 0-无效,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL
);
-- 用户优惠券表
CREATE TABLE user_coupon (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
template_id BIGINT NOT NULL,
coupon_code VARCHAR(32) NOT NULL,
status TINYINT DEFAULT 0 COMMENT 0-未使用 1-已使用 2-已过期,
order_id BIGINT COMMENT 关联订单ID,
get_time DATETIME NOT NULL,
use_time DATETIME,
expire_time DATETIME NOT NULL,
FOREIGN KEY (template_id) REFERENCES coupon_template(id)
);
```
三、核心功能实现
1. 优惠券发放
```java
public class CouponService {
// 发放优惠券给用户
public UserCoupon grantCoupon(Long userId, Long templateId) {
CouponTemplate template = couponTemplateRepository.findById(templateId)
.orElseThrow(() -> new RuntimeException("优惠券模板不存在"));
// 检查用户是否已达到领取上限
long userCount = userCouponRepository.countByUserIdAndTemplateId(userId, templateId);
if (userCount >= template.getUserLimit()) {
throw new RuntimeException("已达到领取上限");
}
// 创建用户优惠券
UserCoupon userCoupon = new UserCoupon();
userCoupon.setUserId(userId);
userCoupon.setTemplateId(templateId);
userCoupon.setCouponCode(generateCouponCode());
userCoupon.setStatus(0); // 未使用
userCoupon.setGetTime(new Date());
userCoupon.setExpireTime(calculateExpireTime(template));
// 更新模板剩余数量
couponTemplateRepository.decreaseRemainingCount(templateId);
return userCouponRepository.save(userCoupon);
}
private Date calculateExpireTime(CouponTemplate template) {
if (template.getValidDays() != null && template.getValidDays() > 0) {
return DateUtils.addDays(new Date(), template.getValidDays());
}
return template.getValidEndTime();
}
}
```
2. 优惠券使用规则校验
```java
public class CouponRuleValidator {
public boolean validateCouponUsage(UserCoupon userCoupon, Order order) {
CouponTemplate template = couponTemplateRepository.findById(userCoupon.getTemplateId())
.orElseThrow(() -> new RuntimeException("优惠券模板不存在"));
// 检查有效期
if (userCoupon.getExpireTime().before(new Date())) {
throw new RuntimeException("优惠券已过期");
}
// 检查订单金额是否满足最低要求
if (template.getMinOrderAmount() > 0 &&
order.getTotalAmount().compareTo(template.getMinOrderAmount()) < 0) {
throw new RuntimeException("订单金额不满足最低要求");
}
// 检查商品/品类限制
if (template.getProductScope() != null) {
switch (template.getProductScope()) {
case 1: // 全品类
break;
case 2: // 指定品类
if (!checkCategoryMatch(order, template.getCategoryIds())) {
throw new RuntimeException("优惠券不适用于订单中的商品");
}
break;
case 3: // 指定商品
if (!checkProductMatch(order, template.getProductIds())) {
throw new RuntimeException("优惠券不适用于订单中的商品");
}
break;
}
}
return true;
}
private boolean checkCategoryMatch(Order order, String categoryIds) {
// 实现品类匹配逻辑
return true;
}
private boolean checkProductMatch(Order order, String productIds) {
// 实现商品匹配逻辑
return true;
}
}
```
3. 优惠券核销
```java
public class OrderService {
@Transactional
public Order createOrderWithCoupon(OrderCreateRequest request, Long couponId) {
// 1. 查询用户优惠券
UserCoupon userCoupon = userCouponRepository.findByIdAndUserId(couponId, request.getUserId())
.orElseThrow(() -> new RuntimeException("优惠券不存在或不属于该用户"));
// 2. 验证优惠券使用条件
couponRuleValidator.validateCouponUsage(userCoupon, convertToOrder(request));
// 3. 计算订单金额
Order order = convertToOrder(request);
applyCouponDiscount(order, userCoupon);
// 4. 更新优惠券状态
userCoupon.setStatus(1); // 已使用
userCoupon.setUseTime(new Date());
userCoupon.setOrderId(order.getId());
userCouponRepository.save(userCoupon);
// 5. 保存订单
return orderRepository.save(order);
}
private void applyCouponDiscount(Order order, UserCoupon userCoupon) {
CouponTemplate template = couponTemplateRepository.findById(userCoupon.getTemplateId())
.orElseThrow(() -> new RuntimeException("优惠券模板不存在"));
switch (template.getType()) {
case 1: // 满减券
order.setTotalAmount(order.getTotalAmount().subtract(template.getDiscountValue()));
break;
case 2: // 折扣券
order.setTotalAmount(order.getTotalAmount().multiply(
new BigDecimal(1 - template.getDiscountValue().doubleValue())));
break;
case 3: // 无门槛券
order.setTotalAmount(order.getTotalAmount().subtract(template.getDiscountValue()));
break;
}
// 确保金额不为负
if (order.getTotalAmount().compareTo(BigDecimal.ZERO) < 0) {
order.setTotalAmount(BigDecimal.ZERO);
}
}
}
```
四、前端交互设计
1. 优惠券列表展示
```javascript
// 伪代码示例
function fetchCoupons() {
api.get(/api/coupons/my)
.then(response => {
const usableCoupons = response.data.filter(coupon =>
coupon.status === 0 && new Date(coupon.expireTime) > new Date()
);
const expiredCoupons = response.data.filter(coupon =>
coupon.status === 0 && new Date(coupon.expireTime) <= new Date()
);
const usedCoupons = response.data.filter(coupon => coupon.status === 1);
renderCoupons(usableCoupons, usable);
renderCoupons(expiredCoupons, expired);
renderCoupons(usedCoupons, used);
});
}
```
2. 订单结算页优惠券选择
```javascript
function applyCoupon(couponId) {
api.post(/api/orders/calculate, {
cartItems: getCartItems(),
couponId: couponId
}).then(response => {
updateOrderSummary(response.data);
});
}
function renderCouponSelector(coupons) {
const selector = document.getElementById(coupon-selector);
selector.innerHTML = coupons.map(coupon => `
¥${coupon.discountValue}
${coupon.name}
${coupon.minOrderAmount > 0 ?
`满${coupon.minOrderAmount}元可用` : 无门槛}
有效期至: ${formatDate(coupon.expireTime)}
`).join();
}
```
五、关键技术点
1. 分布式锁:在高并发场景下,使用Redis分布式锁防止优惠券超发
```java
public class CouponLock {
private static final String LOCK_PREFIX = "coupon:lock:";
public boolean tryLock(Long templateId, long expireTime) {
String key = LOCK_PREFIX + templateId;
return redisTemplate.opsForValue().setIfAbsent(key, "1", expireTime, TimeUnit.SECONDS);
}
public void unlock(Long templateId) {
String key = LOCK_PREFIX + templateId;
redisTemplate.delete(key);
}
}
```
2. 异步消息处理:使用消息队列处理优惠券发放和状态变更通知
```java
@KafkaListener(topics = "coupon-events")
public void handleCouponEvent(CouponEvent event) {
switch (event.getType()) {
case GRANTED:
// 处理优惠券发放事件
break;
case USED:
// 处理优惠券使用事件
break;
case EXPIRED:
// 处理优惠券过期事件
break;
}
}
```
3. 缓存优化:对热门优惠券模板进行缓存
```java
@Cacheable(value = "couponTemplates", key = " templateId")
public CouponTemplate getCouponTemplate(Long templateId) {
return couponTemplateRepository.findById(templateId).orElse(null);
}
```
六、测试方案
1. 单元测试:测试优惠券规则引擎的各种边界条件
2. 集成测试:测试优惠券发放、查询、核销的全流程
3. 压力测试:模拟高并发场景下的优惠券领取和使用
4. 异常测试:测试过期优惠券、不符合条件的优惠券使用等场景
七、部署与监控
1. 监控指标:
- 优惠券发放成功率
- 优惠券核销率
- 优惠券系统响应时间
- 优惠券库存预警
2. 日志记录:
- 优惠券发放日志
- 优惠券使用日志
- 优惠券状态变更日志
3. 报警机制:
- 优惠券库存不足报警
- 优惠券系统错误率过高报警
- 优惠券核销异常报警
通过以上设计,美团买菜系统可以实现一个灵活、高效、可扩展的优惠券通用功能,支持多种业务场景下的优惠券使用需求。