1

需要基于规则筛选过滤对象的一种通用代码实现方案

业务

下单页查询优惠券列表

  • 查询当前可用的所有优惠券

  • 筛选符合条件的优惠券

    • 校验类目 通用券或者与商品类目相同

    • 校验满减券 如满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" # 下单页查询优惠券 关闭(即忽略此规则) 提交订单 开启(需校验此规则)
}

参考文档

http://www.baeldung.com/sprin...


zhuguowei2
825 声望26 粉丝

引用和评论

0 条评论