Java实现优惠券领取:从设计到落地的完整方案

一、系统架构设计

1.1 模块划分

优惠券领取系统可分为四个核心模块:用户服务、优惠券服务、库存服务、日志服务。用户服务负责用户身份验证与权限控制,优惠券服务管理优惠券类型与规则,库存服务处理并发领取的原子性操作,日志服务记录操作轨迹。采用微服务架构时,各模块可通过REST API或RPC进行通信,建议使用Spring Cloud实现服务发现与熔断机制。

1.2 数据库设计

表结构包含四张核心表:用户表(user)、优惠券表(coupon)、库存表(coupon_stock)、领取记录表(coupon_record)。关键设计点包括:

  • 库存表采用状态字段(status)标记优惠券是否可用
  • 领取记录表记录用户ID、优惠券ID、领取时间、使用状态等字段
  • 索引设计:用户ID+优惠券ID的复合索引加速查询

1.3 接口定义

主要接口包括:

  1. // 优惠券服务接口
  2. public interface CouponService {
  3. // 领取优惠券
  4. Result<CouponRecord> acquireCoupon(Long userId, Long couponId);
  5. // 查询用户优惠券
  6. List<CouponRecord> queryUserCoupons(Long userId);
  7. }
  8. // 库存服务接口
  9. public interface StockService {
  10. // 扣减库存
  11. boolean decreaseStock(Long couponId);
  12. }

二、核心实现方案

2.1 并发控制实现

2.1.1 数据库乐观锁方案

  1. @Transactional
  2. public boolean decreaseStockWithOptimisticLock(Long couponId) {
  3. CouponStock stock = couponStockMapper.selectById(couponId);
  4. if (stock.getStock() <= 0) {
  5. return false;
  6. }
  7. int updated = couponStockMapper.updateStock(
  8. couponId,
  9. stock.getVersion(),
  10. stock.getStock() - 1
  11. );
  12. return updated > 0;
  13. }
  14. // Mapper XML
  15. <update id="updateStock">
  16. UPDATE coupon_stock
  17. SET stock = stock - 1, version = version + 1
  18. WHERE id = #{id} AND version = #{version} AND stock > 0
  19. </update>

2.1.2 Redis分布式锁方案

  1. public boolean acquireWithRedisLock(Long userId, Long couponId) {
  2. String lockKey = "coupon:lock:" + couponId;
  3. String requestId = UUID.randomUUID().toString();
  4. try {
  5. // 尝试获取锁,设置10秒过期
  6. boolean locked = redisTemplate.opsForValue().setIfAbsent(
  7. lockKey,
  8. requestId,
  9. 10,
  10. TimeUnit.SECONDS
  11. );
  12. if (!locked) {
  13. return false;
  14. }
  15. // 业务处理
  16. return stockService.decreaseStock(couponId);
  17. } finally {
  18. // 释放锁(需校验requestId防止误删)
  19. String currentValue = redisTemplate.opsForValue().get(lockKey);
  20. if (requestId.equals(currentValue)) {
  21. redisTemplate.delete(lockKey);
  22. }
  23. }
  24. }

2.2 业务逻辑实现

2.2.1 完整领取流程

  1. @Service
  2. public class CouponServiceImpl implements CouponService {
  3. @Autowired
  4. private StockService stockService;
  5. @Autowired
  6. private CouponRecordMapper recordMapper;
  7. @Override
  8. @Transactional
  9. public Result<CouponRecord> acquireCoupon(Long userId, Long couponId) {
  10. // 1. 参数校验
  11. if (userId == null || couponId == null) {
  12. return Result.fail("参数错误");
  13. }
  14. // 2. 并发控制(选择方案)
  15. boolean success = stockService.decreaseStock(couponId);
  16. if (!success) {
  17. return Result.fail("优惠券已领完");
  18. }
  19. // 3. 创建领取记录
  20. CouponRecord record = new CouponRecord();
  21. record.setUserId(userId);
  22. record.setCouponId(couponId);
  23. record.setStatus(CouponStatus.UNUSED);
  24. record.setAcquireTime(new Date());
  25. recordMapper.insert(record);
  26. // 4. 返回结果
  27. return Result.success(record);
  28. }
  29. }

2.2.2 防重复领取实现

  1. public boolean checkAndAcquire(Long userId, Long couponId) {
  2. // 查询是否已领取过
  3. int count = recordMapper.selectCount(
  4. new QueryWrapper<CouponRecord>()
  5. .eq("user_id", userId)
  6. .eq("coupon_id", couponId)
  7. .eq("status", CouponStatus.UNUSED)
  8. );
  9. if (count > 0) {
  10. return false;
  11. }
  12. // 继续领取流程
  13. return acquireCoupon(userId, couponId).isSuccess();
  14. }

三、高级优化方案

3.1 异步化处理

采用消息队列解耦领取操作与后续处理:

  1. @Async
  2. public void asyncProcessCoupon(CouponRecord record) {
  3. // 1. 发送通知
  4. notificationService.sendCouponNotice(record);
  5. // 2. 更新统计信息
  6. statsService.incrementAcquireCount(record.getCouponId());
  7. // 3. 记录操作日志
  8. logService.recordCouponOperation(record);
  9. }

3.2 限流策略实现

  1. public class RateLimiter {
  2. private final RateLimiter limiter = RateLimiter.create(100); // 每秒100个请求
  3. public boolean tryAcquire() {
  4. return limiter.tryAcquire();
  5. }
  6. }
  7. // 在Controller中使用
  8. @GetMapping("/acquire")
  9. public Result<?> acquire(@RequestParam Long couponId, HttpServletRequest request) {
  10. String token = request.getHeader("Authorization");
  11. Long userId = jwtUtil.getUserId(token);
  12. if (!rateLimiter.tryAcquire()) {
  13. return Result.fail("系统繁忙,请稍后再试");
  14. }
  15. return couponService.acquireCoupon(userId, couponId);
  16. }

3.3 数据一致性保障

采用TCC(Try-Confirm-Cancel)模式处理分布式事务:

  1. public interface TccCouponService {
  2. // 尝试阶段
  3. boolean tryAcquire(Long userId, Long couponId);
  4. // 确认阶段
  5. boolean confirmAcquire(Long userId, Long couponId);
  6. // 取消阶段
  7. boolean cancelAcquire(Long userId, Long couponId);
  8. }
  9. // 实现示例
  10. @Transactional
  11. public boolean tryAcquire(Long userId, Long couponId) {
  12. // 1. 预扣库存
  13. boolean stockReserved = stockService.reserveStock(couponId, 1);
  14. if (!stockReserved) {
  15. return false;
  16. }
  17. // 2. 创建预记录
  18. CouponRecord record = new CouponRecord();
  19. record.setUserId(userId);
  20. record.setCouponId(couponId);
  21. record.setStatus(CouponStatus.TRYING);
  22. recordMapper.insert(record);
  23. return true;
  24. }

四、测试与部署方案

4.1 单元测试示例

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class CouponServiceTest {
  4. @Autowired
  5. private CouponService couponService;
  6. @MockBean
  7. private StockService stockService;
  8. @Test
  9. public void testAcquireSuccess() {
  10. when(stockService.decreaseStock(1L)).thenReturn(true);
  11. Result<CouponRecord> result = couponService.acquireCoupon(1001L, 1L);
  12. assertTrue(result.isSuccess());
  13. assertNotNull(result.getData());
  14. }
  15. @Test
  16. public void testAcquireFailure() {
  17. when(stockService.decreaseStock(1L)).thenReturn(false);
  18. Result<CouponRecord> result = couponService.acquireCoupon(1001L, 1L);
  19. assertFalse(result.isSuccess());
  20. assertEquals("优惠券已领完", result.getMessage());
  21. }
  22. }

4.2 压力测试方案

使用JMeter进行压力测试:

  1. 线程组设置:1000个线程,循环10次
  2. HTTP请求配置:/coupon/acquire接口
  3. 监听器配置:聚合报告、响应时间图
  4. 预期指标:QPS≥500,平均响应时间≤200ms,错误率≤0.1%

4.3 部署建议

  1. 容器化部署:使用Docker打包服务,Kubernetes管理集群
  2. 配置管理:通过Spring Cloud Config实现环境差异化配置
  3. 监控告警:集成Prometheus+Grafana监控系统指标,设置库存阈值告警

五、最佳实践总结

  1. 并发控制选择:优先使用数据库乐观锁,高并发场景下结合Redis分布式锁
  2. 幂等性设计:所有写入操作必须保证幂等,防止重复提交
  3. 数据一致性:重要业务采用TCC模式,普通业务采用最终一致性
  4. 性能优化:热点数据缓存,异步处理非核心业务
  5. 安全防护:接口限流、防刷机制、数据脱敏处理

通过以上方案,可构建一个高可用、高并发的优惠券领取系统,满足电商、O2O等场景的业务需求。实际开发中需根据具体业务场景调整技术选型和实现细节。