Java实现优惠券领取系统:核心设计与代码实践

Java实现优惠券领取系统:核心设计与代码实践

一、系统架构与核心模块设计

优惠券领取系统需满足高并发、低延迟的业务需求,推荐采用分层架构:表现层(Spring MVC)、业务逻辑层(Service)、数据访问层(DAO)及数据库(MySQL/Redis)。核心模块包括:

  1. 用户模块:用户信息管理、身份验证
  2. 优惠券模块:优惠券模板定义、库存管理
  3. 领取模块:领取规则校验、并发控制
  4. 通知模块:短信/站内信推送

数据库设计关键点

  1. -- 优惠券模板表
  2. CREATE TABLE coupon_template (
  3. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  4. name VARCHAR(50) NOT NULL,
  5. type TINYINT NOT NULL COMMENT '1-折扣 2-满减 3-现金券',
  6. discount DECIMAL(10,2) COMMENT '折扣率或减免金额',
  7. condition DECIMAL(10,2) COMMENT '使用条件',
  8. total INT NOT NULL COMMENT '总库存',
  9. start_time DATETIME NOT NULL,
  10. end_time DATETIME NOT NULL,
  11. status TINYINT DEFAULT 1 COMMENT '1-启用 0-禁用'
  12. );
  13. -- 用户优惠券表
  14. CREATE TABLE user_coupon (
  15. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  16. user_id BIGINT NOT NULL,
  17. template_id BIGINT NOT NULL,
  18. status TINYINT DEFAULT 0 COMMENT '0-未使用 1-已使用 2-已过期',
  19. get_time DATETIME NOT NULL,
  20. use_time DATETIME,
  21. FOREIGN KEY (template_id) REFERENCES coupon_template(id)
  22. );

二、核心业务逻辑实现

1. 领取规则校验

实现前需验证三大条件:

  • 用户资格(新用户/会员等级)
  • 优惠券状态(未过期/库存充足)
  • 领取限制(每日限领/总限领)
  1. public class CouponService {
  2. @Autowired
  3. private CouponTemplateDao templateDao;
  4. @Autowired
  5. private UserCouponDao userCouponDao;
  6. @Autowired
  7. private RedisTemplate<String, Integer> redisTemplate;
  8. public Result receiveCoupon(Long userId, Long templateId) {
  9. // 1. 校验优惠券模板
  10. CouponTemplate template = templateDao.selectById(templateId);
  11. if (template == null || template.getStatus() != 1) {
  12. return Result.fail("优惠券不存在或已下架");
  13. }
  14. // 2. 校验时间有效性
  15. if (LocalDateTime.now().isBefore(template.getStartTime()) ||
  16. LocalDateTime.now().isAfter(template.getEndTime())) {
  17. return Result.fail("不在有效期内");
  18. }
  19. // 3. 校验库存(Redis原子操作)
  20. String key = "coupon:stock:" + templateId;
  21. Integer stock = redisTemplate.opsForValue().decrement(key);
  22. if (stock == null || stock < 0) {
  23. redisTemplate.opsForValue().increment(key); // 回滚
  24. return Result.fail("库存不足");
  25. }
  26. // 4. 创建用户优惠券记录
  27. UserCoupon coupon = new UserCoupon();
  28. coupon.setUserId(userId);
  29. coupon.setTemplateId(templateId);
  30. coupon.setStatus(0);
  31. coupon.setGetTime(LocalDateTime.now());
  32. userCouponDao.insert(coupon);
  33. return Result.success("领取成功");
  34. }
  35. }

2. 并发控制方案

高并发场景下需解决超卖问题,推荐组合方案:

  1. Redis原子操作DECR指令实现库存扣减
  2. 数据库乐观锁:版本号控制
  3. 分布式锁:Redisson实现
  1. // 分布式锁实现示例
  2. public boolean tryLock(String lockKey, Long userId) {
  3. RLock lock = redissonClient.getLock(lockKey);
  4. try {
  5. // 尝试加锁,等待10秒,锁自动释放时间30秒
  6. return lock.tryLock(10, 30, TimeUnit.SECONDS);
  7. } catch (InterruptedException e) {
  8. Thread.currentThread().interrupt();
  9. return false;
  10. }
  11. }

三、API接口设计

1. 领取接口

  1. @RestController
  2. @RequestMapping("/api/coupon")
  3. public class CouponController {
  4. @Autowired
  5. private CouponService couponService;
  6. @PostMapping("/receive")
  7. public Result receive(@RequestParam Long userId,
  8. @RequestParam Long templateId) {
  9. // 参数校验
  10. if (userId == null || templateId == null) {
  11. return Result.fail("参数错误");
  12. }
  13. return couponService.receiveCoupon(userId, templateId);
  14. }
  15. }

2. 接口安全设计

  • 签名验证:防止参数篡改
  • 频率限制:防止恶意刷接口
  • 幂等性设计:防止重复领取
  1. // 幂等性处理示例
  2. public Result receiveWithIdempotent(Long userId, Long templateId, String requestId) {
  3. // 检查是否已处理过该请求
  4. if (redisTemplate.hasKey("coupon:req:" + requestId)) {
  5. return Result.fail("请勿重复提交");
  6. }
  7. redisTemplate.opsForValue().set("coupon:req:" + requestId, "1", 24, TimeUnit.HOURS);
  8. return receiveCoupon(userId, templateId);
  9. }

四、性能优化策略

1. 缓存策略

  • 热点数据缓存:优惠券模板信息缓存
  • 多级缓存:Redis + Caffeine
  • 缓存预热:系统启动时加载常用数据
  1. // 缓存加载示例
  2. @PostConstruct
  3. public void init() {
  4. List<CouponTemplate> templates = templateDao.selectAllActive();
  5. templates.forEach(t -> {
  6. redisTemplate.opsForValue().set("coupon:template:" + t.getId(), t);
  7. });
  8. }

2. 异步处理

  • 领取成功后的通知使用消息队列(RabbitMQ/Kafka)
  • 日志记录异步化
  1. // 异步通知示例
  2. @Async
  3. public void sendNotification(Long userId, Long couponId) {
  4. // 查询用户信息
  5. User user = userDao.selectById(userId);
  6. // 发送短信/站内信
  7. notificationService.send(user.getPhone(), "您已成功领取优惠券...");
  8. }

五、测试与部署方案

1. 单元测试

  1. public class CouponServiceTest {
  2. @Mock
  3. private CouponTemplateDao templateDao;
  4. @Mock
  5. private RedisTemplate<String, Integer> redisTemplate;
  6. @InjectMocks
  7. private CouponService couponService;
  8. @Test
  9. public void testReceiveSuccess() {
  10. // 模拟数据
  11. CouponTemplate template = new CouponTemplate();
  12. template.setId(1L);
  13. template.setTotal(100);
  14. when(templateDao.selectById(1L)).thenReturn(template);
  15. when(redisTemplate.opsForValue().decrement("coupon:stock:1")).thenReturn(99);
  16. // 执行测试
  17. Result result = couponService.receiveCoupon(1L, 1L);
  18. assertTrue(result.isSuccess());
  19. }
  20. }

2. 部署建议

  • 容器化部署:Docker + Kubernetes
  • 监控指标:QPS、错误率、库存水位
  • 弹性伸缩:根据并发量自动调整实例数

六、扩展功能建议

  1. 优惠券组合:支持多张券叠加使用
  2. 精准投放:基于用户画像的优惠券推荐
  3. 对账系统:记录优惠券核销明细
  4. 防刷机制:IP限制、设备指纹识别

七、常见问题解决方案

  1. 库存不一致
    • 方案:Redis+MySQL双写,通过定时任务校正
  2. 超时领取
    • 方案:设置领取有效期,过期自动回收
  3. 数据倾斜
    • 方案:优惠券ID分片,均匀分配库存

通过上述设计,可构建一个支持每秒1000+请求的优惠券领取系统。实际开发中需根据业务规模调整技术方案,小型系统可简化为单库+本地缓存,大型系统需考虑分库分表和微服务架构。