目前活动有多种优惠方式,并且复杂的优惠规则很难让用户自行计算优惠叠加的顺序。需要系统推荐出一条最优惠的方式。
目前的优惠形式有订单满减、组合满减、赠品、换购、单品、运费、打包费等活动。
优惠的计算顺序可以分为平行式和渐进式。平行式优惠之间没有依赖关系。渐进式优惠存在依赖关系,下一步优惠取决于上一步优惠结果。如下图所示,不同的优惠计算顺序会影响最终的优惠金额。
策略需要配置互斥组,最终需要根据配置挑选可用优惠,默认不在互斥组中的优惠共享。
举个例子:
DiscountconfluctGro
CalculateEngine -> 计价引擎,用于在购物车场景,试算提单组合优惠。
public class CalculateEngine{
/**
* 算价引擎算价
*/
public DiscountResponse calculate(CalculateReq req, Promotion[] promotionArr){
dfs(req, CartLocker.of(), promotionArr, new int[promotionArr.length]);
return getCalculateResp(clList);
}
private List clList = new ArrayList<>();
/**
* 回溯算法计算优惠全排列组合
*/
public void dfs(CalculateReq req, CartLocker cartLocker, Promotion[] promotionArr, int[] visited){
boolean end = true;
for (int i = 0; i < promotionArr.length; i++){
if (visited[i] == 1){
continue;
}
visited[i] = 1;
end = false;
CartLocker cpCL= cartLocker.copy();
// 互斥组判断通过
if (StrategyGroup.isNotConflict(cpCl.getUsedStrategySet(),
promotion[i].getStrategy().getType()
)){
// 策略计算,结果存在cartLocker中
promotion[i].getStrategy().apply(req, cpCl);
}
// 全排列
dfs(req, cpCL, promotionArr, visited);
visited[i] = 0;
}
if (end){
clList.add(cartLocker);
}
}
/**
* 获取最佳的一个优惠组合
*/
public DiscountRes getCalculateResp(List cartLockerList){
DiscountRes res = null;
if (CollectionUtil.isEmpty(cartLockerList)){
return res;
}
CartLocker maxCartLocker = cartLockerList.get(0);
for (int i = 1; i < cartLockerList.size(); i++){
if (cartLockerList.get(i).getPromotionAmount() > maxCartLocker.getpromotionAmount()){
maxCartLocker = cartLockerList.get(i);
}
}
DiscountRes res = new DiscountRes();
res.setPromotionAmount(maxCartLocker.getPromotionAmount());
res.setCartLocker(maxCartLocker);
res.setDiscountInfo(maxCartLocker.getDiscountInfo());
return res;
}
}
CartLocker -> 记录购物车所享有的优惠信息,包括每件商品对应优惠等信息。
class CartLocker{
// 活动id列表
private List promotionId = new ArrayList<>();
// 策略列表
private Set strategySet = new HashSet<>();
// >
private Map> productLockersMap;
// >
private Map> promotionLockersMap;
@Data
public static class Locker{
private String promotionId;
private String skuId;
/**
* 锁定数量
*/
private Integer quantity;
/**
* 商品分摊金额
*/
private Integer promotionAmount;
}
}
SkuDeduct -> 单品立减策略
class SkuDeduct{
private String skuId;
private Integer deductAmount;
public ApplyResult apply(CalculateRequest req, CartLocker cartLocker){
List tmpLockQueue = new ArrayList<>();
RowLockerManager rowLockerManager = new RowLockerManager(cartLocker);
int promotionAmount = 0;
for(Proudct p : req.getProducts()){
if (Objects.equals(skuId, p.getSkuId())){
for (int i = 0; i < p.getQuantity(); i++){
if (rowLockerManager.tryLock(p)){
tmpLockQueue.add(p);
promotionAmount += Math.min(deductAmount, p.getUnitPrice());
}
}
break;
}
}
if (tmpLockqueue.size() == 0){
return null;
}
// 商品锁定
rowLockerManager.lockProducts(tmpLockQueue);
// 优惠分摊
rowLockerManager.getLocker(skuId).setPromotionAmount(promotionAmount);
// 将锁定行更新到cartLocker中
cartLocker.addLockers(rowLockerManager.getLockers());
// 返回结果
return ApplyResult.builder().lockers(rowlockerManager.getLockers()).promotionAmount(promotionAmount).build();
}
}
RowLockManager
RowLockManager主要对商品件进行控制,防止使用多种优惠
class RowLockerManager{
private List lockers;
//
private Map lockerMap;
//
private Map tryLockerMap;
private CartLocker cartLocker;
public boolean tryLock(Product product){
var hasUsedCnt = cartLocker.getUsedCnt(product.getSkuId());
var tmpUsedCnt = lockerMap.get(product.getSkuId()).getQuantity() +
tryLockerMap.get(product.getSkuId()).getQuantity();
// 没有商品可以使用
if (hasUsedCnt + tmpUsedCnt == product.getQuantity()){
return false;
}
// 更新tryLockerMap, 这里省略了些判断代码
tryLockerMap.get(product.getSkuId()).setQuantity($ + 1);
return true;
}
public void lock(Product product){
// 只是简单流程代码
tryLockerMap.get(product.getSkuId()).setQuantity($ - 1);
lockerMap.get(product.getSkuId()).setQuantity($ + 1);
}
public void lockProducts(List products){
for(Product product:products){
lockProduct(product);
}
}
}