基于 Java 的优惠券模板服务设计与实现

3 阅读4分钟

在电商、O2O、会员营销等业务场景中,优惠券是提升用户转化率和复购率的核心工具之一。为了支撑灵活多变的营销需求,构建一个高内聚、可扩展、易维护的优惠券模板服务至关重要。本文将从系统设计、核心模型、关键逻辑到代码实现,详解如何使用 Java(以 Spring Boot 为例)搭建一套通用的优惠券模板服务。


一、为什么需要“优惠券模板”?

直接为每张优惠券硬编码规则会导致系统僵化。引入“模板”概念,可将优惠规则抽象为可复用的配置单元,例如:

  • 满100减20
  • 打8折(最高减50)
  • 新用户专享无门槛券

通过模板 + 实例的方式,运营人员可在后台动态创建不同批次的优惠券,而无需开发介入。


二、核心模型设计

1. CouponTemplate(优惠券模板)

描述一类优惠券的通用规则,由运营配置,不可发放给用户。

字段说明
id模板ID(主键)
name模板名称(如“618大促满减券”)
description描述
type类型:0-满减券,1-折扣券,2-无门槛券
rule规则 JSON(如 {"threshold":100, "discount":20}
validStartTime / validEndTime模板有效期(控制可发券时间)
applicableScope适用范围(全店/指定商品/指定品类)
totalQuantity总发行量(-1 表示不限)
issuedCount已发放数量
status状态:启用/停用

2. UserCoupon(用户优惠券实例)

用户实际领取或获得的优惠券,关联具体模板。

字段说明
id实例ID
templateId关联模板ID
userId用户ID
code优惠券码(唯一)
status状态:未使用/已使用/已过期
receiveTime领取时间
usedTime使用时间
orderId关联订单ID(使用后填充)

三、关键技术实现(Spring Boot + MyBatis)

1. 定义模板实体类

public class CouponTemplate {
    private Long id;
    private String name;
    private String description;
    private Integer type; // 0: 满减, 1: 折扣, 2: 无门槛
    private String rule; // JSON 字符串
    private LocalDateTime validStartTime;
    private LocalDateTime validEndTime;
    private Integer totalQuantity;
    private Integer issuedCount;
    private Boolean enabled;
    // getters & setters
}

建议rule 字段可进一步封装为 CouponRule 对象,通过 Jackson 注解自动序列化/反序列化。

2. 创建优惠券模板(运营端)

@Service
@Transactional
public class CouponTemplateService {

    @Autowired
    private CouponTemplateMapper templateMapper;

    public void createTemplate(CreateTemplateRequest request) {
        CouponTemplate template = new CouponTemplate();
        template.setName(request.getName());
        template.setType(request.getType());
        template.setRule(JsonUtils.toJson(request.getRule()));
        template.setTotalQuantity(request.getTotalQuantity());
        template.setEnabled(true);
        template.setValidStartTime(request.getValidStartTime());
        template.setValidEndTime(request.getValidEndTime());

        templateMapper.insert(template);
    }
}

3. 用户领取优惠券

@Service
@Transactional
public class UserCouponService {

    @Autowired
    private CouponTemplateMapper templateMapper;
    @Autowired
    private UserCouponMapper userCouponMapper;

    public void receiveCoupon(Long userId, Long templateId) {
        // 1. 校验模板是否存在且启用
        CouponTemplate template = templateMapper.selectById(templateId);
        if (template == null || !template.getEnabled()) {
            throw new BusinessException("模板不可用");
        }

        // 2. 校验是否超发
        if (template.getTotalQuantity() > 0 &&
            template.getIssuedCount() >= template.getTotalQuantity()) {
            throw new BusinessException("优惠券已发完");
        }

        // 3. 生成唯一券码(如 UUID 或雪花 ID)
        String couponCode = IdGenerator.nextCouponCode();

        // 4. 插入用户券实例
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(userId);
        userCoupon.setTemplateId(templateId);
        userCoupon.setCode(couponCode);
        userCoupon.setStatus(CouponStatus.UNUSED);
        userCoupon.setReceiveTime(LocalDateTime.now());
        userCouponMapper.insert(userCoupon);

        // 5. 更新已发数量(需加分布式锁或数据库乐观锁)
        templateMapper.incrementIssuedCount(templateId);
    }
}

注意:高并发下“检查+更新”需防止超发,建议使用数据库乐观锁(version 字段)或 Redis 分布式计数器。

4. 订单结算时校验并计算优惠

public class CouponCalculator {

    public BigDecimal calculateDiscount(BigDecimal orderAmount, CouponTemplate template) {
        CouponRule rule = JsonUtils.parse(template.getRule(), CouponRule.class);
        
        switch (template.getType()) {
            case 0: // 满减
                if (orderAmount.compareTo(rule.getThreshold()) >= 0) {
                    return rule.getDiscount();
                }
                break;
            case 1: // 折扣
                BigDecimal discounted = orderAmount.multiply(rule.getDiscountRate());
                return discounted.min(rule.getMaxDiscount()); // 不超过上限
            case 2: // 无门槛
                return rule.getDiscount();
        }
        return BigDecimal.ZERO;
    }
}

四、扩展与优化建议

  1. 缓存模板数据:将高频访问的模板缓存至 Redis,减少 DB 压力。
  2. 异步发券:通过 MQ 实现批量发券(如活动奖励),避免阻塞主流程。
  3. 过期自动归档:定时任务扫描 validEndTime < now 的模板,标记为过期。
  4. 权限与审计:记录模板创建/修改日志,支持运营操作回溯。
  5. AB测试支持:模板可绑定人群标签,实现精准发券。

结语

一套设计良好的优惠券模板服务,不仅能支撑当前业务,更能为未来营销创新提供技术底座。通过将“规则”与“实例”分离、采用清晰的数据模型、结合事务与并发控制,Java 开发者可以高效构建稳定可靠的优惠券系统。记住:灵活性源于抽象,稳定性来自边界控制。在满足业务的同时,务必守住系统的一致性与可维护性底线。