要实现多优惠券叠加核销及场景互斥逻辑,需从规则定义、校验流程、核销执行 三方面入手,结合优惠券模板(CouponTemplate
)的配置来控制业务逻辑。以下是具体实现方案:
一、核心思路
通过优惠券模板(CouponTemplate
)配置每张优惠券的叠加规则 和适用场景,在核销前先校验多券的兼容性,再按规则执行核销。
二、具体实现步骤
1. 扩展优惠券模板(CouponTemplate
)
java
import lombok.Data;
import java.util.List;
/**
* 优惠券模板:定义优惠券的基础规则(可叠加、适用场景等)
*/
@Data
public class CouponTemplate {
private Long id;
private String name;
private CouponTypeEnum type; // 优惠券类型(满减、折扣等)
/** 可叠加的优惠券类型(为空表示可与所有类型叠加;非空则仅能与指定类型叠加) */
private List<CouponTypeEnum> allowStackWith;
/** 适用场景标签(如 "生鲜"、"数码"、"全品类" 等) */
private List<String> sceneTags;
}
2. 多优惠券叠加核销实现
步骤 1:校验多券叠加规则在核销前,先检查所有优惠券是否满足 "可叠加" 规则:
java
/**
* 校验多优惠券是否可叠加
* @param coupons 待核销的优惠券列表(需包含关联的 CouponTemplate)
* @return 是否可叠加
*/
private boolean checkStackable(List<UserCoupon> coupons) {
// 若只有1张券,默认可核销
if (coupons.size() <= 1) return true;
// 遍历每两张券,检查是否满足互相叠加规则
for (int i = 0; i < coupons.size(); i++) {
for (int j = i + 1; j < coupons.size(); j++) {
UserCoupon couponA = coupons.get(i);
UserCoupon couponB = coupons.get(j);
CouponTemplate templateA = couponA.getTemplate();
CouponTemplate templateB = couponB.getTemplate();
// 检查 A 是否允许与 B 类型叠加
if (!templateA.getAllowStackWith().contains(templateB.getType())) {
log.warn("优惠券{}(类型:{})不允许与优惠券{}(类型:{})叠加",
couponA.getId(), templateA.getType(),
couponB.getId(), templateB.getType());
return false;
}
}
}
return true;
}
步骤 2:执行多券核销若校验通过,循环调用各券的核销逻辑:
java
public void writeOffMultipleCoupons(List<UserCoupon> coupons) {
// 1. 校验叠加规则
if (!checkStackable(coupons)) {
throw new CouponStackException("优惠券不可叠加");
}
// 2. 循环核销每张券
for (UserCoupon coupon : coupons) {
CouponTypeEnum type = coupon.getTemplate().getType();
ICouponWriteOffService service = factory.getWriteOffService(type);
service.writeOff(coupon);
}
}
3. 多优惠券场景互斥实现
步骤 1:定义订单与商品的场景标签 订单或商品需携带 "场景标签",与优惠券模板的 sceneTags
匹配:
java
@Data
public class Order {
private Long id;
private List<OrderItem> items; // 订单商品
private List<String> sceneTags; // 订单整体场景(如 "全品类")
}
@Data
public class OrderItem {
private Long id;
private String sceneTag; // 商品所属场景(如 "生鲜")
private BigDecimal price;
}
步骤 2:校验优惠券场景兼容性
java
/**
* 校验优惠券与订单场景是否匹配
* @param coupons 待核销优惠券
* @param order 目标订单
* @return 是否匹配
*/
private boolean checkSceneMatch(List<UserCoupon> coupons, Order order) {
for (UserCoupon coupon : coupons) {
CouponTemplate template = coupon.getTemplate();
List<String> couponScenes = template.getSceneTags();
// 检查订单整体场景是否匹配
boolean orderSceneMatch = order.getSceneTags().stream()
.anyMatch(couponScenes::contains);
// 检查订单商品场景是否匹配
boolean itemSceneMatch = order.getItems().stream()
.anyMatch(item -> couponScenes.contains(item.getSceneTag()));
if (!orderSceneMatch && !itemSceneMatch) {
log.warn("优惠券{}(场景:{})与订单场景不匹配",
coupon.getId(), couponScenes);
return false;
}
}
return true;
}
步骤 3:整合场景校验到核销流程
java
public void writeOffCouponsWithSceneCheck(List<UserCoupon> coupons, Order order) {
// 1. 校验场景匹配
if (!checkSceneMatch(coupons, order)) {
throw new CouponSceneException("优惠券场景不匹配");
}
// 2. 校验叠加规则(复用之前的 checkStackable 方法)
if (!checkStackable(coupons)) {
throw new CouponStackException("优惠券不可叠加");
}
// 3. 执行核销
writeOffMultipleCoupons(coupons);
}
三、总结
- 多券叠加 :通过
CouponTemplate
的allowStackWith
配置可叠加的优惠券类型,核销前校验每两张券是否满足互相叠加规则。 - 场景互斥 :通过
CouponTemplate
的sceneTags
配置适用场景,核销前校验优惠券场景与订单 / 商品场景是否匹配。 - 优势 :所有规则通过
CouponTemplate
外部配置,无需修改代码即可灵活控制优惠券的叠加和场景逻辑,扩展性极强。