【我要找工作_04】年会抽奖时中大奖的为什么都是Leader?

284 阅读4分钟

不知道你是否想过:公司年会抽奖的时候、中大奖的都是我们的领导?而不是我们这种小卡拉米?如果你想知道的话。不妨看看此文~

本文主要描述了如何实现一个规则引擎,要实现的功能为通过输入员工的信息【性别、年龄 或者工龄】在年会抽奖的过程中、使其抽奖得到年会奖品。如抽奖者信息为女性、22岁使其得到200w的公司年会奖金。

数据模型:由于存在多个判断条件、且相互之间有一定的依赖性,如需要先判断性别、在判断年龄、在判断职位信息,图如下:

001.png

这就是数据结构中常见的树结构,刷过leetcode 的都知道,首先定义一个TreeNode节点类、表示一个个节点:

public class TreeNode {
    private Long treeNodeId;
    // 1 非叶子节点 2 叶子节点
    private Long nodeType;
    // 当前节点的判断规则
    private String ruleKey;
    // 当为叶子节点时,才有值,表示决策的结果
    private String nodeValue;
    // 用于描述每一条线,
    private List<NodeLine> nodeLines;
}
​
​

在定义一个 NodeLine 类,用于描述树中的路径:

public class NodeLine{
  
    private Long nodeLineId;
    // 树节点的id
    private Long treeNodeId;
    private Long fromNodeId;
    private Long toNodeId;
    // 0 大于 1 小于 2 等于 3 大于等于 4 小于等于
    private int nodeLineType;
    // 是否满足这个值,满足这两个判断条件才能够找到当前节点的下一个节点。
    private String nodeLineValue;
}

基于这两个类就可以组装一棵规则树了。对于每一个非叶子节点而言,首先要找到当前节点的判断规则,其次要根据用户的当前节点值,遍历nodeLine属性找到下一个节点,一直找到包含奖品信息的叶子节点。

规则树创建出来之后,就需要根据这棵树进行逻辑判断,不同的节点的判断依据不一样,依赖节点中的 ruleKey 属性,通过 ruleKey属性获取到当前节点的判断器、通过比较 nodeLine 的值和用户信息的值,确定是否能够达到下一节点。定义一个如下的接口:

// 所有非叶子节点的 common 行为
public interface LogicFilter {
    // 获取用户当前所在节点的节点值,在gender节点获取【man | woman】 在age节点获取【年龄】
    String getValue(UserRequest request);
    // 获取当前节点能否进入下一个节点,value:用户当前节点的节点值
    Long getNextNode(String value, List<NodeLine> nodeLines);
}

使用抽象类进行简单的实现:

public abstract class BaseLogic implements LogicFilter {
    // 不同的节点获取值的方式不一样,需要交给具体的filter实现
    @Override
    public abstract String getValue(UserRequest request);
​
    @Override
    public Long getNextNode(String value, List<NodeLine> nodeLines) {
        for (NodeLine nodeLine : nodeLines) {
            if (decisionLogic(value, nodeLine)) {
                return nodeLine.getToNodeId();
            }
        }
      // return null 也可~
        return -1L;
    }
   // 判断
    public boolean decisionLogic(String value, NodeLine nodeLine) {
​
        switch (nodeLine.getNodeLineType()) {
            case 1:
                return value.equals(nodeLine.getNodeLineValue());
            case 2:
                return Integer.parseInt(value) > Integer.parseInt(nodeLine.getNodeLineValue());
            case 3:
                return Integer.parseInt(value) < Integer.parseInt(nodeLine.getNodeLineValue());
            case 4:
                return Integer.parseInt(value) >= Integer.parseInt(nodeLine.getNodeLineValue());
            case 5:
                return Integer.parseInt(value) <= Integer.parseInt(nodeLine.getNodeLineValue());
            default:
                return false;
​
        }
    }
​
}

以年龄和性别为例:实现方式如下

public class UserGenderFilter extends BaseLogic {
    @Override
    public String getValue(UserRequest request) {
        return request.getInfos().get("userGender");
    }
}
public class UserAgeFilter extends BaseLogic {
    @Override
    public String getValue(UserRequest request) {
        return request.getInfos().get("userAge");
    }
}
​

将其放到一个容器中,共我们后续使用:

public class LogicContext {
    public static final Map<String, LogicFilter>  LOGIC_FILTER_MAP = new HashMap<>();
    static {
        LOGIC_FILTER_MAP.put("userAge", new UserAgeFilter());
        LOGIC_FILTER_MAP.put("userGender", new UserGenderFilter());
    }
}

UserRequest是一个封装了用户信息的JavaBean,主要属性如下:

private Long userId;
private Long ruleTreeId;
private Map<String, String> infos;

有了节点和判断器,就需要将其组装起来使用。定义一个引擎接口:这是给客户端调用的接口。

public interface Engine {
    FilterResponse process(UserRequest request);
}

简单实现如下:

public abstract class BaseEngine implements Engine {
​
    @Override
    public FilterResponse process(UserRequest request) {
        throw new RuntimeException("not implement");
    }
​
    protected FilterResponse makeResponse(TreeInfo treeInfo, UserRequest request) {
        // 根节点id
        Long treeRootId = treeInfo.getRoot().getTreeRootId();
        System.out.println("treeRootId:" + treeRootId);
        // 获取到第一个root节点的详细信息
        TreeNode treeNode = treeInfo.getTreeNodeMap().get(treeRootId);
        Long nextNodeId = 0L;
       // 当下一个节点不为 null 且 非叶子节点时,一只遍历直到找到叶子节点
        while (treeNode != null && treeNode.getNodeType() != 2l) {
            // 得到当前节点的判断规则
            String ruleKey = treeNode.getRuleKey();
            LogicFilter logicFilter = LogicContext.LOGIC_FILTER_MAP.get(ruleKey);
            String value = logicFilter.getValue(request);
          
            nextNodeId = logicFilter.getNextNode(value, treeNode.getNodeLines());
            System.out.println("nextNodeId = " + nextNodeId);
          
            if (nextNodeId == -1l || nextNodeId == null){
                return new FilterResponse(false, "fail");
            }
            treeNode = treeInfo.getTreeNodeMap().get(nextNodeId);
        }
       // 封装结果
        FilterResponse filterResponse = new FilterResponse();
        if (nextNodeId > 0L && treeNode != null) {
            filterResponse.setSuccess(true);
            filterResponse.setMessage("success" + treeInfo.getTreeNodeMap().get(nextNodeId));
        } else {
            filterResponse.setSuccess(false);
            filterResponse.setMessage("fail");
        }
        return filterResponse;
    }
}

子类实现:

public class EngineHandler extends BaseEngine {
    // 包含所有决策树到容器 模拟当前系统中查出来的所有决策树信息cache
    public static Map<Long, TreeInfo> treeInfoMap = new java.util.HashMap<>();
    @Override
    public FilterResponse process(UserRequest request) {
        Long ruleTreeId = request.getRuleTreeId();
        // mock db cache 操作
        TreeInfo treeInfo = treeInfoMap.get(ruleTreeId);
        if (treeInfo == null) {
            return null;
        }
        return makeResponse(treeInfo, request);
    }
}
​

客户端使用,首先组装 treeInfoMap结构,这一步模拟系统中缓存了决策树信息的Cache。决策树的结构如第一张图。

003_tree.png

构建如下的用户请求:

002_req.png

我是在EngineHandler中通过主方法测试的:

 public static void main(String[] args) {
        EngineHandler engineHandler = new EngineHandler();
        UserRequest request = new UserRequest();
        request.setRuleTreeId(1L);
        Map<String, String> infos = new HashMap<>();
        infos.put("userGender", "woman");
        infos.put("userAge", "22");
        request.setInfos(infos);
        FilterResponse process = engineHandler.process(request);
        System.out.println(process.isSuccess());
        System.out.println(process.getMessage());
    }
// output:
//successTreeNode{treeNodeId=7, nodeType=2, ruleKey='null', nodeValue='ge18: 200w大奖', nodeLines=null}

到这里是否明白了为什么中大奖的不是你?

image.png