優惠券系統的核心在於各種券種的管理,發放和使用。
通常的設計角度是從終端用戶出發,所謂「所見即所得」,終端用戶所見到的形形色色的優惠券,正是開發整個系統的挑戰所在。
可以想像,為了配合不同形式的線上、線下活動,優惠券系統勢必有較大的改動,如何最大限度的降低改動的成本,成為了最核心的挑戰。
就上述問題而言,解決的方法就是:規則與執行相隔離。
規則層,即是各類優惠券的使用限制,以及能達到的效果。
執行層,可以理解為根據規則計算最終成交價格。
對於規則層,筆者並不想將其上升到規則引擎的高度,只做適當的抽象。況且市面上的規則引擎開發框架未必能滿足優惠券這一複雜的應用場景。
值得注意的是,優惠券不等同於任何形式的優惠促銷活動,但卻是整個營銷系統的一部分。相較於優惠券,促銷活動系統的實現還要更困難。本文旨在闡述怎麼開發一個優惠券系統,促銷活動的設計與實現不在本文的範疇了。
在生成優惠券之前,我們先來了解一下『優惠券模板』的概念。
顧名思義,按照模板去生成優惠券。
就好比工業生產流水線上的模具,只需注入原材料進行加工,即可製成成品。
因此,模板上攜帶了大量優惠券相關的信息,包括但不限於名稱、時效性、各類優惠規則,以及優惠券面額等,模板本身也有標題,狀態,創建人等基本信息。
當需要生成優惠券的時候,指定是哪套模板,然後填寫準備要生成的券總數即可,亦可在生成券的環節,指定接收對象,將券的派發操作一併完成。
可能讀者不禁會發問了:這樣設計似乎有些多餘,券模板和券實體重合的屬性很多,何不直接跳過券模板的環節,直接生成優惠券呢?
這樣做的原因有兩個:
其一,不想重複輸入相同的優惠券信息,只要輸入一遍模板信息,即可實現批量生成券,能提高運營人員的工作效率;
其二,創建券模板和發送券實體是有兩類不同許可權的人完成的。可能更高許可權的運營人員能掌握著創建模板的許可權,普通的運營人員則只需要按模板發優惠券即可。
如下圖,可反映出券模板和券實體的關係:
上圖涉及的內容基本上都能一目了然,接下來我們將重點放在優惠規則的設計上。
優惠規則無疑是優惠券系統的核心部分,能不能最大限度的適應市場運營需求,就看優惠規則實現得怎麼樣了。
顯然,用窮盡法去實現市面上的優惠規則是不明智。
正所謂「世上唯一不變的就是變」,我們必須提煉出優惠規則的本質,才能「以不變應萬變」。
筆者對市面上常見的優惠規則進行了調研,並結合自身業務需求,可以將優惠規則大致分成兩大類:
1、計算型規則。形如「無門檻直降20元」,「滿xx減xx」等,這些規則都暴露給終端用戶,是顯而易見的,讓用戶知曉這個優惠券是如何參與計算。
2、限制型規則。相較於計算型規則,限制型規則大多數情況下是隱性的。比如:限制某些用戶領取,限制某套券模板最多能發放多少金額的優惠券,限制優惠券的渠道等。這類規則可以很好的支撐日常的運營工作。
「萬物相生相剋」,眾多的優惠規則,也並不是都能共存的,這裡就引入了規則『互斥性』的概念。當一個優惠規則與另一個優惠規則不能同時存在於一套模板里的時候,我們就認為這兩個規則是互斥的,這在設計規則的時候也需要有所考慮。
其次,優惠規則也會講究先後順序,所以必然就帶來了一個優惠規則的『優先順序』屬性,我們約定,數字越小,表示越優先,也就是按從小到大的順序。
以下給出一張完整的規則表,信息量較大,筆者將做必要的解釋。
規則表中,渠道限制、對象限制、金額限制和數量限制四者皆屬於限制型規則,優先順序排在了較前面。同時,也給出了參數說明,規則描述,相對於模板的關係以及互斥規則,這些都是一目了然的。
接下來的扣減規則和封頂規則同屬於計算型規則,也算是優惠券的重中之重了。
筆者著重解釋一下滿減規則中的「階梯滿減」。我們平常會看到有這類說法:每滿100元減10元,言下之意便是:滿100元減10元,滿200減20,以此類推,筆者將其稱之為「階梯滿減」規則。
規則表中有涉及好多數字,筆者設計了一套生成規則。
規則編號是int型,Java 編程語言中,int全長 32位,如下圖所示:
1、第一位是符號位,固定為0,且不允許出現 32位全是0的情況,即為正整數;
2、高8位是規則組別編號,理論上允許的數值範圍是0~255,但是實際的業務規則是假設最多有15組優惠規則,每組優惠規則編號取10的倍數,範圍即為 10~150;
3、第10位和11位作為備用,暫無實際用處,固定為00;
4、中間15位,存放規則組下的細則編號,允許的範圍0~32767,但是實際業務規則是要達到兩兩互斥的目的,取值如下(以四位二進位為例):
0001
0010
0100
1000
結論:排除全為0的情況,那麼有N位,就有N組兩兩互斥。如果組內組外互斥都考慮,那麼可取值就更少了;
5、末6位存放規則組的優先順序,允許的值範圍是0~63,實際取值從1開始,考慮到之後會插入其他的規則組,會在每兩個規則組別直接預留兩個級別,初始的優先順序設置為1,4,7,10,13,16…;
6、按照上述規則,根據既定的組別和優先順序可以生成上表中的細則編號。
渠道限制 83888577
0 00001010 00 000000000100111 000001
用戶類型限制 167775300
0 00010100 00 000000000110001 000100
指定用戶限制 167776900
0 00010100 00 000000001001010 000100
總金額限制 251660487
0 00011110 00 000000000100011 000111
單位金額限制 251662535
0 00011110 00 000000001000011 000111
總量限制 335545290
0 00101000 00 000000000001111 001010
個人所獲限制 335544778
0 00101000 00 000000000000111 001010
無門檻直減 419430989
0 00110010 00 000000000001001 001101
滿減 419431565
0 00110010 00 000000000010010 001101
打折 419436813
0 00110010 00 000000001100100 001101
封頂規則 503323024
0 00111100 00 000000001100110 010000
在做程序設計之前,我們必須把握住關於優惠券的三個主要動作:
1、管理。指的是對券模板的創建,規則設置,關閉等操作。當然,在給模板設置規則參數的時候,需要校驗規則參數的合理性。
2、派發/領取。筆者將這兩個操作進行了合併,兩者的區別無非就是有沒有綁定終端用戶,在介面層面是可以合併的。通常,限制型規則會在此發揮作用,當觸發了這兩個操作的其中一個,在生成優惠券之前都將會先用限制型規則進行校驗。
3、使用。就是將優惠券花出去,計算型規則會在此發揮作用,主要是判斷滿不滿足券的使用條件,計算減免金額等。
綜上,程序設計就靠這三個動作進行延展。
我們先來設計一下規則層。
根據規則表中定義的一系列規則,反映在程序中可以有兩種形式:
一種是使用配置文件(通常是XML),然後程序去解析;
另一種則是直接使用枚舉類(或者其他形式的類)。
第一種開發難度稍大,想做得比較強大的話,是需要花費不少功夫的。
對於優惠規則的定義和實現都是事先內置在程序中的,並不是非專業人員改變一下配置文件就能達到效果的。
所以基於此考慮,使用了第二種實現方式。如下所示即為規則定義:
Rulers含有五種重要的屬性:規則編號(ruleNo),規則名稱(ruleName),排序(sort),對應的Model Class對象,以及實現特定介面的serviceName。
接下來的三個方法也較為重要:通過規則編號直接找到對應的枚舉,判斷是否是計算型規則,規則是否有多參數,和券模板是1~*的關係就表示是多參數。
當然,也少不了持久化到資料庫的各類Beans,每個Bean都會繼承一個叫作SuperRule的抽象類,子類的屬性都是規則表中提及到的。Rulers中的Model Class就是來自於此。由於規則較多,下面只展示SuperRule:
我們再來看看Service的定義:
最頂層的BaseRuleService是規則基本的CRUD操作,使用了泛型R,繼承自SuperRule。BaseRuleService有一個抽象實現,除了泛型R,還有一個Mapper(Mybatis)。
因為將規則分成了兩大類,自然而然的就能派生出兩個Service,分別對應計算型規則和限制型規則。
最後一個是對規則參數的校驗,使用了泛型R,繼承自SuperRule。
至此,介面的設計就囊括了上述所說的三種優惠券相關的操作,也就意味著,如果出現了一個新的規則,至多實現上述的三個Service(因為可能有既是限制型規則,又是計算型規則),不過大多數情況只要實現兩個Service,然後在Rulers配置好此新規則即可。至於規則基本的CRUD操作,只要繼承AbstractCouponRuleService,不需要額外花精力去實現。
這麼多規則,自然是需要一個『工廠類』進行調度的,比如生成一個規則Bean實例,生成一個Service實例。
本文從業務設計,到程序設計,對優惠券系統做了一個比較全面清晰的闡述,如果讀者正好也需要研發一個優惠券系統,這篇文章將會是很好的參考。