文章
问答
冒泡
IP地区识别离线库IP2Region使用

前言:

最近在做一块业务,需要开发IP归属地识别相关的相关功能,调查了一下,目前IP归属地是有两种方案,一种是在线接口比如淘宝、百度、ip-api等。这种方案的优点是覆盖面广,查询准确,缺点是有的限制qps,有的收费。另外一种就是离线库查询,具体的开源各个语言不尽相同,这里我使用到JAVA的ip2region开源库,这里记录一下使用方式。

一、maven坐标

ip2region总体分为两个大版本:

1.x、提供三种算法,其中内存查询速度在0.1x毫秒级别,离线库文件ip2region.db

2.x、同样提供三种算法,其中内存查询速度在微秒级别,离线库文件ip2region.xdb

这里我直接使用2.x的最新版本。

<dependency>
   <groupId>org.lionsoul</groupId>
   <artifactId>ip2region</artifactId>
   <version>2.6.5</version>
</dependency>

二、离线库下载及加载

离线数据库可以去github项目中下载,地址 ip2region,文件目录地址在/ip2regon/data/ip2region.xdb。

1、放置目录

将文件下载完成后,放到项目的resource目录下新建的ip2region目录中(注意:如果使用maven插件打包的话,需要添加过滤,否则xdb文件会存在混乱的情况。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <configuration>
        <encoding>UTF-8</encoding>
        <nonFilteredFileExtensions>
            <nonFilteredFileExtension>xdb</nonFilteredFileExtension>
            <nonFilteredFileExtension>db</nonFilteredFileExtension>
        </nonFilteredFileExtensions>
    </configuration>
</plugin>

(这里需要过滤xdb文件)

2、编写文件加载类

这里我为了最小依赖( spring太重了),所以使用了静态加载的模式来加载,初始化当前对象即可使用searcher或者使用提供的封装方法(ip2region searcher类是不提供线程安全的,所以这里直接new Ip2Region对象使用searcher可隔离查询对象

public class Ip2Region {

    protected static Searcher searcher;

    static {
        InputStream resourceAsStream = Ip2Region.class.getClassLoader().getResourceAsStream("ip2region/ip2region.xdb");
        if (resourceAsStream != null) {
            byte [] cBuff= null;
            try {
                cBuff = IOUtils.toByteArray(resourceAsStream);
                searcher = Searcher.newWithBuffer(cBuff);
            }catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    resourceAsStream.close();
                }catch (Exception e) {}
            }
        }
    }
    public static Searcher getSearch(){
        return searcher;
    }
}

三、封装使用

1、封装操作类

这里封装了一个主要的方法,根据IP获取地区的,返回map。

/**
 * ip转long
 * @param ip
 * @return
 */
public static long ipToLong(String ip){
    String[] split = ip.split("\\.");
    long i1 = 0L;
    i1 += Integer.parseInt(split[0])<<24;
    i1 += Integer.parseInt(split[1])<<16;
    i1 += Integer.parseInt(split[2])<<8;
    return i1 + Integer.parseInt(split[3]);
}

/**
 * 获取IP对应地区
 * @param ip
 * @return Map<String,String>
 *          country: 中国
 *          province: 江苏
 *          city: 苏州
 */
public static Map<String,String> getArea(String ip) {
    long ipLong = ipToLong(ip);
    Map<String,String> areaMap;
    try {
        String search = searcher.search(ipLong);
        if (search == null || search.isEmpty())
            return null;
        String[] s1 = search.split("\\|");
        if (s1 == null || s1.length == 0) {
            return null;
        }
        areaMap = new HashMap<>(3);
        areaMap.put("country",s1[0]);
        areaMap.put("province",s1[2]);
        areaMap.put("city",s1[3]);
        return areaMap;
    }catch (IOException ioException) {
        ioException.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

2、封装使用类

静态加载 Ip2Regin对象,检查输入参数是否合法。

public class Ip2RegionHandler {

    protected static final Ip2Region INSTANCE = new Ip2Region();

    public static Map<String,String> getArea(String ip) throws Exception {
        checkIP(ip);
        return INSTANCE.getArea(ip);
    }

    public static void checkIP(String ip) throws Exception {
        if (ip == null) {
            throw new Exception("ip format err");
        }
        String[] ipSplit = ip.split("\\.");
        if (ipSplit.length != 4) {
            throw new Exception("ip format err");
        }
        for (int i = 0, n = ipSplit.length; i < n; i++) {
            if (!ipSplit[i].chars().allMatch(Character::isDigit)) {
                throw new Exception("ip format err");
            }
            int i1 = Integer.parseInt(ipSplit[i]);
            if ( i1 > 0XFF  || i1 < 0X0) {
                throw new Exception("ip format err");
            }
        }
    }
}

四、测试使用

这里我写了个单元测试,测试不同的IP归属地识别情况及错误输入参数。

class Ip2RegionHandlerTest {

    @ParameterizedTest
    @ValueSource(strings = {"12.0.1.123","12.0.1.123","1.125.1.97","5.125.1.97"})
    void getArea(String ip) throws Exception {
        Map<String, String> area = Ip2RegionHandler.getArea(ip);
        System.out.println(area.toString());
    }

    @ParameterizedTest
    @ValueSource(strings = {"12.0.1.123","257.0.0.1","asdasew"})
    void checkIP(String ip) {
        try {
            Ip2RegionHandler.checkIP(ip);
        }catch (Exception e) {
            System.out.println("errip : " + ip);
        }
    }
}

1、IP归属地测试

(由于离线库能做到的有限,所以国外IP只能到国家级别,国内能到市级别)

Connected to the target VM, address: '127.0.0.1:54264', transport: 'socket'
{province=0, country=美国, city=0}
{province=0, country=美国, city=0}
{province=南澳大利亚, country=澳大利亚, city=阿德莱德}
{province=0, country=伊朗, city=0}
Disconnected from the target VM, address: '127.0.0.1:54264', transport: 'socket'
Process finished with exit code 0

2、IP参数校验测试

Connected to the target VM, address: '127.0.0.1:54507', transport: 'socket'

errip : 257.0.0.1

errip : asdasew

Disconnected from the target VM, address: '127.0.0.1:54507', transport: 'socket'

Process finished with exit code 0

测试结果:符合预期

ip2region

关于作者

Dane.shang
快30岁了还没去过酒吧
获得点赞
文章被阅读