版权声明:本文为博主原创文章,未经博主允许不得转载。关注公众号 技术汇(ID: jishuhui_2015) 可联系到作者。
优惠券系统的核心在于各种券种的管理,发放和使用。
通常的设计角度是从终端用户出发,所谓“所见即所得”,终端用户所见到的形形色色的优惠券,正是开发整个系统的挑战所在。
可以想象,为了配合不同形式的线上、线下活动,优惠券系统势必有较大的改动,如何最大限度的降低改动的成本,成为了最核心的挑战。
就上述问题而言,解决的方法就是:规则与执行相隔离。
规则层,即是各类优惠券的使用限制,以及能达到的效果。
执行层,可以理解为根据规则计算最终成交价格。
对于规则层,笔者并不想将其上升到规则引擎的高度,只做适当的抽象。况且市面上的规则引擎开发框架未必能满足优惠券这一复杂的应用场景。
值得注意的是,优惠券不等同于任何形式的优惠促销活动,但却是整个营销系统的一部分。相较于优惠券,促销活动系统的实现还要更困难。本文旨在阐述怎么开发一个优惠券系统,促销活动的设计与实现不在本文的范畴了。
一、怎么生成优惠券
在生成优惠券之前,我们先来了解一下『优惠券模板』的概念。
顾名思义,按照模板去生成优惠券。
就好比工业生产流水线上的模具,只需注入原材料进行加工,即可制成成品。
因此,模板上携带了大量优惠券相关的信息,包括但不限于名称、时效性、各类优惠规则,以及优惠券面额等,模板本身也有标题,状态,创建人等基本信息。
当需要生成优惠券的时候,指定是哪套模板,然后填写准备要生成的券总数即可,亦可在生成券的环节,指定接收对象,将券的派发操作一并完成。
可能读者不禁会发问了:这样设计似乎有些多余,券模板和券实体重合的属性很多,何不直接跳过券模板的环节,直接生成优惠券呢?
这样做的原因有两个:
其一,不想重复输入相同的优惠券信息,只要输入一遍模板信息,即可实现批量生成券,能提高运营人员的工作效率;
其二,创建券模板和发送券实体是有两类不同权限的人完成的。可能更高权限的运营人员能掌握着创建模板的权限,普通的运营人员则只需要按模板发优惠券即可。
如下图,可反映出券模板和券实体的关系:
二、优惠规则的设计
上图涉及的内容基本上都能一目了然,接下来我们将重点放在优惠规则的设计上。
优惠规则无疑是优惠券系统的核心部分,能不能最大限度的适应市场运营需求,就看优惠规则实现得怎么样了。
显然,用穷尽法去实现市面上的优惠规则是不明智。
正所谓“世上唯一不变的就是变”,我们必须提炼出优惠规则的本质,才能“以不变应万变”。
笔者对市面上常见的优惠规则进行了调研,并结合自身业务需求,可以将优惠规则大致分成两大类:
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实例。
五、总结
本文从业务设计,到程序设计,对优惠券系统做了一个比较全面清晰的阐述,如果读者正好也需要研发一个优惠券系统,这篇文章将会是很好的参考。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。