營銷系統規則引擎設計

例子
某電商公司是多站點結構,目前已經開設了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閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容