【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
一般来说,只要是面向零售,不管是B2C,C2C还是B2B2C模式,商城的促销模式都是差不多的,比如满就送,满x减x,满x赠x,满x折扣x,组合销售,优惠券等等。所有促销活动都有明确的起止周期,超期后活动自动关闭。
个人理解电商行业的促销行为有如下几个特点
手段灵活多变,而且多种多样。
促销的主体是运营商和商家。
经常根据营销策略动态调整
促销必须定义准确,不能存在歧义
促销可以组合应用,甚至多种促销手段存在依存关系或互斥关系
因此,最好用一种灵活可变的方式来设计促销机制。
我考虑的是用表达式引擎实现,非常灵活,最重要是很简单。
首先设计之初要理清促销的两大要素,一个是条件,另一个是资源,条件是促销规则成立的基础,资源是促销规则所要分配的内容,比如红包,金额,包邮之类。
条件主要包括
地域,组织,会员生日月份,会员生日,会员积分,会员等级,下单时间,整点,金额,件数,指定商品,下单日期/时间,定时(用做商品秒杀),限定数量,限定金额,商品组合等
资源主要包括
赠送现金
扣减费用
免单
免邮费
成为会员
送积分xx
送红包
送商品
表达式中可以插入java对象/方法,如某店搞活动,活动规则为
1.满100返10块,最高上限200元
2.晚上10点之后半价
两个规则不能同时成立
规则计算的返回值一定是一组结果(比如金额,红包,优惠券,减免费用额度等)。同时多个规则之间也存在着互斥的因素,比如以上的两个规则就存在互斥。
促销规则的思路基本上是检查条件并生成结果,如果有多个规则,按照定义先后顺次执行,最终运算所得结果做为最终促销规则应用之后的费用计算和买家返利的依据。
目前考虑把整个规则分解成两类对象
条件:一旦条件达成就执行规则,比如整点秒杀,超过200元等。
资源:资源类似结果,比如减免10元钱,包邮等。
规则体系需要具有如下特点
易于维护:以类似Excel公式的形式编写的一组公式,确保让一些不会编程但熟悉excel公式的人可以马上上手使用,即使不会excel公式也不要紧,因为非常简单。
规则可以累加:可以存在多条规则,规则会按定义顺序依次执行,如果符合规则1又符合规则2,那么就同时享有两个规则所定义的资源。
规则可以互斥:可以设定规则的互斥特性,一个规则生效后,另一个规则就无法执行,确保不能同时享有多个规则所定义的资源。
通过以上规则体系可以较好的解决目前商城中促销行为需要硬编码或者以数据库方式定义规则又不够灵活的问题。
下面举例说明,假设某店庆活动搞了一次促销,从本月1日到15日有效
满100赠10块,并且上限500封顶,会员享受半价并且会员不享受满赠
要实现这个促销规则就要先定义两个促销规则,预设规则如下
预设规则定义 | 说明 |
public booelan f_满赠(boolean condiation, Resource res1, Resource res2); | 满xxx元就送xxxx,送的东西可以是钱/积分/商品/运费/红包等 |
public f_会员优惠(boolean condiation, Resource res1, Resource res2); | 针对会员优惠的规则 |
另外还要设置一些内置函数,实现获取数据,变更金额和资源等功能,如下所示
预设函数定义 | 说明 |
public void f_满赠(boolean condiation, Resource res1, Resource res2) throws Exception; |
满xxx元就送xxxx,送的东西可以是钱/积分/商品/运费/红包等。 |
public Resoure 当前结算金额() | 当前订单的结算金额,这个结算金额会根据规则的执行而变化,比如应付款是100,规则1是满100减1元,执行了规则1之后,当前结算金额就变成了90 |
public Resource 结算总金额(); | 订单的结算总金额,这个金额不会随着规则的执行而变化 |
public Resource 减价(double money); | 对结算金额进行扣减,扣减后当前结算金额会有变化 |
public Resource 无优惠(); | 不会对当前结算金额有影响,也不会对当前的任何资源有影响 |
public void f_会员优惠(boolean condiation, Resource res1, Resource res2) throws Exception; | 针对会员优惠的规则 |
public boolean 是否会员(User current_user) | 判断是否是会员,传入的参数是当前用户 |
public User 当前用户(); | 返回订单指向的购买者 |
public boolean 上一规则条件() | 返回上一条规则是否执行成功,如果当前正在执行的就是第一条规则,那么就返回true。这个函数主要是用来处理和判断规则互斥用的 |
public List<Resource> 资源集合(); | 执行完成之后的资源列表,其中的资源主要是指非现金的资源,比如红包,积分,礼品等.此方法是只读的。 |
下面是最终的规则表达式
f_满赠(金额()>=100 and 金额()<500,减价(金额()/100*10),无优惠());
f_会员优惠((not 上一规则条件()) and 是否会员(当前用户()), 减价(金额()/2),无优惠())
从上表中可以看出分别定义了两个规则(用分号隔开),当f_满赠成立后,f_会员优惠就不执行了,两个规则是互斥的。
至于规则的生效时间,可以加入到表达式里面,也可以在规则执行前后检查,个人认为时间条件是通用需求,所有表达式规则都有时间条件,完全可以在规则执行之前检查,而且这样做性能也略好一点。
这组规则表达式执行完成之后,当前结算金额():也就是买方的应付金额。 资源集合():就是订单的赠品
这个思路应该问题不大,难点在于找个什么表达式引擎能够进行表达式计算,并且支持绑定java实例和方法,但又要阻止在脚本里调用其它的java类,只能调用自己预置的函数。
表达式引擎可以使用jdk自带javascript引擎,不过它的缺点是可以调用java类,只是不太好屏蔽掉对java类的反射,无法阻止在表达式里面创建java类和对象,总觉得不安全也不放心,万一有人利用这一点,用你的表达式脚本搞点破坏就麻烦了。
也可以使用其它的脚本引擎比如beanshell,这东西是开源的,可以修改代码禁用掉对java类的反射。
当然还有别的一堆,可以从github上找找。
下面给出一部分使用ScriptEngine的代码,此段代码仅仅用来阐明思路,因为不能有效阻止js里反射调用java类,因此并不适合做表达式引擎。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class Demo1 {
public static abstract class Resource{
public abstract String getType();
}
public static class MoneyResource extends Resource{
private double money;
public MoneyResource(double money){
this.money = money;
}
public double getMoney(){
return money;
}
public String getType(){
return "money";
}
}
public static class Rules{
private Functions func;
private List<Resource> resources = new ArrayList<Resource>(200);
private double curMoney;//当前结算金额,这个值会随着规则的应用而变化
private double totalMoney;//结算总金额
public double getCurMoney() {
return curMoney;
}
public void setCurMoney(double curMoney) {
this.curMoney = curMoney;
}
public double getTotalMoney() {
return totalMoney;
}
public void setTotalMoney(double totalMoney) {
this.totalMoney = totalMoney;
}
public List<Resource> getResources(){
return Collections.unmodifiableList(resources);
}
public void setFunc(Functions func){
this.func = func;
}
public void f_满赠(boolean condiation, Resource res1, Resource res2) throws Exception{
//......
if (!condiation) return;
if ("money".equals(res1.getType())){
curMoney += ((MoneyResource)res1).getMoney();
}else{
resources.add(res1);
}
}
public void f_会员优惠(boolean condiation, Resource res1, Resource res2) throws Exception{
//功能和f_满赠类似就不写了
}
}
public static class Functions{
private Rules rules;
public Functions(Rules rules){
this.rules = rules;
}
public Resource 当前结算金额(){}
public Resource 结算总金额(){}
public Resource 减价(double money){}
public Resource 无优惠(){}
public boolean 是否会员(User current_user){}
public User 当前用户(){}
public boolean 上一规则条件(){}
public List<Resource> 资源集合(){}
}
public static void main(String[] args) throws ScriptException {
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("js");
Rules rule = new Rules();
Functions func = new Functions(rule);
rule.setCurMoney(200);
rule.setTotalMoney(200);
engine.put("r", rule);
engine.put("func", func);
engine.eval("r.f_满赠(func.金额()>=100 && func.金额()<500,func.减价(func.金额()/100*10),func.无优惠()); "
+"r.f_会员优惠((not func.上一规则条件()) && func.是会员(func.当前用户()), func.减价(func.金额()/2),func.无优惠())");
System.out.println(rule.getCurMoney());
System.out.println(rule.getResources());
}
上面这段代码只是为了说明过程代码并不能直接运行,而且表达式和先前定义略有出入(主要是为了适应js引擎做的调整,思路并没有什么变化)。
由于编写规则表达式的时候相对容易出错,特别是经验不足的人更是如此,因此建议添加表达式校验,代入数据试算功能来验证公式是否正确。
来源:oschina
链接:https://my.oschina.net/u/1996799/blog/300556