Java实现IP动态匹配

最近接触了个新的数据结构: Trie 树/ 前缀树/ 字典树 等,都是指的一个意思。想了想,刚好能用来做IP地址的动态匹配,那就实践一下,学以致用。


文章目录

  • Java实现IP动态匹配
  • 需求描述
  • 分析设计
  • 普通思路分析
  • 前缀树思路分析
  • 代码实现
  • 测试用例
  • 小结


需求描述

有些时候处于安全考虑,我们需要在系统访问路径或者功能操作上面增加一些权限管控。其中一个比较简单的方案就是IP管控,能不能实现一个简单的动态IP管控呢?

这里只简单的考虑两种用例

  • 全字段匹配,192.168.10.1
  • 模糊字段匹配,192.168.10.* 或 192.168.*.1

分析设计

全字段匹配其实可以直接equals 比较 就行了,复杂的只要是动态字段匹配。

普通思路分析

用面向对象的思路来整。

可以IP地址切分4段,每个段抽象成一个Class,value表示规则的值,fuzzy表示是否模糊匹配,这样4个段对象就构成了1个IP规则

class Segment{
    String value;
    boolean fuzzy;
}

class IPRule{
    Segment [] segments;
}

然后为了提高效率,可以使用字典结构,将每个规则段的 Value 用 HashMap 缓存起来

HashMap<String, IPRule> firstSegmentMap;
HashMap<String, IPRule> secondSegmentMap;
HashMap<String, IPRule> thirdSegmentMap;
HashMap<String, IPRule> fourthSegmentMap;

然后就是 进行分段匹配,做个筛选

中间可能做一些优化,segment 引用 IPRule 或者 segment 引用下一个Segment,链式结构等等

也能满足需求

前缀树思路分析

这里基本思路和上一小节分析类似,不过这里就用成熟的数据结果—前缀树。

将IP规则按段分成4部分,其中每一部分都可以作为一个节点,那么我们就整成了一个3层的树,类似

java ip段 java ip段匹配_java ip段

基本的结构就是这样的

class IPNode {
   String pattern;
   String nodeVal;
   List<IPNode> childrenNodes;
   boolean fuzzy;

抽象两个方法

// 增加IP规则的方法
void addRule(String ipRule);
// 查询具体的IP地址的方法
IPNode search(String ipAddress);

search 基本思路:

  1. 将IP分4段先从根节点中 开始找 1段 匹配的所有子节点 nodes1
  2. 然后再从1中的nodes1 中找 2段匹配的 所有子节点 nodes2
  3. 然后再从2中的nodes2 中找 3段匹配的 所有子节点 nodes3
  4. 然后再从3中的nodes3 中找 4段匹配的 所有子节点 nodes4
  5. 找到任意一个就结束

代码实现

这里主要写前缀树的实现

@Data
public class IPNode {
    private String pattern; //规则内容
    private String nodeValue; //最后段内容
    private List<IPNode> childrenNodes; //子节点
    private boolean fuzzy; //是否模糊匹配,nodeValue 为 "*"时,为true


    public IPNode() {
    }

    public IPNode(String nodeValue) {
        this.nodeValue = nodeValue;
        this.pattern = "";
        fuzzy = "*".equals(nodeValue);
    }

    /**
     * 增加一个IP地址规则
     * @param ipAddressPattern 新的IP地址规则
     */
    public void addPattern(String ipAddressPattern){
        addPattern(ipAddressPattern, ipAddressPattern.split("\\."), 0);
    }

    /**
     * 增加一个IP地址规则
     * @param pattern IP地址规则
     * @param nodeValues 规则分段值
     * @param valueIndex 规则索引
     */
    private void addPattern(String pattern, String[] nodeValues, int valueIndex){
        if(nodeValues.length == valueIndex){
            this.pattern = pattern;
            return;
        }

        String nodeValue = nodeValues[valueIndex];
        IPNode child = matchChild(nodeValue);

        if(child==null){
            child = new IPNode(nodeValue);
            if(childrenNodes==null){
                childrenNodes = new ArrayList<>();
            }
            childrenNodes.add(child);
        }

        child.addPattern(pattern, nodeValues, valueIndex+1);
    }

    /**
     * 找出匹配到的第一个子节点
     * @param nodeValue IP分段值
     * @return IPNode 匹配到的规则
     */
    private IPNode matchChild(String nodeValue) {
        if(childrenNodes == null || childrenNodes.isEmpty()){
            return null;
        }
        for(IPNode n: childrenNodes){
            if(n.getNodeValue().equals(nodeValue) || n.isFuzzy()){
                return n;
            }
        }
        return null;
    }

    /**
     * 通过IP地址匹配
     * @param ipAddress ip地址
     * @return IPNode 匹配到的规则
     */
    public IPNode search(String ipAddress){
        return search(ipAddress.split("\\."),0);
    }

    /**
     * 通过IP地址匹配
     * @param nodeValues ip地址分段数组
     * @param valueIndex 值序号
     * @return IPNode 匹配到的规则
     */
    private IPNode search(String[]nodeValues, int valueIndex){
        if(nodeValues.length==valueIndex ){
            if(pattern==null|| pattern.length()==0){
                return null;
            }
            return this;
        }

        String currentNode = nodeValues[valueIndex];
        List<IPNode> childrenNodes = matchChildren(currentNode);
        if(childrenNodes!=null && childrenNodes.size()>0) {
            for (IPNode child : childrenNodes) {
                IPNode search = child.search(nodeValues, valueIndex + 1);
                if (search != null) {
                    return search;
                }
            }
        }
        return null;
    }

    /**
     * 检索匹配到的所有子节点
     * @param currentNode 当前节点IP分段值
     * @return List<IPNode> 匹配到的规则
     */
    private List<IPNode> matchChildren(String currentNode) {
        List<IPNode> nodes = new ArrayList<>();
        if(childrenNodes!=null) {
            for (IPNode n : childrenNodes) {
                if (n.getNodeValue().equals(currentNode) || n.isFuzzy()) {
                    nodes.add(n);
                }
            }
        }
        return nodes;
    }
}

测试用例

编写一个测试用例

public class IPMatcherTest {

    public static void main(String[] args) {
        String addressPattern ="192.168.*.161";
        IPNode root = new IPNode();
        root.addPattern(addressPattern);
        passOrNot(root, "192.168.10.161");
        passOrNot(root, "192.168.10.162");

    }

    public static void passOrNot(IPNode root, String targetIP){
        IPNode search = root.search(targetIP);
        if(search!=null) {
            System.out.printf("pattern: %s, nodeValue: %s, fuzzy: %b\n", search.getPattern(), search.getNodeValue(), search.isFuzzy());
            System.out.printf("%s pass\n",targetIP);
        }else{
            System.out.printf("%s not pass\n",targetIP);
        }
    }
}

小结

本篇通过前缀树实现了IP动态匹配,支持的功能比较简单,不过思路值得记录。