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层的树,类似
基本的结构就是这样的
class IPNode {
String pattern;
String nodeVal;
List<IPNode> childrenNodes;
boolean fuzzy;
抽象两个方法
// 增加IP规则的方法
void addRule(String ipRule);
// 查询具体的IP地址的方法
IPNode search(String ipAddress);
search 基本思路:
- 将IP分4段先从根节点中 开始找 1段 匹配的所有子节点 nodes1
- 然后再从1中的nodes1 中找 2段匹配的 所有子节点 nodes2
- 然后再从2中的nodes2 中找 3段匹配的 所有子节点 nodes3
- 然后再从3中的nodes3 中找 4段匹配的 所有子节点 nodes4
- 找到任意一个就结束
代码实现
这里主要写前缀树的实现
@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动态匹配,支持的功能比较简单,不过思路值得记录。