营销系统规则引擎设计

例子
某电商公司是多站点结构,目前已经开设了ABC三个子站点,这3个子站点的会员等级体系不同,
但产品模型和数据是完全一致的,产品平时在各个站点分别有不同的销售价格;现计划在全公司范围内进行618大促,
活动期间为6月17日零时-6月19日零时,针对不同用户的会员等级,对产品销售实行不同折扣优惠;
设计相关的系统对外提供商品实时价格获取功能;
A站点
超级VIP用户:9折优惠
VIP用户:7折优惠
普通用户:无优惠
B站点
金牌客户:8.5折优惠
银牌客户:7.5折优惠
铜牌客户:6.5折优惠
普通用户:无优惠
C站点
皇冠会员:8折优惠
普通用户:无优惠


分析

1.我们怎么来领域建模?

画个图如下


营销决策树.png

(1)初看可能会认为根据站点建立一个领域对象,根据用户等级建立一个领域对象,然后进行组合?
但细想,我们怎么能够穷举所有的具体规则和对象呢?,万一以后还需要根据新的标签比如用户性别,年龄,职业等去建立规则,我们岂不是要建立一堆的对象?
所以我们建模应该面向抽象而不是具体

(2)那这里的抽象又是指的是什么?
我们可以看到,该系统的核心诉求是能根据不同的规则,拿到对应的结果
进一步的细分诉求
(1)规则,结果需要可配置
(2)规则可随意扩展(可以随时增删改规则),随意组合(即可以调整顺序)
(3)规则可方便管理(规则的可视化以及方便的目录管理,我一眼就知道这个规则是干什么的)
(4)规则可以复用,(规则和规则之间要独立解耦)

(3)根据上面的需求我们来设计对应的数据结构和算法?
可以随意配置编排 ->数据结构我们自然想到链表,设计模式我们自然想到责任链模式
可复用 ->我们自然想到模板模式
方便管理可视化 ->我们自然想到决策树结构

(4)那决策树就是我们的一个核心领域对象,它应该包含哪些细分?
节点TreeNode
a.节点类型

nodeType:根节点,叶子节点,果实节点

b.节点对应的值

nodeValue

c.节点的id

treeNodeId

d.节点对应的处理器类型和描述

ruleKey,ruleDesc

e.节点对应的边

List<TreeNodeLink> treeNodeLinkList

f.节点所属的树id

treeId


节点对应的边TreeNodeLink
a.边的起始和终止节点id

fromNodeId,toNodeId

b.边上面的判断规则,只有满足判断规则,才能从起始走到终止

ruleLimitType,ruleLimitValue


树节点TreeNode

public class TreeNode {
    /*
     *所属的树id
     */
    private Long treeId;
    /*
     *节点类型 根节点 叶子节点 果实节点
     */
    private Integer nodeType;

    /*
     *节点id
     */
    private Long treeNodeId;
    /*
     * 节点所对应的值  一般只有果实节点才会有值
     */
    private String nodeValue;
    /*
     *节点所对应的处理器类型
     */
    private  String ruleKey;
    /*
     *节点所对应的处理器描述
     */
    private String ruleDesc;

    /*
     *节点所对应的边
     */
    private List<TreeNodeLink> treeNodeLinkList;
}

边TreeNodeLink

public class TreeNodeLink {
    /*
     * 对应的起始节点
     */
    private Long nodeIdFrom;
    /*
     * 对应的终止节点
     */
    private Long nodeIdTo;
    /*
     * 对应的规则判断类型  大于/小于/大于等于/小于等于/等于/不等于
     */
    private Integer ruleLimitType;
    /*
     * 对应的规则判断值
     */
    private String ruleLimitValue;
}

根节点TreeRoot

public class TreeRoot {
    /*
     *根节点Id
     */
    private Long treeRootNodeId;
    /*
     *根节点名称
     */
    private String treeName;
    /*
     *树Id
     */
    private Long treeId;
}

决策树TreeRich

public class TreeRich {
    /*
     * 决策树的根节点
     */
    private TreeRoot treeRoot;
    /*
     * 决策树的子节点
     */
    private Map<Long, TreeNode> treeNodeMap;
}

至此我们的核心对象设计完毕,下面可以来设计接口将他们编排起来


接口设计

image.png

树节点逻辑过滤器接口LogicFilter

后续新增规则,可以通过实现这个接口,比如UserAgeFilter针对用户年龄做一些处理,UserGenerFilter针对用户性别做一些处理

public interface LogicFilter {


    /**
     * 逻辑决策器
     *
     * @param matterValue          决策值
     * @param treeNodeLineInfoList 决策节点
     * @return 下一个节点Id
     */
    Long filter(String matterValue, List<TreeNodeLink> treeNodeLineInfoList);

    /**
     * 获取决策值
     *
     * @param decisionMatter 决策物料
     * @return 决策值
     */
    String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

}

决策抽象类提供基础服务

  • 在抽象方法中实现了接口方法,同时定义了基本的决策方法;1、2、3、4、5,等于、小于、大于、小于等于、大于等于的判断逻辑。
  • 同时定义了抽象方法,让每一个实现接口的类都必须按照规则提供决策值,这个决策值用于做逻辑比对。
public abstract class BaseLogic implements  LogicFilter{
    @Override
    public Long filter(String matterValue, List<TreeNodeLink> treeNodeLinkList) {
        for (TreeNodeLink nodeLine : treeNodeLinkList) {
            if (decisionLogic(matterValue, nodeLine)){
                return nodeLine.getNodeIdTo();
            }
        }
        return 0L;
    }

    @Override
    public abstract String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

    private boolean decisionLogic(String matterValue, TreeNodeLink nodeLink) {
        switch (nodeLink.getRuleLimitType()) {
            case 1:
                return matterValue.equals(nodeLink.getRuleLimitValue());
            case 2:
                return Double.parseDouble(matterValue) > Double.parseDouble(nodeLink.getRuleLimitValue());
            case 3:
                return Double.parseDouble(matterValue) < Double.parseDouble(nodeLink.getRuleLimitValue());
            case 4:
                return Double.parseDouble(matterValue) <= Double.parseDouble(nodeLink.getRuleLimitValue());
            case 5:
                return Double.parseDouble(matterValue) >= Double.parseDouble(nodeLink.getRuleLimitValue());
            default:
                return false;
        }
    }
}

树节点逻辑实现类

站点过滤器SourceTypeFilter

/*
 *站点类型过滤器
 */
public class SourceTypeFilter extends BaseLogic{
    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get(RuleType.USER_SOURCE.getType());
    }
}

用户类型过滤器

/*
 *用户类型过滤器
 */
public class UserTypeFilter extends BaseLogic{
    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get(RuleType.USER_LEVEL.getType());
    }
}

目前两个决策逻辑的节点获取值的方式都非常简单,只是获取用户的入参即可。实际的业务开发可以从数据库、RPC接口、缓存运算等各种方式获取。


决策引擎接口定义

IEngine
对于使用方来说也同样需要定义统一的接口操作,这样的好处非常方便后续拓展出不同类型的决策引擎,也就是可以建造不同的决策工厂。

public interface IEngine {
    /*
     * @param treeId 决策树id
     * @param userId 用户id
     * @param treeRich 整棵决策树
     * @param decisionMatter 决策值
     * @return  EngineResult
     */
    EngineResult process(final Long treeId, final String userId, TreeRich treeRich, final Map<String, String> decisionMatter);
}

决策节点配置

EngineConfig

public class EngineConfig {
    static Map<String, LogicFilter> logicFilterMap;

    static {
        logicFilterMap = new ConcurrentHashMap<>();
        logicFilterMap.put(RuleType.USER_SOURCE.getType(), new SourceTypeFilter());
        logicFilterMap.put(RuleType.USER_LEVEL.getType(), new UserTypeFilter());
    }
    
}
  • 在这里将可提供服务的决策节点配置到map结构中,对于这样的map结构可以抽取到数据库中,那么就可以非常方便的管理。

基础决策引擎功能

EngineBase责任链的模式

public abstract class EngineBase extends EngineConfig implements IEngine {

    @Override
    public abstract EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter);

    protected TreeNode engineDecisionMaker(TreeRich treeRich, Long treeId, String userId, Map<String, String> decisionMatter) {
        TreeRoot treeRoot = treeRich.getTreeRoot();
        Map<Long, TreeNode> treeNodeMap = treeRich.getTreeNodeMap();
        // 规则树根ID
        Long rootNodeId = treeRoot.getTreeRootNodeId();
        TreeNode treeNodeInfo = treeNodeMap.get(rootNodeId);
        //节点类型[NodeType];1子叶、2果实
        while (treeNodeInfo.getNodeType().equals(1)) {
            String ruleKey = treeNodeInfo.getRuleKey();
            LogicFilter logicFilter = logicFilterMap.get(ruleKey);
            String matterValue = logicFilter.matterValue(treeId, userId, decisionMatter);
            Long nextNode = logicFilter.filter(matterValue, treeNodeInfo.getTreeNodeLinkList());
            treeNodeInfo = treeNodeMap.get(nextNode);
            String log = "决策树引擎=>{} userId:{} treeId:{} treeNode:{} ruleKey:{} matterValue:{}";
            String.format(log, treeRoot.getTreeName(), userId, treeId, treeNodeInfo.getTreeNodeId(), ruleKey, matterValue);
            System.out.println(log);
        }
        return treeNodeInfo;
    }
}
  • 这里主要提供决策树流程的处理过程,有点像通过链路的关系(站点,用户会员等级)在二叉树中寻找果实节点的过程。
  • 同时提供一个抽象方法,执行决策流程的方法供外部去做具体的实现。

决策引擎的实现

TreeEngineHandle

public class TreeEngineHandle extends EngineBase{
    @Override
    public EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter) {
        // 决策流程
        TreeNode treeNode = engineDecisionMaker(treeRich, treeId, userId, decisionMatter);
        // 决策结果
        return new EngineResult(userId, treeId, treeNode.getTreeNodeId(), treeNode.getNodeValue());
    }
}

其它辅组类

NodeType

public enum NodeType {
    ROOT(0,"根节点"),

    LEAF(1,"叶子节点"),

    RESULT(2,"结果节点")
    ;

    private Integer code;
    private String desc;
}

RuleLimitType

public enum RuleLimitType {
    EQUAL(1,"相等"),

    GREATER(2,"大于"),

    LESS(3,"小于"),

    GREATER_OR_EQUAL(4,"大于等于"),

    LESS_OR_EQUAL(5,"小于等于"),
    ;

    private Integer code;
    private String desc;
}

RuleType

public enum RuleType {

    USER_AGE(1,"userAge","用户年龄"),

    USER_SEX(2,"userSex","用户性别"),

    USER_LEVEL(3,"userLevel","用户级别"),

    USER_SOURCE(4,"userSource","用户来源"),
    ;


    private Integer code;
    private String type;
    private String desc;
}

RuleConstant

public class RuleConstant {

    //用户来源于A站点
    public static final String USER_SOURCE_A = "A";

    //用户来源于B站点
    public static final String USER_SOURCE_B = "B";

    //用户来源于C站点
    public static final String USER_SOURCE_C = "C";

    //用户超级VIP
    public static final String USER_LEVEL_SUPER_VIP = "SUPER_VIP";

    //用户VIP
    public static final String USER_LEVEL_VIP = "VIP";

    //普通用户
    public static final String USER_LEVEL_COMMON = "COMMON";

    //金牌会员
    public static final String USER_LEVEL_GOLD = "GOLD";

    //银牌会员
    public static final String USER_LEVEL_SILVER = "SILVER";

    //铜牌会员
    public static final String USER_LEVEL_BRONZE = "BRONZE";

    //皇冠会员
    public static final String USER_LEVEL_CROWN = "CROWN";
}

最终测试类

TestMain

package 营销;

import cn.hutool.json.JSONUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestMain {
    static TreeRich treeRich;

    public static void main(String[] args) {
        init();

        System.out.println("决策树组合结构信息:");
        System.out.println(JSONUtil.toJsonStr(treeRich));

        IEngine treeEngineHandle = new TreeEngineHandle();

        Map<String, String> decisionMatter = new HashMap<>();
        //用户是来源于A站点的普通VIP时  获取对应的优惠
        decisionMatter.put(RuleType.USER_SOURCE.getType(), "A");
        decisionMatter.put(RuleType.USER_LEVEL.getType(), RuleConstant.USER_LEVEL_VIP);

        EngineResult result = treeEngineHandle.process(10001L, "12122333", treeRich, decisionMatter);

        System.out.println("测试结果:");
        System.out.println(JSONUtil.toJsonStr(result));
    }

    //初始化规则
    //某电商公司是多站点结构,目前已经开设了ABC三个子站点,这3个子站点的会员等级体系不同,
    //但产品模型和数据是完全一致的,产品平时在各个站点分别有不同的销售价格;现计划在全公司范围内进行618大促,
    //活动期间为6月17日零时-6月19日零时,针对不同用户的会员等级,对产品销售实行不同折扣优惠;
    //请设计相关的系统对外提供商品实时价格获取功能;
    //A站点
    //   超级VIP用户:7折优惠
    //   VIP用户:9折优惠
    //   普通用户:无优惠
    //B站点
    //   金牌客户:6.5折优惠
    //   银牌客户:7.5折优惠
    //   铜牌客户:8.5折优惠
    //   普通用户:无优惠
    //C站点
    //   皇冠会员:8折优惠
    //   普通用户:无优惠

    public static void init() {
        // 节点:1
        TreeNode treeNode_01 = new TreeNode();
        treeNode_01.setTreeId(10001L);
        treeNode_01.setTreeNodeId(1L);
        treeNode_01.setNodeType(1);
        treeNode_01.setNodeValue(null);
        treeNode_01.setRuleKey(RuleType.USER_SOURCE.getType());
        treeNode_01.setRuleDesc(RuleType.USER_SOURCE.getDesc());

        // 链接:1->11  即根节点 -> A站点
        TreeNodeLink treeNodeLink_11 = new TreeNodeLink();
        treeNodeLink_11.setNodeIdFrom(1L);
        treeNodeLink_11.setNodeIdTo(11L);
        treeNodeLink_11.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_11.setRuleLimitValue("A");

        // 链接:1->12 即根节点 -> B站点
        TreeNodeLink treeNodeLink_12 = new TreeNodeLink();
        treeNodeLink_12.setNodeIdFrom(1L);
        treeNodeLink_12.setNodeIdTo(12L);
        treeNodeLink_12.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_12.setRuleLimitValue("B");

        // 链接:1->13 即根节点 -> C站点
        TreeNodeLink treeNodeLink_13 = new TreeNodeLink();
        treeNodeLink_13.setNodeIdFrom(1L);
        treeNodeLink_13.setNodeIdTo(13L);
        treeNodeLink_13.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_13.setRuleLimitValue("C");


        List<TreeNodeLink> treeNodeLinkList_1 = new ArrayList<>();
        treeNodeLinkList_1.add(treeNodeLink_11);
        treeNodeLinkList_1.add(treeNodeLink_12);
        treeNodeLinkList_1.add(treeNodeLink_13);
        treeNode_01.setTreeNodeLinkList(treeNodeLinkList_1);


        // 节点:11 即A站点
        TreeNode treeNode_11 = new TreeNode();
        treeNode_11.setTreeId(10001L);
        treeNode_11.setTreeNodeId(11L);
        treeNode_11.setNodeType(NodeType.LEAF.getCode());
        treeNode_11.setNodeValue(null);
        treeNode_11.setRuleKey(RuleType.USER_LEVEL.getType());
        treeNode_11.setRuleDesc(RuleType.USER_LEVEL.getDesc());

        // 链接:11->111  A站点的超级VIP用户
        TreeNodeLink treeNodeLink_111 = new TreeNodeLink();
        treeNodeLink_111.setNodeIdFrom(11L);
        treeNodeLink_111.setNodeIdTo(111L);
        treeNodeLink_111.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_111.setRuleLimitValue(RuleConstant.USER_LEVEL_SUPER_VIP);

        // 链接:11->112 A站点的VIP用户
        TreeNodeLink treeNodeLink_112 = new TreeNodeLink();
        treeNodeLink_112.setNodeIdFrom(11L);
        treeNodeLink_112.setNodeIdTo(112L);
        treeNodeLink_112.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_112.setRuleLimitValue(RuleConstant.USER_LEVEL_VIP);

        // 链接:11->113 A站点的普通用户
        TreeNodeLink treeNodeLink_113 = new TreeNodeLink();
        treeNodeLink_113.setNodeIdFrom(11L);
        treeNodeLink_113.setNodeIdTo(112L);
        treeNodeLink_113.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_113.setRuleLimitValue(RuleConstant.USER_LEVEL_COMMON);


        List<TreeNodeLink> treeNodeLinkList_11 = new ArrayList<>();
        treeNodeLinkList_11.add(treeNodeLink_111);
        treeNodeLinkList_11.add(treeNodeLink_112);
        treeNodeLinkList_11.add(treeNodeLink_113);
        treeNode_11.setTreeNodeLinkList(treeNodeLinkList_11);

        // 节点:12  B站点的会员
        TreeNode treeNode_12 = new TreeNode();
        treeNode_12.setTreeId(10001L);
        treeNode_12.setTreeNodeId(12L);
        treeNode_12.setNodeType(NodeType.LEAF.getCode());
        treeNode_12.setNodeValue(null);
        treeNode_12.setRuleKey(RuleType.USER_LEVEL.getType());
        treeNode_12.setRuleDesc(RuleType.USER_LEVEL.getDesc());

        // 链接:12->121  B站点金牌会员
        TreeNodeLink treeNodeLink_121 = new TreeNodeLink();
        treeNodeLink_121.setNodeIdFrom(12L);
        treeNodeLink_121.setNodeIdTo(121L);
        treeNodeLink_121.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_121.setRuleLimitValue(RuleConstant.USER_LEVEL_GOLD);

        // 链接:12->122 B站点银牌会员
        TreeNodeLink treeNodeLink_122 = new TreeNodeLink();
        treeNodeLink_122.setNodeIdFrom(12L);
        treeNodeLink_122.setNodeIdTo(122L);
        treeNodeLink_122.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_122.setRuleLimitValue(RuleConstant.USER_LEVEL_SILVER);

        // 链接:12->123 B站点铜牌会员
        TreeNodeLink treeNodeLink_123 = new TreeNodeLink();
        treeNodeLink_123.setNodeIdFrom(12L);
        treeNodeLink_123.setNodeIdTo(123L);
        treeNodeLink_123.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_123.setRuleLimitValue(RuleConstant.USER_LEVEL_BRONZE);

        // 链接:12->123 B站点普通用户
        TreeNodeLink treeNodeLink_124 = new TreeNodeLink();
        treeNodeLink_124.setNodeIdFrom(12L);
        treeNodeLink_124.setNodeIdTo(124L);
        treeNodeLink_124.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_124.setRuleLimitValue(RuleConstant.USER_LEVEL_COMMON);

        List<TreeNodeLink> treeNodeLinkList_12 = new ArrayList<>();
        treeNodeLinkList_12.add(treeNodeLink_121);
        treeNodeLinkList_12.add(treeNodeLink_122);
        treeNodeLinkList_12.add(treeNodeLink_123);
        treeNodeLinkList_12.add(treeNodeLink_124);
        treeNode_12.setTreeNodeLinkList(treeNodeLinkList_12);


        // 节点:13  C站点的会员
        TreeNode treeNode_13 = new TreeNode();
        treeNode_13.setTreeId(10001L);
        treeNode_13.setTreeNodeId(13L);
        treeNode_13.setNodeType(NodeType.LEAF.getCode());
        treeNode_13.setNodeValue(null);
        treeNode_13.setRuleKey(RuleType.USER_LEVEL.getType());
        treeNode_13.setRuleDesc(RuleType.USER_LEVEL.getDesc());

        // 链接:13->131  C站点金牌会员
        TreeNodeLink treeNodeLink_131 = new TreeNodeLink();
        treeNodeLink_131.setNodeIdFrom(13L);
        treeNodeLink_131.setNodeIdTo(131L);
        treeNodeLink_131.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_131.setRuleLimitValue(RuleConstant.USER_LEVEL_CROWN);

        // 链接:13->132  C站点普通用户
        TreeNodeLink treeNodeLink_132 = new TreeNodeLink();
        treeNodeLink_132.setNodeIdFrom(13L);
        treeNodeLink_132.setNodeIdTo(132L);
        treeNodeLink_132.setRuleLimitType(RuleLimitType.EQUAL.getCode());
        treeNodeLink_132.setRuleLimitValue(RuleConstant.USER_LEVEL_COMMON);

        List<TreeNodeLink> treeNodeLinkList_13 = new ArrayList<>();
        treeNodeLinkList_13.add(treeNodeLink_131);
        treeNodeLinkList_13.add(treeNodeLink_132);
        treeNode_13.setTreeNodeLinkList(treeNodeLinkList_13);

        // 构建结果节点
        // 结果节点:111  A站点的超级会员
        TreeNode treeNode_111 = new TreeNode();
        treeNode_111.setTreeId(10001L);
        treeNode_111.setTreeNodeId(111L);
        treeNode_111.setNodeType(NodeType.RESULT.getCode());
        treeNode_111.setNodeValue("7折优惠");

        // 结果节点:112  A站点的会员
        TreeNode treeNode_112 = new TreeNode();
        treeNode_112.setTreeId(10001L);
        treeNode_112.setTreeNodeId(112L);
        treeNode_112.setNodeType(NodeType.RESULT.getCode());
        treeNode_112.setNodeValue("9折优惠");

        // 结果节点:113  A站点的普通用户
        TreeNode treeNode_113 = new TreeNode();
        treeNode_113.setTreeId(10001L);
        treeNode_113.setTreeNodeId(113L);
        treeNode_113.setNodeType(NodeType.RESULT.getCode());
        treeNode_113.setNodeValue("无优惠");

        // 结果节点:121  B站点的金牌会员
        TreeNode treeNode_121 = new TreeNode();
        treeNode_121.setTreeId(10001L);
        treeNode_121.setTreeNodeId(121L);
        treeNode_121.setNodeType(NodeType.RESULT.getCode());
        treeNode_121.setNodeValue("6.5折优惠");

        // 结果节点:122  B站点的银牌会员牌会员
        TreeNode treeNode_122 = new TreeNode();
        treeNode_122.setTreeId(10001L);
        treeNode_122.setTreeNodeId(122L);
        treeNode_122.setNodeType(NodeType.RESULT.getCode());
        treeNode_122.setNodeValue("7.5折优惠");

        // 结果节点:123  B站点的铜牌会员牌会员
        TreeNode treeNode_123 = new TreeNode();
        treeNode_123.setTreeId(10001L);
        treeNode_123.setTreeNodeId(123L);
        treeNode_123.setNodeType(NodeType.RESULT.getCode());
        treeNode_123.setNodeValue("8.5折优惠");

        // 结果节点:124  B站点的普通用户
        TreeNode treeNode_124 = new TreeNode();
        treeNode_124.setTreeId(10001L);
        treeNode_124.setTreeNodeId(124L);
        treeNode_124.setNodeType(NodeType.RESULT.getCode());
        treeNode_124.setNodeValue("无优惠");


        // C站点的结果节点:131  C站点的黄金会员
        TreeNode treeNode_131 = new TreeNode();
        treeNode_131.setTreeId(10001L);
        treeNode_131.setTreeNodeId(131L);
        treeNode_131.setNodeType(NodeType.RESULT.getCode());
        treeNode_131.setNodeValue("8折优惠");

        TreeNode treeNode_132 = new TreeNode();
        treeNode_132.setTreeId(10001L);
        treeNode_132.setTreeNodeId(132L);
        treeNode_132.setNodeType(NodeType.RESULT.getCode());
        treeNode_132.setNodeValue("无优惠");


        // 树根
        TreeRoot treeRoot = new TreeRoot();
        treeRoot.setTreeId(10001L);
        treeRoot.setTreeRootNodeId(1L);
        treeRoot.setTreeName("规则决策树");
        Map<Long, TreeNode> treeNodeMap = new HashMap<>();
        treeNodeMap.put(1L, treeNode_01);
        treeNodeMap.put(11L, treeNode_11);
        treeNodeMap.put(12L, treeNode_12);
        treeNodeMap.put(13L, treeNode_13);
        treeNodeMap.put(111L, treeNode_111);
        treeNodeMap.put(112L, treeNode_112);
        treeNodeMap.put(121L, treeNode_121);
        treeNodeMap.put(122L, treeNode_122);
        treeNodeMap.put(123L, treeNode_123);
        treeNodeMap.put(124L, treeNode_124);
        treeNodeMap.put(131L, treeNode_131);
        treeNodeMap.put(132L, treeNode_132);
        treeRich = new TreeRich(treeRoot, treeNodeMap);
    }


}

一些还可以继续深入优化的点

  1. 可以做对应的页面进行配置

  2. 规则可能会膨胀,为了便于管理,可以参考Nacos的命名空间隔离设计


    image.png
  3. 为了规则的实时生效,可以参考sentinel和nacos是怎么在页面配置后实时生效的

  4. 当前设计还不支持多条件都满足时,比如说满足条件的有N个优惠券,那我可能还得计算出优惠最大得组合,那就涉及到树的回溯,和树的减枝.可以使用树的序列化和反序列化,结合字符串匹配算法来进行匹配减枝,其次真正的营销系统还会分为平行式规则和递进式规则,这又是另外的扩展优化方向

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

推荐阅读更多精彩内容