在电商、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;
}
}
四、扩展与优化建议
- 缓存模板数据:将高频访问的模板缓存至 Redis,减少 DB 压力。
- 异步发券:通过 MQ 实现批量发券(如活动奖励),避免阻塞主流程。
- 过期自动归档:定时任务扫描
validEndTime < now的模板,标记为过期。 - 权限与审计:记录模板创建/修改日志,支持运营操作回溯。
- AB测试支持:模板可绑定人群标签,实现精准发券。
结语
一套设计良好的优惠券模板服务,不仅能支撑当前业务,更能为未来营销创新提供技术底座。通过将“规则”与“实例”分离、采用清晰的数据模型、结合事务与并发控制,Java 开发者可以高效构建稳定可靠的优惠券系统。记住:灵活性源于抽象,稳定性来自边界控制。在满足业务的同时,务必守住系统的一致性与可维护性底线。