作者:废物大师兄 \
起源:www.cnblogs.com/cjsblog/p/9306637.html
电商所谓营销,归根结底都是订单金额的变动;如果咱们分明的晓得订单金额的计算流程是怎么的,那么咱们只须要顺着零碎的计算流程做促销,就不必放心各种促销类型之间产生重叠或者抵触的状况了。
当咱们晓得这个关系后,就能够将营销活动区分为三种类型:改商品价格、改商品小计价格、改订单价格,因为无论什么营销归根结底都是能够形容成改价格。
购物车中任何增删查改都要从新计算促销,所以促销的计算变得尤为重要,感觉京东曾经把促销做到了极致。
从模式上来讲,咱们公司的促销就相当于京东自营,所以很多也都是参考京东自营的,但咱们还没法做到像京东促销那样弱小。
这里,将咱们做的促销跟大家分享一下,只波及后盾接口逻辑局部。
接口的性能就是输出商品列表,返回加了促销分组后的商品列表。
首先要申明几点:
一、不是通用的促销设计,只是咱们公司目前反对的促销设计及逻辑;
二、作者程度无限,不会画图,所以图画得比拟丑,也很粗,心愿大家不要介意;
三、不谈性能
废话就不多说了,上面正式开始。。。
促销类型
后面说了,促销归根结底是改价格。在咱们这里其它单品促销就是改商品价格;而条件促销就相当于改小计的价格;至于赠品促销不设计改价格,能够认为是单品促销的一种类型。
主流程
“ 同类型通过实体进行互斥、不同类型能够互相叠加。”这是他人总结的设计电商促销零碎的根本准则,我也比拟认同。
下面接口主流程就是先利用单品促销,再利用条件促销。略微再细化一点儿就是这样的:
先解决赠品促销,将赠品挂载到主商品(原先用户增加的购物车中的商品我称之为主商品)上,再利用单品促销。
在进行单品促销的时候,很有可能同一个商品命中多个单品促销。这个时候只能取一个促销,此处的计算逻辑是这样的:
- 优惠力度最大的优先
- 优惠力度雷同时,取最新创立的那个(创立工夫最新)
例如:
商品 A 命中四条促销,别离是:【促销 1】直降 2 元,【促销 2】折扣 8 折,【促销 3】直降 1 元。假如 A 的原价时 10 元,那么通过计算【促销 1】8 元,【促销 2】8 元,【促销 3】9 元。这个时候,【促销 3】应该被剔除,假如【促销 2】的创立工夫比【促销 1】要晚,那么应该取【促销 2】。即商品 A 最终命中【促销 2】。原价 10 元,促销价 8 元。
计算商品价格流程
略微解释一下:
- 特价:商品 A 原价 12 元,今日特价 9.9 元。
- 折扣:商品打几折。
- 直降:商品 A 原价 12 元,今日直降 3 元,所以最终 9 元。且当促销价低于原价的 70% 时复原原价。
限购流程
这里有两点须要阐明:
- 限购的话须要查订单零碎,然而方才说了购物车中的任意增删查改都要从新计算促销,所以如果这里间接调订单的话可能订单的顶不住(技术实力还比拟单薄,无奈!!!),思考到这里咱们冗余了订单数据,每次从本地数据库去查。当然,这样必定不准,然而咱们只保障 90% 的状况就能够了,所以这里咱们采纳这种形式。
- 拆商品行。还是用下面的例子,商品 A 命中了【促销 2】,假如【促销 2】限购每人每单 1 件,而当初 A 的数量时 3,那么咱们会拆成 2 行,第一行商品 A 售价 8 元数量 1 件,第二行商品 A 售价 10 元数量 2 件。
条件促销分组
同一个商品可能会命中多个条件促销,而最终每个商品只能利用一个条件促销(即每个商品最终只能属于一个组)
咱们说,同种类型的促销不能叠加,不同类型的促销能够叠加。在咱们这里,单品促销和单品促销不能叠加,条件促销与条件促销不能叠加,单品与条件能够叠加。
程序走到这里,咱们曾经实现了单品促销的解决,接下来解决条件促销。在决定商品应该最终利用哪个条件促销时,咱们的准则是这样的:
1、优先思考满足条件的促销
这句话的意思是,假如商品 A,商品 B 满足【促销 1】满 100 减 20 这个阶梯,同时 A 和 B 又都命中了【促销 2】然而不满足【促销 2】的条件,因为假如【促销 2】的最小阶梯是满 150 减 30。那么这个时候,尽管 A 和 B 都同时命中【促销 1】和【促销 2】,但 A 和 B 一起正好合乎【促销 1】满 100 减 20 的条件,所以这个时候促销 A 和 B 应该最终取【促销 2】
2、同时满足多个条件促销时,取后创立的那个(创立工夫最近)
还是下面的例子,假如 A 和 B 的总金额加起来是 160 元,那么它们都满足【促销 1】和【促销 2】,假如【促销 2】是后创立的,所以此时它们最终命中的条件促销应该取【促销 2】。并且,之后应该讲它们从【促销 1】的商品组中剔除(PS:因为一个商品只能属于一个组,即只能利用一个条件促销)。京东在这里对每种促销做了计算,把最终用哪个促销的决定权交给用户去选,咱们这里不搞这么简单。
说了这么多,可能有点晕,上面举个例子
假如有 A,B,C,D 四个商品,促销 1234 是四个促销
如图,【促销 1】是所有商品,所有 A,B,C,D 四个都命中【促销 1】,换句话说【促销 1】的商品组中有 A,B,C,D
【促销 2】的商品组中有 A,C
【促销 3】的商品组中有 A,B
【促销 4】的商品组中有 A,B,C
假如促销 1,2,3,4 是顺次创立的,也就是说 4 是最晚创立的,1 是最早创立的
再假如,A+B+ C 合乎【促销 4】的其中一个阶梯条件,A+ B 合乎【促销 3】中的其中一个阶梯条件,A+B+C+ D 合乎【促销 1】的其中最低一级的阶梯条件
那么,最终的促销分组应该是这样的:
【促销 4】的商品组有:A,B,C
【促销 3】的商品组为空
【促销 2】的商品组为空
【促销 1】的商品组中有:D,而且不满足最低的阶梯,因为原来 A +B+C+ D 满足最低一级的阶梯,当初只剩下 D 了当然不满足最低一个的阶梯
条件促销分组计算
在代码实现上,这里是两层循环:
- 第一层是条件促销列表
- 第二层是某个条件促销中的商品组
局部代码实现
代码可能是这样的,上面贴出条件促销局部的代码片段:
// 解决条件促销
// 算小计
for (PromotionProductDTO promotionProductDTO : promotionProductDTOList) {promotionProductDTO.setSubtotal(promotionProductDTO.getPromotionPrice().multiply(new BigDecimal(promotionProductDTO.getQuantity())));
}
List<PromotionInfoDTO> conditionPromotionInfoDTOList = promotionInfoMap.get(PromotionTypeEnum.TIAOJIAN.getType());
// 限购
List<PromotionInfoDTO> validConditionPromotionInfoDTOList = new ArrayList<>();
for (PromotionInfoDTO promotionInfoDTO : conditionPromotionInfoDTOList) {if (isMaxConditionPromotionLimit(promotionInfoDTO, userId)) {continue;}
validConditionPromotionInfoDTOList.add(promotionInfoDTO);
}
conditionPromotionInfoDTOList = validConditionPromotionInfoDTOList;
// 按范畴初步将商品归到各个条件促销下(撒网)for (PromotionInfoDTO promotionInfoDTO : conditionPromotionInfoDTOList) {List<PromotionProductDTO> matchedPromotionProductDTOList = new ArrayList<>();
List<PromotionProductEntity> promotionProductEntityList = promotionInfoDTO.getDefinitiveProductEntityList();
for (PromotionProductDTO promotionProductDTO : promotionProductDTOList) {
// 商品匹配到的促销
if (promotionInfoDTO.getProductRange() == PromotionPruductRangeEnum.ALL.getValue()) {matchedPromotionProductDTOList.add(promotionProductDTO);
}else if (promotionInfoDTO.getProductRange() == PromotionPruductRangeEnum.CATEGORY.getValue()) {Set<String> secondCategorySet = promotionProductEntityList.stream().map(PromotionProductEntity::getProCategorySecond).collect(Collectors.toSet());
if (secondCategorySet.contains(promotionProductDTO.getCategoryCode())) {matchedPromotionProductDTOList.add(promotionProductDTO);
}
}else if (promotionInfoDTO.getProductRange() == PromotionPruductRangeEnum.SPECIFIED.getValue()) {Set<Long> specialProductIdSet = promotionProductEntityList.stream().map(PromotionProductEntity::getProductId).collect(Collectors.toSet());
if (specialProductIdSet.contains(promotionProductDTO.getId())) {matchedPromotionProductDTOList.add(promotionProductDTO);
}
}
}
// 促销匹配到的商品
promotionInfoDTO.setMatchedProductDTOList(matchedPromotionProductDTOList);
// 判断促销匹配的这些商品是否满足条件
BigDecimal totalAmount = BigDecimal.ZERO;
for (PromotionProductDTO promotionProductDTO : matchedPromotionProductDTOList) {totalAmount = totalAmount.add(promotionProductDTO.getSubtotal());
}
PromotionStairEntity promotionStairEntity = matchStair(promotionInfoDTO.getDefinitiveStairEntityList(), totalAmount);
if (null != promotionStairEntity) {promotionInfoDTO.setPromotionStairEntity(promotionStairEntity);
}
}
// 按满足条件与否以及促销创立的先后顺序进一步归档商品(即分组)// 挑选出满足条件的促销,并依照创立工夫降序排序
List<PromotionInfoDTO> matchedConditionPromotionInfoDTOList = conditionPromotionInfoDTOList.stream()
.filter(x->null != x.getPromotionStairEntity())
.sorted(Comparator.comparing(PromotionInfoDTO::getCreateTime).reversed())
.collect(Collectors.toList());
// 去重,以保障每个组中的商品之间无交加
int len = matchedConditionPromotionInfoDTOList.size();
for (int i = 0; i < len - 1; i++) {PromotionInfoDTO majorPromotionInfoDTO = matchedConditionPromotionInfoDTOList.get(i);
for (int j = i + 1; j < len; j++) {PromotionInfoDTO minorPromotionInfoDTO = matchedConditionPromotionInfoDTOList.get(j);
for (PromotionProductDTO majorMatchedPromotionProductDTO : majorPromotionInfoDTO.getMatchedProductDTOList()) {minorPromotionInfoDTO.setMatchedProductDTOList(minorPromotionInfoDTO.getMatchedProductDTOList()
.stream()
.filter(x -> !x.getId().equals(majorMatchedPromotionProductDTO.getId()))
.collect(Collectors.toList()));
}
}
}
// 最终命中的促销
List<PromotionInfoDTO> ultimatePromotionInfoDTOList = new ArrayList<>();
// 从新计算各组匹配的阶梯规定
for (PromotionInfoDTO promotionInfoDTO : matchedConditionPromotionInfoDTOList) {List<PromotionProductDTO> promotionProductDTOS = promotionInfoDTO.getMatchedProductDTOList();
// 过滤掉空的促销
if (null == promotionProductDTOS || promotionProductDTOS.size() < 1) {continue;}
ultimatePromotionInfoDTOList.add(promotionInfoDTO);
BigDecimal totalAmount = BigDecimal.ZERO;
for (PromotionProductDTO promotionProductDTO : promotionProductDTOS) {totalAmount = totalAmount.add(promotionProductDTO.getSubtotal());
}
// 查问该组商品满足的最高阶梯
PromotionStairEntity promotionStairEntity = matchStair(promotionInfoDTO.getDefinitiveStairEntityList(), totalAmount);
if (null != promotionStairEntity) {
// 设置这组商品命中的促销的哪一个阶梯
promotionInfoDTO.setPromotionStairEntity(promotionStairEntity);
// 设置每个商品最终命中的惟一的条件促销
for (PromotionProductDTO promotionProductDTO : promotionProductDTOS) {promotionProductDTO.setConditionpromotionInfoDTO(promotionInfoDTO);
}
}else {
// 计算还差多少钱满足最低阶梯
List<PromotionStairEntity> promotionStairList = promotionInfoDTO.getDefinitiveStairEntityList().stream().sorted(Comparator.comparing(PromotionStairEntity::getMinimumCharge)).collect(Collectors.toList());
PromotionStairEntity promotionStairEntity2 = promotionStairList.get(0);
BigDecimal minimumCharge = promotionStairEntity2.getMinimumCharge();
BigDecimal balance = minimumCharge.subtract(totalAmount);
promotionInfoDTO.setBalance(balance);
}
}
返回的数据接口
最终返回的应该是一个列表,列表中的每一个元素代表一个条件促销(即分组)
接口看起来可能是这样的:
参考:
http://www.woshipm.com/pd/741…\
http://www.woshipm.com/pd/594…\
http://www.woshipm.com/pd/716…
近期热文举荐:
1.Java 15 正式公布,14 个新个性,刷新你的认知!!
2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!
3. 我用 Java 8 写了一段逻辑,共事直呼看不懂,你试试看。。
4. 吊打 Tomcat,Undertow 性能很炸!!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!