可自由扩展的促销活动架构设计

需求和定义

  设计一个灵活的促销架构,可以容纳当前可预见到的所有促销活动类型。活动的配置方式要足够自由,允许运营管理员自由配置活动的参与人群、活动包含的商品范围、特别是不同活动的叠加关系。
  配置时,一个商品可以同时在同一时间参加多个活动。而在结算时互斥的多个活动中只能选择其中一个参加,这个选择可以是系统自动的,也可以是用户自己选择的。不互斥的活动可以则同时参加。
  这里的促销活动是指用户购买商品时会改变订单的实付价格或者订单商品数量的促销类型,例如限时降价、满100减10、买1赠1、会员打折等。不在下单过程中起作用的活动不包括在这个范围里,例如新用户注册送券、下单后赠券、下单后返现等。

逻辑架构

  这个架构设计最关键的问题是要解决不同促销活动叠加使用的问题,而且能否叠加是可以由运营在配置的时候来指定的,而不是在代码里固定不变。例如运营设置了一个限时价活动为原价的2折,已经突破了成本价,这时候运营可能不希望用户再使用其它的促销活动;而另一个限时价活动只是原价的9折,这时候运营可能就允许用户叠加使用其它活动。
  活动叠加存在先后关系,后面的促销结果基于前面的促销结果进行计算。
  根据这个以上需求,设计一个分层的促销活动架构,处在同一层的促销表示互斥的、不能同时参加的活动类型,不同一层的活动则可以叠加使用。一个常见的分层方式是这样的:

层级 促销方式 举例
1级 直接修改价格 直降价、秒杀、拼团、预订
2级 范围促销 指定商品买1送1;指定品牌买满100减10;全场商品满100减10;
3级 加价换购 买满100后加10元换购xx;
4级 优惠券 满100减10券;满3件8折券;
5级 会员折上折 在实付金额上直接打95折;
6级 包邮 单品包邮;满99包邮;大促全场包邮;
7级 运费券
8级 积分抵扣 100积分抵1元

  分层的方式和层次之间的优先级主要依据以下2点:
  1.逻辑上是否允许一个商品能否同时参加两个活动,例如用户购买一个商品时,显然不能同时参加秒杀活动又同时参加拼团活动。
  2.从运营角度是否允许一个商品同时参加两个活动,例如有些平台会把范围促销分成类目促销和全场促销两种,并且允许用户同时参加这两种活动,这时候就要把范围促销拆分成两层。
  层次之间能否叠加由前面的活动来指定,后面的活动不能指定能否叠加前面层级的促销,否则将需要不断的逆向回滚。即,在配置1级活动的时候可以指定能否叠加后面的2到8级的促销,而在配置7级活动时只能指定能否叠加8级促销。

促销计算过程

  计算过程参考管道设计模式(pipeline),把每一个促销层级定义为一个主策略。同一层内有多种促销活动的,把每一种活动定义为一个子策略,在主策略里主要实现如果选择子策略的逻辑。
  另外,不同位置和不同渠道可以使用的促销类型可能会有所不同,例如购物车一般不需要计算运费这一级以后促销、微信渠道不能使用会员折上折等。这种情况只要把主策略组合成不同的组合,计算时按需求指定要使用的策略组合就可以了。
  也就是说,从上到下依次有三个层次的策略:策略组合、主策略、子策略。这三种策略的输入参数和输出结果完全一样。因此定义一个促销计算结果对象,作为所有策略的输入参数和输出结果。


促销活动-数据流图

   计算时,把用户购物车里的商品组装成促销计算结果结构,依次通过全部策略,每个策略都会修改促销计算结果里的价格,以及在商品信息上附加一个促销计算结果。下面这个例子,用户购买了goodsId=1的商品2件,商品原价是10元,这时候还没有经过促销计算,实付价等于原价。注意promotion的isUserSelect字段,表示用户指定要参加id=10的这个秒杀活动。

{
  "goods":[{
    "goodsId":1,
    "buyCount":2,
    "originalPrice":10,
    "payPrice":10,
    "promotions":[{
      "type":"flashSale",
      "id":10,
      "isUserSelect":true
    }]
  }],
  "originalPrice":20,
  "payPrice":20,
  "promotions":[]
}

  经过全部促销策略计算之后得到类似这样的一个结构。这个商品一共应用了两个促销活动,实付价变成5元,其中参加秒杀活动减了4元,优惠券减了1元。
   levelStacking字段表示这个促销可以跟哪些层级的促销叠加下,例如新客秒杀这个活动的levelStacking=[4,5],表示这个活动可以叠加4级和5级促销,但是不能同时参加其它层级的活动。
   priceAdjustment字段表示这个商品在这个促销活动上减了多少钱。

{
  "goods":[{
    "goodsId":1,
    "goodsName":"示例商品",
    "buyCount":2,
    "originalPrice":10,
    "payPrice":5,
    "promotions":[{
      "type":"flashSale",
      "id":10,
      "name":"新客秒杀",
      "description":"仅限新客参加",
      "isUserSelect":true,
      "levelStacking":[4,5],
      "priceAdjustment":4,
      "extraInfo":[]
     },{
      "type":"discountCoupon",
      "id":11,
      "name":"新客无门槛减1券",
      "description":"仅限新客参加",
      "isUserSelect":false,
      "levelStacking":[5],
      "priceAdjustment":1,
      "extraInfo":[]
    }]
  ],
  "originalPrice":20,
  "payPrice":10,
  "promotions":[{
      "type":"flashSale",
      "id":10,
      "name":"新客秒杀",
      "priceAdjustment":8,
      "joinedGoodsId":[1],
      "extraInfo":[]
     },{
      "type":"discountCoupon",
      "id":11,
      "levelStacking":[5],
      "priceAdjustment":2,
      "joinedGoodsId":[1],
      "extraInfo":[]
    }]
}

  计算结果还要获取到商品和活动的其它详情信息,把这个结果返回给购物车和订单后,由调用方根据各自的需求解释成所需的结构。
  由于策略的输入输出都是一样的,所以理论上所有策略是可以任意组合的。也就是说可以随意往架构里增加新的促销方式或减少促销方式。例如想是增加一个商品兑换券的促销方式,只需要在3级和4级促销之间增加一个主策略,再在这个主策略里增加兑换券的子策略就可以了。

数据库设计

  首先把优惠券跟其它活动分开来,因为优惠券是属于用户资产类型,用户要先拥有了券才能参加活动。然后会员折上折和会员积分是由用户属性决定的,不需要在促销里另外保存。
  除此之外的其它促销都满足“在某个时间内购买了某个商品,就可以优惠多少元或者获得赠品”这样一个结构,这些活动都可以放到一个表里保存。


促销活动数据库设计

  把促销活动的公有信息放到主表里,各种不同活动的特有信息放到各自的主表里,子表的id就使用主表的id。如果使用Doctrine作为ORM可以很方便地实现这种架构。
  优惠券的数据表也使用相同的结构,在主表里保存券的通用配置,在子表里保存满减券、满折券、兑换券等不同券的信息。

其它问题

  活动的一般格式是买满多少元/多少件,可以减少多少元/打多少折/获得什么赠品,可以把这些抽象成活动条件和活动结果两类策略,在各种不同的促销里都可以调用。
  不同活动叠加时,后面的活动可以按照原价来计算优惠,也可以按照实付价来计算,也可以从某一层级开始用实付价。
  活动的可参与人群可以按用户标签以及标签的交并补运算进行配置。参与活动的商品范围一般按品牌、品类、商品id进行配置,或者这些属性的交并补运算。这两个配置的集合运算的实现方式将在后面的文章详细说明。
  以上只是促销活动的大致架构,在实际开发中由于各种促销之间有很大的差异,这个架构还要做很多兼容处理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容