vivo商城計價中心 - 從容應對複雜場景價格計算

vivo網際網路技術發表於2021-08-23

一、背景

隨著vivo商城的業務架構不斷升級,整個商城較為複雜多變的營銷玩法被拆分到獨立的促銷系統中。

拆分後的促銷系統初期只是負責了營銷活動玩法的維護,促銷中最為重要的計價業務仍然遺留在商城主站業務中,且由於歷史建設問題,商城核心交易鏈路中商詳頁、購物車、下單這三塊關於計價邏輯是分開獨立維護的,沒有統一,顯然隨著促銷優惠的增加或者玩法的變動,商城側業務重複開發量會顯著加大。

促銷系統的獨立,計價相關業務能力從業務邊界上也應由促銷系統提供,因此促銷側需要從頭開始設計促銷計價相關能力。

二、原有計價業務

2.1 計價業務場景

商城原有涉及到計價業務的主要是商詳頁、購物車、確認下單、提交訂單這幾個業務場景。

如果將每一個影響最終售賣價的優惠叫做計價因子的話,那前述幾種場景下對於售賣價有影響的計價因子歸為三大類:

  • 優惠活動(單品優惠、訂單優惠)

  • 優惠券(優惠券、代金券)

  • 虛擬抵扣(積分、換新鼓勵金)

對於每種計價場景與計價因子有如下關係:

2.2 原有計價模型

對於具體執行的計價業務中各計價因子間是有一定的先後優先順序關係的,綜合如下圖所示,也在一定程度說明了原有計價業務模型:

三、促銷計價模型

3.1 分層模型

促銷系統從零搭建基礎計價能力,對於系統的穩定性及擴充套件性必須有一定的保障,而這也就對於促銷系統的計價模型提出了一定的要求,通用的基礎計價模型最好是能有過一定的實踐經歷驗證過的,因此我們採用了傳統電商久經考驗的計價模型:分層計價。

所謂的分層計價即傳統電商中優惠涉及的三個層面:商品級、店鋪級、平臺級,正常情況下不同級別的優惠預設是可以疊加的,同一級別的優惠預設情況下是互斥的

這裡需要說明的是,每一層級的優惠計算的時候,對於有些優惠的門檻條件是否滿足需要依賴原價,預設情況下依賴於上一個層級的優惠計算後的價格,即商品級優惠計算依賴商品原價,店鋪級優惠依賴於商品級優惠計算後的價格,平臺級優惠依賴於店鋪級優惠計算後的價格。

疊加規則特別說明:

正常優惠疊加是指兩個優惠可以同時享受,對於不同層級的優惠預設就是疊加的,對於同一層級的優惠預設是不疊加的,比如正常情況下,優惠券下的各種型別券是隻能用一張的。

但某些場景下,業務上會指定同一層級的優惠可以疊加使用的,同時指定疊加使用的場景下還會分為普通疊加和並行疊加,舉個例子:訂單優惠和優惠券這兩個型別的疊加就屬於普通疊加(優惠券門檻是否滿足的判斷取決於訂單優惠後的價格),優惠券和代金券的疊加屬於並行疊加(優惠券和代金券的門檻是否滿足的判斷都取決於這兩者的前序優惠後的價格)。

對於同一層級的優惠按不同維度分為:必選/勾選、可疊加(並行疊加/普通疊加)/不可疊加

3.2 新的計價模型

3.3 核心計價流程

3.3.1 主流程

通過前述計價模型可以得知,在計算優惠價時的先後順序是:商品級(CalcItem)、店鋪級(CalcShop)、平臺級(CalcGroup),另外根據一些特殊業務場景,增加了可能的中斷業務邏輯(CalcInterrupt),因此可得到下圖所示的最粗粒度的計價流程;

那這三個級別的計算優惠價內部又是如何實現的呢?經過業務抽象,這三個級別的計算可以變成一個通用的計算優惠邏輯,僅有優惠級別的區分。

3.3.2 通用流程

經過業務抽象發現三個級別的優惠計算的通用邏輯:

  • 獲取當前層級的優惠查詢器(Get Current Level PromotionGetter)

  • 過濾優惠查詢器(Filter PromotionGetter)

  • 查詢優惠(Get Promotion)

  • 過濾優惠(Filter Promotion)

  • 通過計價引擎計算優惠(Calc Engine)

  • 過濾計價結果(Filter CalcResult)

因此我們得出如下的通用的計價流程:

通用計價流程中的又有幾個相對靈活的與業務相關過濾邏輯,從後面的細節流程中可以瞭解更多的實現。

3.3.3 細節流程

之所以在通用計價流程中會有幾個過濾節點,是因為在業務上會有一些特殊的過濾邏輯,比如商詳頁來源的時候,只能使用商品級優惠查詢器,某個優惠只能特殊渠道去享受等等。

所以需要抽象出一個通用的可擴充套件的過濾機制來實現業務需求,因此會按照不同維度去定製一些鏈式過濾器,執行流程如下圖所示:

當然圖中所示的不同維度額過濾器只是目前業務中的一部分,比如還有按照終端、付款方式、外部業務方等等,這些在具體實現的時候可以非常靈活的支援。

那上述過濾器是如何制定?以及與業務如何關聯的?

上圖中列出部分業務定製過濾序器,自定義過濾器後會自動註冊到統一的優惠業務過濾器工廠中,在前述的計價流程中,需要用到相關過濾器時,只需帶上相關上下文引數可以自動從過濾器工廠中獲取匹配的過濾器。

3.3.4 完整全流程

把前面這一系列流程中進行一個組合拼裝,就可以得到計價的完整全流程圖,如下:

從這個完整流程圖中,可以看到一個通用穩定的核心計價流程以及一個支援業務多變的定製過濾器,既保證了核心的穩定,又保留靈活的擴充套件。

四、系統核心設計

在通用的計價執行流程中一個節點是「Calc Engine」,也就是計價引擎,這是整個計價邏輯中最核心底層的能力,由它來判定每個優惠是否能被使用者享有。

4.1 統一優惠模型

由於計價中心在建設的時候,已經存在了促銷系統中的各個優惠活動、獨立的優惠券及代金券、遺留在商城主站的未遷移的優惠,因此想用相容這麼多的優惠型別,必然需要建立一個統一的優惠模型,而在建設過程中需將現有的優惠模型進行適配轉換至統一模型。

統一優惠模型中的一些關鍵資訊有:優惠標識、優惠型別、優惠模板id、開始結束時間、優惠引數及一些擴充套件引數等。

4.2 優惠模板

1)在進行促銷計價時,每個具體的優惠都會對應一個唯一的優惠模板,每個優惠模板本質上是一個JSON字串,只是這些JSON字串是由遵循了一定特殊邏輯規則的元資訊資料轉化而成,而這些元資訊在被計價引擎解釋執行時,都是返回布林型別標識是否通過。

2)基本的元資訊資料有這幾種:

**AndMeta(與)**對應邏輯關係中的“與”關係,表示該型別的元資訊所包含的子元資訊解釋執行都返回真才為真;

**OrMeta(或)**對應邏輯關係中的“或“關係,表示該型別的元資訊所包含的子元資訊任一解釋執行返回真就為真;

**NotMeta(非)**對應邏輯關係中的“非”關係,表示該型別中元資訊所包含的子元資訊解釋為假當前元資訊為真;

**ConditionalMeta(條件)**如果條件引數不存在或者從上下文獲取引數指定的布林值不為true,則當前元資訊返回真,否則根據元資訊中包含的子元資訊解釋執行的結果作為當前元資訊執行結果;

**ComplexMeta(組合元資訊)**該元資訊作為所有模板的通用載體,該元資訊中包含兩個重要資訊conditon、action,兩者的關係是隻有condition條件都滿足後後,才會去執行後續的action,而condition和action都可能為前述中的各種元資訊的複雜組合。

3)模板元資訊關係:

4)優惠模板示例:

{
  "type": "COMPLEX",
  "condition": {
    "type": "AND",
    "metas": [
      {
        "type": "CONDITIONAL",
        "metas": [
          {
            "type": "CONDITION",
            "metaCode": "terminalCheckCondition"
          }
        ],
        "param": "needTerminalCheck"
      },
      {
        "type": "CONDITION",
        "metaCode": "amountOverCondition"
      }
    ]
  },
  "action": {
    "type": "AND",
    "metas": [
      {
        "type": "ACTION",
        "metaCode": "cutPriceAction"
      },
      {
        "type": "ACTION",
        "metaCode": "freezeCouponAction"
      }
    ]
  }
}

4.3 計價引擎

計價引擎本質上就是對應優惠模板的解釋執行,並配合相關上下文,進行優惠計算,關鍵程式碼如下:

private boolean executeMeta(Meta meta, EngineContext context) {
    if (meta instanceof AndMeta) {
        return executeAndMeta((AndMeta)meta, context);
    } else if (meta instanceof OrMeta) {
        return executeOrMeta((OrMeta) meta, context);
    } else if (meta instanceof NotMeta) {
        return executeNotMeta((NotMeta)meta, context);
    } else if (meta instanceof ComplexMeta) {
        return executeComplexMeta((ComplexMeta)meta, context);
    } else if (meta instanceof ConditionalMeta) {
        return executeConditionalMeta((ConditionalMeta)meta, context);
    } else {
        return executeIMeta(meta, context);
    }
}
 
......
 
private boolean executeComplexMeta(ComplexMeta complexMeta, EngineContext context) {
    Meta condition = complexMeta.getCondition();
    Meta action = complexMeta.getAction();
    return executeMeta(condition, context) && executeMeta(action, context);
}
 
private boolean executeConditionalMeta(ConditionalMeta conditionalMeta, EngineContext context) {
    PromotionContext promotionContext = context.getPromotionContext();
    if (promotionContext == null || promotionContext.getParameters() == null) {
        return true;
    }
 
    String conditionParam = conditionalMeta.getParameter();
    String sNeedProcess = promotionContext.getParameters().get(conditionParam);
    if (sNeedProcess == null) {
        return true;
    }
 
    boolean needProcess = Boolean.parseBoolean(sNeedProcess);
    if (needProcess) {
        return executeMeta(conditionalMeta.getMetas().get(0), context);
    } else {
        return true;
    }
}
 
private boolean executeIMeta(Meta meta, EngineContext context) {
    IMeta iMeta = MetaFactory.get(meta.getMetaDef().getMetaCode());
    if (iMeta == null) {
        throw new CalcException("meta not found, metaCode=" + meta.getMetaDef().getMetaCode());
    }
 
    return iMeta.execute(context);
}

五、小結

通過前面幾章內容的描述,我們基本把vivo商城促銷系統建設計價中心的關鍵思路闡述完了。建設完計價中心後,整個促銷系統的核心基礎才立住,但這也只是個開始,整個商城圍繞著促銷計價中心仍然還有其他待建設的內容,比如整個商城的營銷價格能力矩陣,價格監控,商城時光機等等,而這些內容我們後續有機會也會陸續輸出相關文章,與大家一起交流學習。

作者:vivo網際網路伺服器團隊-Wei Fuping

相關文章