一、功能概述
美团买菜系统的优惠券通用功能需要支持多种类型的优惠券(满减券、折扣券、无门槛券等),能够在不同业务场景(买菜、配送、会员服务等)下通用,并实现精准的发放、使用和结算。
二、系统架构设计
1. 核心模块划分
- 优惠券模板管理:定义优惠券规则和属性
- 优惠券发放系统:控制优惠券的生成和分发
- 优惠券使用系统:处理优惠券的核销和抵扣
- 优惠券结算系统:与订单系统对接完成金额计算
- 优惠券统计系统:数据分析和效果追踪
2. 数据库设计
```sql
-- 优惠券模板表
CREATE TABLE coupon_template (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL COMMENT 优惠券名称,
type TINYINT NOT NULL COMMENT 类型(1:满减,2:折扣,3:无门槛),
discount_type TINYINT COMMENT 折扣类型(1:百分比,2:固定金额),
value DECIMAL(10,2) NOT NULL COMMENT 优惠值,
min_order_amount DECIMAL(10,2) DEFAULT NULL COMMENT 最低订单金额,
start_time DATETIME NOT NULL COMMENT 生效时间,
end_time DATETIME NOT NULL COMMENT 过期时间,
total_count INT DEFAULT NULL COMMENT 总发放量,
remain_count INT DEFAULT NULL COMMENT 剩余量,
scope TINYINT NOT NULL COMMENT 使用范围(1:全品类,2:指定品类,3:指定商品),
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 COMMENT 用户ID,
template_id BIGINT NOT NULL COMMENT 模板ID,
coupon_code VARCHAR(32) NOT NULL COMMENT 优惠券码,
status TINYINT NOT NULL COMMENT 状态(1:未使用,2:已使用,3:已过期),
order_id BIGINT DEFAULT NULL COMMENT 关联订单ID,
get_time DATETIME NOT NULL COMMENT 获取时间,
use_time DATETIME DEFAULT NULL COMMENT 使用时间,
expire_time DATETIME NOT NULL COMMENT 过期时间,
UNIQUE KEY uk_coupon_code (coupon_code)
);
```
三、核心功能实现
1. 优惠券模板管理
```java
public class CouponTemplate {
private Long id;
private String name;
private Integer type; // 1-满减, 2-折扣, 3-无门槛
private BigDecimal value;
private BigDecimal minOrderAmount;
private Date startTime;
private Date endTime;
private Integer totalCount;
private Integer remainCount;
private Integer scope; // 1-全品类, 2-指定品类, 3-指定商品
// getters and setters
}
// 创建优惠券模板服务
public CouponTemplate createCouponTemplate(CouponTemplateDTO dto) {
// 参数校验
validateTemplate(dto);
CouponTemplate template = new CouponTemplate();
// 转换DTO到Entity
BeanUtils.copyProperties(dto, template);
// 设置默认值
template.setStatus(1); // 有效
template.setCreateTime(new Date());
template.setUpdateTime(new Date());
// 保存到数据库
couponTemplateMapper.insert(template);
return template;
}
```
2. 优惠券发放系统
```java
public interface CouponDistributionService {
/
* 发放优惠券给用户
* @param userId 用户ID
* @param templateId 模板ID
* @param count 发放数量
* @return 发放结果
*/
DistributionResult distributeCoupon(Long userId, Long templateId, Integer count);
}
@Service
public class CouponDistributionServiceImpl implements CouponDistributionService {
@Autowired
private CouponTemplateMapper couponTemplateMapper;
@Autowired
private UserCouponMapper userCouponMapper;
@Override
@Transactional
public DistributionResult distributeCoupon(Long userId, Long templateId, Integer count) {
// 1. 查询模板信息
CouponTemplate template = couponTemplateMapper.selectById(templateId);
if (template == null || template.getStatus() != 1) {
return DistributionResult.fail("优惠券模板不存在或已失效");
}
// 2. 检查发放数量
if (template.getRemainCount() < count) {
return DistributionResult.fail("优惠券库存不足");
}
// 3. 生成用户优惠券
List coupons = new ArrayList<>();
for (int i = 0; i < count; i++) {
UserCoupon coupon = new UserCoupon();
coupon.setUserId(userId);
coupon.setTemplateId(templateId);
coupon.setCouponCode(generateCouponCode());
coupon.setStatus(1); // 未使用
coupon.setGetTime(new Date());
coupon.setExpireTime(template.getEndTime());
coupons.add(coupon);
}
// 4. 批量插入
userCouponMapper.batchInsert(coupons);
// 5. 更新模板剩余数量
couponTemplateMapper.decreaseRemainCount(templateId, count);
return DistributionResult.success();
}
private String generateCouponCode() {
// 生成唯一优惠券码
return UUID.randomUUID().toString().replace("-", "").substring(0, 16).toUpperCase();
}
}
```
3. 优惠券使用系统
```java
public interface CouponUsageService {
/
* 使用优惠券
* @param userId 用户ID
* @param couponCode 优惠券码
* @param orderAmount 订单金额
* @return 优惠信息
*/
CouponUsageResult useCoupon(Long userId, String couponCode, BigDecimal orderAmount);
}
@Service
public class CouponUsageServiceImpl implements CouponUsageService {
@Autowired
private UserCouponMapper userCouponMapper;
@Autowired
private CouponTemplateMapper couponTemplateMapper;
@Override
@Transactional
public CouponUsageResult useCoupon(Long userId, String couponCode, BigDecimal orderAmount) {
// 1. 查询用户优惠券
UserCoupon userCoupon = userCouponMapper.selectByCodeAndUserId(couponCode, userId);
if (userCoupon == null || userCoupon.getStatus() != 1) {
return CouponUsageResult.fail("优惠券不存在或不可用");
}
// 2. 查询模板信息
CouponTemplate template = couponTemplateMapper.selectById(userCoupon.getTemplateId());
if (template == null || template.getStatus() != 1) {
return CouponUsageResult.fail("优惠券模板已失效");
}
// 3. 检查有效期
if (new Date().after(template.getEndTime())) {
return CouponUsageResult.fail("优惠券已过期");
}
// 4. 检查使用范围
if (!checkCouponScope(template, orderItems)) { // 假设有orderItems参数
return CouponUsageResult.fail("优惠券不适用于当前商品");
}
// 5. 检查最低订单金额
if (template.getMinOrderAmount() != null
&& orderAmount.compareTo(template.getMinOrderAmount()) < 0) {
return CouponUsageResult.fail("订单金额不满足使用条件");
}
// 6. 计算优惠金额
BigDecimal discountAmount = calculateDiscount(template, orderAmount);
// 7. 更新优惠券状态
userCoupon.setStatus(2); // 已使用
userCoupon.setUseTime(new Date());
userCouponMapper.updateById(userCoupon);
return CouponUsageResult.success(discountAmount, template.getType());
}
private BigDecimal calculateDiscount(CouponTemplate template, BigDecimal orderAmount) {
switch (template.getType()) {
case 1: // 满减券
return template.getValue();
case 2: // 折扣券
if (template.getDiscountType() == 1) { // 百分比
return orderAmount.multiply(template.getValue()).divide(new BigDecimal(100), 2, RoundingMode.DOWN);
} else { // 固定金额折扣
return template.getValue();
}
case 3: // 无门槛券
return template.getValue();
default:
return BigDecimal.ZERO;
}
}
}
```
4. 优惠券结算系统集成
```java
@Service
public class OrderSettlementService {
@Autowired
private CouponUsageService couponUsageService;
public OrderSettlementResult settle(Long userId, List items, String couponCode) {
// 计算商品总价
BigDecimal totalAmount = calculateTotalAmount(items);
// 使用优惠券
CouponUsageResult couponResult = null;
if (StringUtils.isNotBlank(couponCode)) {
couponResult = couponUsageService.useCoupon(userId, couponCode, totalAmount);
if (!couponResult.isSuccess()) {
return OrderSettlementResult.fail(couponResult.getMessage());
}
}
// 计算最终金额
BigDecimal finalAmount = totalAmount;
if (couponResult != null) {
finalAmount = totalAmount.subtract(couponResult.getDiscountAmount());
if (finalAmount.compareTo(BigDecimal.ZERO) < 0) {
finalAmount = BigDecimal.ZERO;
}
}
// 返回结算结果
return OrderSettlementResult.success(finalAmount,
couponResult != null ? couponResult.getDiscountAmount() : BigDecimal.ZERO);
}
}
```
四、关键业务规则实现
1. 优惠券使用范围检查
```java
private boolean checkCouponScope(CouponTemplate template, List items) {
if (template.getScope() == 1) { // 全品类
return true;
}
// 获取优惠券适用的品类或商品ID列表
List applicableIds = getApplicableIds(template.getId());
for (OrderItem item : items) {
if (template.getScope() == 2) { // 指定品类
if (!applicableIds.contains(item.getCategoryId())) {
return false;
}
} else if (template.getScope() == 3) { // 指定商品
if (!applicableIds.contains(item.getProductId())) {
return false;
}
}
}
return true;
}
```
2. 优惠券叠加使用限制
```java
public class CouponCombinationValidator {
public ValidationResult validateCombination(List coupons, BigDecimal orderAmount) {
// 1. 检查优惠券类型是否允许叠加
Set types = coupons.stream().map(UserCoupon::getTemplateType).collect(Collectors.toSet());
if (types.size() > 1 && !isCombinationAllowed(types)) {
return ValidationResult.fail("不支持此类优惠券叠加使用");
}
// 2. 检查每张优惠券的使用条件
for (UserCoupon coupon : coupons) {
CouponTemplate template = getTemplateById(coupon.getTemplateId());
if (orderAmount.compareTo(template.getMinOrderAmount()) < 0) {
return ValidationResult.fail("订单金额不满足优惠券使用条件");
}
}
return ValidationResult.success();
}
private boolean isCombinationAllowed(Set types) {
// 实现业务规则:哪些类型的优惠券可以叠加使用
// 例如:满减券和折扣券不能叠加,但多张满减券可以叠加
// 这里需要根据实际业务需求实现
return true;
}
}
```
五、系统优化与扩展
1. 性能优化
- 使用Redis缓存热门优惠券模板信息
- 对用户优惠券列表实现分页查询
- 异步处理优惠券发放和统计
2. 扩展功能
- 优惠券分享功能
- 优惠券转赠功能
- 优惠券过期前提醒
- 优惠券使用数据分析
3. 分布式锁实现
```java
@Service
public class DistributedCouponService {
@Autowired
private RedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "coupon:lock:";
public boolean tryAcquireLock(String couponCode, Long userId) {
String lockKey = LOCK_PREFIX + couponCode;
String lockValue = userId + ":" + System.currentTimeMillis();
try {
// 尝试获取锁,设置过期时间为10秒
Boolean acquired = redisTemplate.opsForValue().setIfAbsent(
lockKey, lockValue, 10, TimeUnit.SECONDS);
return Boolean.TRUE.equals(acquired);
} catch (Exception e) {
return false;
}
}
public void releaseLock(String couponCode, String lockValue) {
String lockKey = LOCK_PREFIX + couponCode;
String currentValue = redisTemplate.opsForValue().get(lockKey);
if (lockValue.equals(currentValue)) {
redisTemplate.delete(lockKey);
}
}
}
```
六、测试用例设计
1. 单元测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class CouponServiceTest {
@Autowired
private CouponDistributionService distributionService;
@Test
public void testDistributeCoupon_Success() {
// 准备测试数据
Long userId = 1L;
Long templateId = 100L;
// 执行测试
DistributionResult result = distributionService.distributeCoupon(userId, templateId, 1);
// 验证结果
assertTrue(result.isSuccess());
// 其他断言...
}
@Test
public void testUseCoupon_InvalidCoupon() {
// 准备测试数据
Long userId = 1L;
String invalidCode = "INVALID_CODE";
// 执行测试
CouponUsageResult result = couponUsageService.useCoupon(userId, invalidCode, new BigDecimal("100"));
// 验证结果
assertFalse(result.isSuccess());
assertEquals("优惠券不存在或不可用", result.getMessage());
}
}
```
2. 集成测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class CouponIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testCouponLifecycle() throws Exception {
// 1. 创建优惠券模板
String templateJson = "{name:测试满减券,type:1,value:10,minOrderAmount:50,"
+ "startTime:2023-01-01,endTime:2023-12-31,totalCount:1000}";
mockMvc.perform(post("/api/coupon/template")
.contentType(MediaType.APPLICATION_JSON)
.content(templateJson))
.andExpect(status().isCreated());
// 2. 用户领取优惠券
mockMvc.perform(post("/api/coupon/distribute")
.param("userId", "1")
.param("templateId", "1")
.param("count", "1"))
.andExpect(status().isOk());
// 3. 用户使用优惠券
mockMvc.perform(post("/api/coupon/use")
.param("userId", "1")
.param("couponCode", "GENERATED_CODE")
.param("orderAmount", "60"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success", is(true)))
.andExpect(jsonPath("$.discountAmount", is(10.0)));
}
}
```
七、部署与监控
1. 监控指标
- 优惠券发放成功率
- 优惠券使用率
- 优惠券核销失败率
- 优惠券系统响应时间
2. 告警规则
- 优惠券发放失败率 > 1%
- 优惠券使用率异常下降
- 系统响应时间 > 500ms
3. 日志收集
```log4j
优惠券使用日志
log4j.logger.coupon.usage=INFO, couponUsage
log4j.appender.couponUsage=org.apache.log4j.DailyRollingFileAppender
log4j.appender.couponUsage.File=/var/log/meituan/coupon_usage.log
log4j.appender.couponUsage.DatePattern=.yyyy-MM-dd
log4j.appender.couponUsage.layout=org.apache.log4j.PatternLayout
log4j.appender.couponUsage.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %m%n
```
以上是美团买菜系统优惠券通用功能的详细实现方案,涵盖了从数据库设计到业务逻辑实现,再到测试和监控的全流程。实际开发中需要根据具体业务需求进行调整和优化。