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

作者:vivo 互聯網服務器團隊 - Wei Fuping

一、背景

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

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

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

二、原有計價業務

2.1 計價業務場景

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

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

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

圖片

2.2 原有計價模型

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

圖片

三、促銷計價模型

3.1 分層模型

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

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

圖片

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

疊加規則特別說明:

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

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

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

3.2 新的計價模型

圖片

3.3 核心計價流程

3.3.1 主流程

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

圖片

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

3.3.2 通用流程

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

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

圖片

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

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

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/JwbYMajRS4kMPx6Zc5Ng8A