概述
購物車模塊的難點有以下幾點
1、購物車促銷的顯示和價格計算
2、結算頁促銷的顯示和價格計算
3、計算和顯示邏輯復雜,還要時時判斷活動的有效性
4、兩個地方的購物車顯示和計算,有一樣的邏輯的地方,也有差異的
5、促銷規則的多種多樣
思路
接下來,我們來詳細看一下Javashop電商系統中購物車的架構思路:
一、將存儲分為兩部分:
sku原始數據
用戶選擇的促銷活動
每次購物車的顯示,都根據這些數據進行一次重新渲染和計算
二、將促銷規則的算法和計算分開
抽像出規則對象,由每個活動根據原始數據去生成這些規則
然后統一將這些規則進行計算形成要顯示的效果和價格
三、把不可避免的耗性能的操作,放在加入購物車中完成,而不是在列表循環中完成
領域模型
CartVO
屬性 | 說明 | 備注 |
---|---|---|
skuList | 規格列表 | 對應CartSkuVO對象 |
sellerId | 賣家id | |
price | 價格對象 | 對應PriceDetailVO對象 |
ruleList | 促銷規則列表 | 對應PromotionRule對象 |
couponList | 優惠卷列表 | 對應CouponVO對象 |
giftList | 贈品列表 | 對應FullDiscountGiftDO |
giftCouponList | 贈送優惠卷列表 | 對應CouponVO對象 |
promotionNotice | 促銷提示 | 目前只有滿優惠提示 |
CartSkuVO
屬性 | 說明 | 備注 |
---|---|---|
name | 商品名稱 | |
skuId | skuid | |
specList | 規則列表 | 對應SpecValueVo對象 |
singleList | 單品活動列表 | 對應CartPromotionVo對象, 顯示在列表中供用戶選擇 |
groupList | 組合活動列表 | 對應CartPromotionVo對象 |
invalid | 是否失效 | |
errorMessage | 失效原因 | |
originalPrice | 商品原價 | 用於計算優惠的基礎價格 |
purchasePrice | 成交價 | |
num | 數量 | |
subtotal | 小計 | |
promotionTags | 促銷標簽 | 顯示當前sku應用了何種優惠 |
PriceDetailVO
屬性 | 說明 | 備注 |
---|---|---|
originalPrice | 原價 | |
goodsPrice | 成交價 | |
freightPrice | 運費 | |
totalPrice | 合計 | |
discountPrice | 總優惠價格 | |
cashBack | 返現金額 | 所有單品活動產生的優惠 |
fullMinus | 滿減金額 | |
couponPrice | 優惠卷抵扣金額 | 不計在返現中 |
isFreeFreight | 是否免運費 | |
exchangePoint | 用了多少積分 | 用於兌換此商品 |
PromotionRule
屬性 | 說明 | 備注 |
---|---|---|
originalPrice | 原價 | |
goodsPrice | 成交價 | |
freightPrice | 運費 | |
totalPrice | 合計 | |
discountPrice | 總優惠價格 | |
cashBack | 返現金額 | 所有單品活動產生的優惠 |
fullMinus | 滿減金額 | |
couponPrice | 優惠卷抵扣金額 | 不計在返現中 |
isFreeFreight | 是否免運費 | |
exchangePoint | 用了多少積分 | 用於兌換此商品 |
CouponVO
屬性 | 說明 | 備注 |
---|---|---|
memberCouponId | 會員優惠卷id | 會員領取后的唯一id,取消時或使用時 要用此id |
couponId | 此優惠卷的id | 會員領取后,此值不變,不能做為使用時調用 |
sellerId | 賣家id | |
amount | 面值 | |
endTime | 有效期 | 到秒的時間戳 |
useTerm | 使用條件 | 如:“滿100元可用” |
selected | 是否選中 | 當用戶選擇此優惠卷時,會標記為1,未選中時為0 |
enable | 是否可用 | 當不可用時(不滿足條件或已過期)為0,可用為1 |
數據存儲
SelectedPromotionVo
屬性 | 說明 | 備注 |
---|---|---|
singlePromotionMap | 用戶選擇的單品活動 | |
couponMap | 用戶選擇的優惠卷 |
1、singlePromotionMap
類型:Map
key是店鋪id ,對應此店鋪對應的促銷活動
2、couponMap
類型:Map
key是店鋪id ,對應此店鋪使用的優惠卷
項 | 前綴 | 連接 | 存儲對象 |
---|---|---|---|
購物車原始數據 | CART_ORIGIN_DATA_PREFIX | buyer.uid | List\ |
購物車促銷 | CART_PROMOTION_PREFIX | buyer.uid | SelectedPromotionVo |
購物車的添加
1、調用原始數據業務類(CartOriginDataManager)的添加方法
根據sku讀出商品數據,並形成CartSkuOriginVo
2、填充促銷信息
讀取此商品的促銷活動,填充到上述的Vo中
此時如果傳遞了要使用的活動id(需要使用活動的,見下面)
3、寫入緩存
形成list\並寫入redis
4、使用活動
如果傳遞了活動id,則調用CartPromotionManager 使用此活動
5、寫入緩存
在使用活動時,會將組合好的 singlePromotionMap 寫入redis
購物車顯示
通過“建造者”模式來完成購物車的促銷信息渲染、價格計算的。
其中要建造的“產品”是CartView,包含一個List和一個price 對象(即列表和總價)
建造過程是一條流水線:
1、首先由SkuRenderer(Sku構建器)構建出全新的一個CartList
這個CartList是由緩存中OriginSku的skulist做為物料生成出來的
2、接下來由促銷規則渲染器(PromotionRuleRenderer)構建出促銷規則(Promotion)
此時的物料是用戶選擇的Promotiont生成出來的,具體的制造過程參見《促銷規則的構建》
3、流水線中下一個制造環節是生產Price
此時的物料是上一步生產的Rule,按照一定的規則算法對價格進行計算:
具體的制造過程參見《價格的計算過程》
4、流水線是由CartBuilder來總體控制的,最終由他來組裝成品:CartView
調用時序如下:
促銷規則的構建
根據需求,促銷規則主要有以下幾種:
組合促銷:滿減
單品促銷:第二件半價、單品立減、團購,秒殺等
優惠卷
其中組合促銷是應用在整個購物車中的,
單品促銷是應用在Sku上的,
優惠卷只有在結算頁才能使用和計算,而且不計算在返現金額中。
綜上所述,我們分別針對如上的種類,定義了:
SkuPromotionRuleBuilder(Sku促銷規則構建器)
CartPromotionRuleBuilder(Cart促銷規則構建器)
CartCouponRuleBuilder(優惠卷促銷規則構建器)
用關系:
先調用CartPromotionManager 獲取已經選中的促銷
再分別調用各種構建器構建出Rule,
從流水線的控制上,優惠卷的構建是要被跳過的(因為購物車是不處理優惠卷的)
將Rule分別放在Cart和Sku中的Rule中
SKU規則構建器
根據目前的單品促銷類型,實現了5個具體的構建器:
SeckillPluginNew 秒殺
GroupBuyGoodsPluginNew 團購
MinusPluginNew 單品立減
HalfPricePluginNew 第二件半件
ExchangePluginNew 積分兌換
具體調用哪個構建器完build rule ,則由實現者的
getPromotionType(): PromotionTypeEnum
方法來決定
Cart規則構建器
這是應用在購物車上的規則構建器,目前只有一個滿減的實現
優惠卷規則構建器
目前只有一個默認實現
結算頁購物車的顯示和價格計算
根據需求,在結算頁要計算運費和優惠卷,因此在流水線上要控制其制造流程:
在促銷規則的構建過程中加入了優惠卷的構建
在計算價格之前加入了運費的計算
在最后加入了優惠卷的渲染CartVo中的CouponLIst
購物車構建器的總體類圖
那么最終購物車構建器總體類圖如下:
促銷規則和價格計算
促銷規則
從上面的架構可以看出,促銷規則的定義非常重要,可以參見《PromotionRule》,即:
在這里我們定義了:
reducedTotalPrice是總體減的金額
reducedPrice:是單品減的金額
useCoupon:是要使用的優惠卷
invalid: 定義了是否失效了,比如加入購物車時活動還有效,但過了一會正好失效了。
invalidReason:
不光定義了失效的原因,還有一些特殊情況:比如加入購物車是商品活動售空數是5,買了5個,過了一會別人下單成功了,售后數是3個了,此時在這里要提示用戶,但不失效,用戶可以勾選改為3個繼續下單
價格計算
價格計算統一面向規則,而不管規則的構建過程,從而實現了算法和計算的分離。
這是在CartPriceCalculator中來完成的,實現過程就比較簡單了: