需要基于规则筛选过滤对象的一种通用代码实现方案
业务
下单页查询优惠券列表
查询当前可用的所有优惠券
筛选符合条件的优惠券
校验类目 通用券或者与商品类目相同
校验满减券 如满100减50 订单金额需满100可用
校验店铺券 不能使用其他店铺的店铺券
实现方案一
// 下单页查询可用优惠券
public List<Coupon> getAvailableCoupons(String productCategory, int orderPrice, String shopId, String userId){
// 查询所有可用优惠券
List<Coupon> couponList = couponMapper.queryCoupons(userId);
// 筛选优惠券
List<Coupon> availableList = new ArrayList<>();
for (Coupon coupon : couponList) {
// 校验类目券 如图书券不可用于购买手机
if(!checkCategory(coupon,productCategory)){ // 类目不匹配
continue;
}
// 校验满减券 如满100减50
if(!checkPrice(coupon,orderPrice)){ // 订单金额不足满减金额
continue;
}
// 校验店铺券
if(!checkShopCoupon(coupon,shopId)){ // 排除其他店铺的优惠券
continue;
}
availableList.add(coupon);
}
return availableList;
}
缺点
单元测试不方便 如想测试校验店铺券失败的情况 即使校验通过了 也不能确定确实调用了
checkShopCoupon
有可能是checkCategory
失败了或者checkPrice
失败导致的 即没办法仅仅针对某一种校验进行充分测试扩展不方便 如想新增一个筛选条件 如图书类目不可使用平台券 就得修改代码 添加一个如下的校验 然后发布上线 以后要取消该用券限制 也得同样修改代码 发布上线
if(!checkBookCategory(productCategory,couponFrom)){
// 校验图书类目 不可使用平台券
continue;
}
实现方案二
通过配置规则的方式来筛选优惠券 如使用spel
来配置上述规则
校验类目规则 不是通用券且不等于作品类目 排除 category != 'all' and category != productCategory
校验满减金额规则 满减券且订单金额不足满减金额 排除 fullPrice != null and orderPrice < fullPrice
校验店铺券规则 店铺券且所属店铺不同于作品的 排除 couponFrom == 'shop' and couponShopId != productShopId
筛选优惠券前 查询上述规则列表 并转为Rule对象 此时代码如下所示
public List<Coupon> getAvailableCoupons2(String productCategory, int orderPrice, String shopId, String userId){
// 查询所有可用优惠券
List<Coupon> couponList = couponMapper.queryCoupons(userId);
// 筛选优惠券
List<Coupon> availableList = new ArrayList<>();
// 从数据库查询出优惠券过滤规则配置
String rules = couponMapper.getFilterRules();
List<CouponFilterRule> couponFilterRules = convertToFilterRules(rules);
outer:for (Coupon coupon : couponList) {
for (CouponFilterRule rule : couponFilterRules) {
// 遍历规则 一旦满足 就排除
if(rule.matches(coupon)){
continue outer;
}
}
availableList.add(coupon);
}
return availableList;
}
优点
单元测试友好 可以灵活自由的测试某一种校验 没有其他校验的干扰 如仅测试校验类目
// coupon1 通用券 coupon2 类目券等于商品类目 coupon3 类目券且不同于商品类目【被排除】
when(couponMapper.queryCoupons(userId)).thenReturn(newArrayList(coupon1,coupon2,coupon3));
when(couponMapper.getFilterRules()).thenReturn("category != 'all' and category != productCategory");
// 验证应该返回2个优惠券
assertEquals(2, availableList.size());
// 校验ID应该是1,2
...
扩展方便 如想增加一个校验规则 如图书类目不支持使用平台券 只需增加一个配置规则即可 如下所示
productCategory == 'book' and couponFrom == 'platform'
无需任何代码开发和上线 以后去掉该规则也方便
额外的工作
为了支持spel
表达式 Coupon对象中需要冗余订单金额 作品类目 作品所属店铺Id等信息
其他问题
可能需要基于命中不同的规则 返回不同的提示 如类目不匹配 订单金额不足等
提交订单也需要校验使用的优惠券是否合法 有些场景要求下单页查询优惠券不限制 提交订单时才限制 如老版本不支持用券 下单页可以选择优惠券 但提交订单失败 提示用户去升级版本
解决
规则配置增加对应的文案 和 场景开闭状态 如
{
"rule": "not checkVersion(3.0.0)", # 版本小于3.0.0不支持用券
"message": "你当前的版本过旧不支持使用优惠劵,请更新版本", # 提示文案
"switchStatus": "01" # 下单页查询优惠券 关闭(即忽略此规则) 提交订单 开启(需校验此规则)
}