vivo商城计价中心 - 从容应对复杂场景价格计算
一、背景
随着vivo商城的业务架构不断升级,整个商城较为复杂多变的营销玩法被拆分到独立的促销系统中。
拆分后的促销系统初期只是负责了营销活动玩法的维护,促销中最为重要的计价业务仍然遗留在商城主站业务中,且由于历史建设问题,商城核心交易链路中商详页、购物车、下单这三块关于计价逻辑是分开独立维护的,没有统一,显然随着促销优惠的增加或者玩法的变动,商城侧业务重复开发量会显著加大。
促销系统的独立,计价相关业务能力从业务边界上也应由促销系统提供,因此促销侧需要从头开始设计促销计价相关能力。
二、原有计价业务
2.1 计价业务场景
商城原有涉及到计价业务的主要是商详页、购物车、确认下单、提交订单这几个业务场景。
如果将每一个影响最终售卖价的优惠叫做计价因子的话,那前述几种场景下对于售卖价有影响的计价因子归为三大类:
优惠活动(单品优惠、订单优惠)
优惠券(优惠券、代金券)
虚拟抵扣(积分、换新鼓励金)
对于每种计价场景与计价因子有如下关系:
2.2 原有计价模型
对于具体执行的计价业务中各计价因子间是有一定的先后优先级关系的,综合如下图所示,也在一定程度说明了原有计价业务模型:
三、促销计价模型
3.1 分层模型
促销系统从零搭建基础计价能力,对于系统的稳定性及扩展性必须有一定的保障,而这也就对于促销系统的计价模型提出了一定的要求,通用的基础计价模型最好是能有过一定的实践经历验证过的,因此我们采用了传统电商久经考验的计价模型:分层计价。
所谓的分层计价即传统电商中优惠涉及的三个层面:商品级、店铺级、平台级,正常情况下不同级别的优惠默认是可以叠加的,同一级别的优惠默认情况下是互斥的。
这里需要说明的是,每一层级的优惠计算的时候,对于有些优惠的门槛条件是否满足需要依赖原价,默认情况下依赖于上一个层级的优惠计算后的价格,即商品级优惠计算依赖商品原价,店铺级优惠依赖于商品级优惠计算后的价格,平台级优惠依赖于店铺级优惠计算后的价格。
叠加规则特别说明:
正常优惠叠加是指两个优惠可以同时享受,对于不同层级的优惠默认就是叠加的,对于同一层级的优惠默认是不叠加的,比如正常情况下,优惠券下的各种类型券是只能用一张的。
但某些场景下,业务上会指定同一层级的优惠可以叠加使用的,同时指定叠加使用的场景下还会分为普通叠加和并行叠加,举个例子:订单优惠和优惠券这两个类型的叠加就属于普通叠加(优惠券门槛是否满足的判断取决于订单优惠后的价格),优惠券和代金券的叠加属于并行叠加(优惠券和代金券的门槛是否满足的判断都取决于这两者的前序优惠后的价格)。
对于同一层级的优惠按不同维度分为:必选/勾选、可叠加(并行叠加/普通叠加)/不可叠加 。
3.2 新的计价模型
3.3 核心计价流程
3.3.1 主流程
通过前述计价模型可以得知,在计算优惠价时的先后顺序是:商品级(CalcItem)、店铺级(CalcShop)、平台级(CalcGroup),另外根据一些特殊业务场景,增加了可能的中断业务逻辑(CalcInterrupt),因此可得到下图所示的最粗粒度的计价流程;
那这三个级别的计算优惠价内部又是如何实现的呢?经过业务抽象,这三个级别的计算可以变成一个通用的计算优惠逻辑,仅有优惠级别的区分。
3.3.2 通用流程
经过业务抽象发现三个级别的优惠计算的通用逻辑:
获取当前层级的优惠查询器(Get Current Level PromotionGetter)
过滤优惠查询器(Filter PromotionGetter)
查询优惠(Get Promotion)
过滤优惠(Filter Promotion)
通过计价引擎计算优惠(Calc Engine)
过滤计价结果(Filter CalcResult)
因此我们得出如下的通用的计价流程:
通用计价流程中的又有几个相对灵活的与业务相关过滤逻辑,从后面的细节流程中可以了解更多的实现。
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商城促销系统建设计价中心的关键思路阐述完了。建设完计价中心后,整个促销系统的核心基础才立住,但这也只是个开始,整个商城围绕着促销计价中心仍然还有其他待建设的内容,比如整个商城的营销价格能力矩阵,价格监控,商城时光机等等,而这些内容我们后续有机会也会陆续输出相关文章,与大家一起交流学习。
作者:vivo互联网服务器团队-Wei Fuping
vivo商城计价中心 - 从容应对复杂场景价格计算的更多相关文章
- vivo商城促销系统架构设计与实践-概览篇
一.前言 随着商城业务渠道不断扩展,促销玩法不断增多,原商城v2.0架构已经无法满足不断增加的活动玩法,需要进行促销系统的独立建设,与商城解耦,提供纯粹的商城营销活动玩法支撑能力. 我们将分系列来介绍 ...
- c#pc上测试微信端企业公众商城个人中心链接的工具JMeter
工具 Apache JMeter 抓包工具,支持回放功能 安装JMeter http://jmeter.apache.org/download_jmeter.cgi 下载页 下边是下载链接 h ...
- vivo全球商城-营销价格监控方案的探索
一.背景 现在日常官网商城的运营中有一定概率出现以下两个问题: 1)优惠信息未对齐 官网商城促销优惠的类型越来越多,能影响最终用户实付价的优惠就有抢购.满减.优惠券.代金券等.实际业务操作中存在不同促 ...
- vivo全球商城时光机 - 大型促销活动保障利器
一.背景 官网商城在双11.双12等大促期间运营同学会精心设计许多给到用户福利的促销活动,当促销活动花样越来越多后就会涉及到很多的运营配置工作(如指定活动有效期,指定活动启停状态,指定活动参与商品等等 ...
- vivo 全球商城:优惠券系统架构设计与实践
一.业务背景 优惠券是电商常见的营销手段,具有灵活的特点,既可以作为促销活动的载体,也是重要的引流入口.优惠券系统是vivo商城营销模块中一个重要组成部分,早在15年vivo商城还是单体应用时,优惠券 ...
- vivo 全球商城:商品系统架构设计与实践
一.前言 随着用户量级的快速增长,vivo官方商城v1.0的单体架构逐渐暴露出弊端:模块愈发臃肿.开发效率低下.性能出现瓶颈.系统维护困难. 从2017年开始启动的v2.0架构升级,基于业务模块进行垂 ...
- Django商城项目笔记No.16用户部分-用户中心收货地址
首先完成省市区三级联动 新建areas应用 python ../../manage.py startapp areas 模型类代码 class Area(models.Model): "&q ...
- Django商城项目笔记No.13用户部分-用户中心个人信息
首先处理个人信息的显示 邮箱绑定: 首先给用户的模型类里添加一个字段来说明用户的邮箱是否激活 然后数据库迁移 python manage.py makemigrations python manage ...
- vivo 评论中台的流量及数据隔离实践
一.背景 vivo评论中台通过提供评论发表.点赞.举报.自定义评论排序等通用能力,帮助前台业务快速搭建评论功能并提供评论运营能力,避免了前台业务的重复建设和数据孤岛问题.目前已有vivo短视频.viv ...
随机推荐
- PHP Kafka 消息队列使用
转载自:https://learnku.com/articles/44442 1. 安装 Kafka 服务# 直接到 kafka 官网 , 下载最新的 wget https://mirror.bi ...
- redis集群环境配置
为什么需要集群 redis是一个开源的 key->value 高速存储系统,但是由于redis单线程运行,在系统中,只能利用单核的性能 当redis的调用越来越频繁时,可能会出现redis过于繁 ...
- cobbler安装4个报错汇总及解决方法:ImportError
安装完,检查cobbler:cobbler check发现这个问题ImportError: No module named yaml 具体报错如下: [root@www localdisk]# cob ...
- KeepAlive详解(转)
一.什么是KeepAlive? 首先,我们要明确我们谈的是TCP的 KeepAlive 还是HTTP的 Keep-Alive.TCP的KeepAlive和HTTP的Keep-Alive是完全不同的概念 ...
- C语言:冒泡排序例子
//冒泡排序 //14个数字排序:14个数的组合:14*13/2=91次 理论上比较91次 ,实际只有39次进行了变量交换 #include <stdio.h> void bubble_s ...
- C语言:toascii()函数
/* 头文件:#include <ctype.h> 定义函数:int toascii(int c); 函数说明:toascii()会将参数c 转换成7 位的unsigned char 值, ...
- Spark编程基础_RDD编程
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变.可分区.里面的元素可并行计算的集合.RDD具有数据流模型的特 ...
- Day6 三种结构 顺序选择循环!
顺序结构 从上到下依次执行,它是任何算法都离不开的一种基本算法结构. package com.xiaoming.struct;public class ShunXuDemo { public ...
- MYSQL时间戳和日期相互转换 笔记整理
相关函数: date_format(date, format) 函数,MySQL日期格式化函数date_format() unix_timestamp() 函数 str_to_date(str, fo ...
- Python自动化测试面试题-经验篇
目录 Python自动化测试面试题-经验篇 Python自动化测试面试题-用例设计篇 Python自动化测试面试题-Linux篇 Python自动化测试面试题-MySQL篇 Python自动化测试面试 ...