Ip2region是什么?

ip2region - 准确率99.9%的离线IP地址定位库,0.0x毫秒级查询,ip2region.db数据库只有数MB,提供了java,php,c,python,nodejs,golang,c#等查询绑定和Binary,B树,内存三种查询算法。

Ip2region特性

99.9%准确率

数据聚合了一些知名ip到地名查询提供商的数据,这些是他们官方的的准确率,经测试着实比经典的纯真IP定位准确一些。
ip2region的数据聚合自以下服务商的开放API或者数据(升级程序每秒请求次数2到4次):
01, >80%, 淘宝IP地址库, 淘宝IP地址库 02, ≈10%, GeoIP, GeoIP Lookup Tool | GeoIP.com 03, ≈2%, 纯真IP库, 纯真-中国IP地理位置数据库首创者备注:如果上述开放API或者数据都不给开放数据时ip2region将停止数据的更新服务。

标准化的数据格式

每条ip数据段都固定了格式:

_城市Id|国家|区域|省份|城市|ISP_

只有中国的数据精确到了城市,其他国家有部分数据只能定位到国家,后前的选项全部是0,已经包含了全部你能查到的大大小小的国家(请忽略前面的城市Id,个人项目需求)。

体积小

包含了全部的IP,生成的数据库文件ip2region.db只有几MB,最小的版本只有1.5MB,随着数据的详细度增加数据库的大小也慢慢增大,目前还没超过8MB。

查询速度快

全部的查询客户端单次查询都在0.x毫秒级别,内置了三种查询算法

  1. memory算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内,C语言的客户端单次查询在0.00x毫秒级别。
  2. binary算法:基于二分查找,基于ip2region.db文件,不需要载入内存,单次查询在0.x毫秒级别。
  3. b-tree算法:基于btree算法,基于ip2region.db文件,不需要载入内存,单词查询在0.x毫秒级别,比binary算法更快。

任何客户端b-tree都比binary算法快,当然memory算法固然是最快的!

多查询客户端的支持

已经集成的客户端有:java、C#、php、c、python、nodejs、php扩展(php5和php7)、golang、rust、lua、lua_c, nginx。

binding

描述

开发状态

binary查询耗时

b-tree查询耗时

memory查询耗时

c

ANSC c binding

已完成

0.0x毫秒

0.0x毫秒

0.00x毫秒

c#

c# binding

已完成

0.x毫秒

0.x毫秒

0.1x毫秒

golang

golang binding

已完成

0.x毫秒

0.x毫秒

0.1x毫秒

java

java binding

已完成

0.x毫秒

0.x毫秒

0.1x毫秒

lua

lua实现的binding

已完成

0.x毫秒

0.x毫秒

0.x毫秒

lua_c

lua的c扩展

已完成

0.0x毫秒

0.0x毫秒

0.00x毫秒

nginx

nginx的c扩展

已完成

0.0x毫秒

0.0x毫秒

0.00x毫秒

nodejs

nodejs

已完成

0.x毫秒

0.x毫秒

0.1x毫秒

php

php实现的binding

已完成

0.x毫秒

0.1x毫秒

0.1x毫秒

php5_ext

php5的c扩展

已完成

0.0x毫秒

0.0x毫秒

0.00x毫秒

php7_ext

php7的c扩展

已完成

0.0毫秒

0.0x毫秒

0.00x毫秒

python

python bindng

已完成

0.x毫秒

0.x毫秒

0.x毫秒

rust

rust binding

已完成

0.x毫秒

0.x毫秒

0.x毫秒

Spring Boot 快速集成 ip2region

 1.  引入maven依赖,笔者使用 2.6.4 版本

<!-- ip2region -->
<dependency>
    <groupId>org.lionsoul</groupId>
    <artifactId>ip2region</artifactId>
    <version>${ip2region.version}</version>
</dependency>

2.  代码编码

package com.healthy.common.ip2region;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ClassLoaderUtil;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

import java.io.IOException;
import java.io.InputStream;

/**
 * Ip2RegionAutoConfiguration
 *
 * @author healthy
 */
@AutoConfiguration
@ConditionalOnClass(Ip2regionSearcher.class)
@EnableConfigurationProperties(Ip2RegionProperties.class)
@ConditionalOnProperty(prefix = Ip2RegionProperties.PREFIX, name = "enabled", havingValue = "true")
public class Ip2RegionAutoConfiguration {

	@Bean
	public Ip2regionSearcher ip2regionSearcher(@Autowired Ip2RegionProperties ip2RegionProperties) {
		ClassLoader classLoader = ClassLoaderUtil.getClassLoader();
		try (InputStream inputStream = classLoader.getResourceAsStream(ip2RegionProperties.getDbFile())) {
			Searcher searcher = Searcher.newWithBuffer(IoUtil.readBytes(inputStream));
			return new Ip2regionSearcher(searcher);
		}
		catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

}
package com.healthy.common.ip2region;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Ip2RegionProperties
 *
 * @author healthy
 */
@ConfigurationProperties(prefix = Ip2RegionProperties.PREFIX)
public class Ip2RegionProperties {

	public static final String PREFIX = "ip2region";

	/**
	 * 是否开启自动配置
	 */
	private boolean enabled = false;

	/**
	 * db数据文件位置
	 * <p>
	 * ClassPath目录下
	 * </p>
	 */
	private String dbFile = "data/ip2region.xdb";

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public String getDbFile() {
		return dbFile;
	}

	public void setDbFile(String dbFile) {
		this.dbFile = dbFile;
	}

}
package com.healthy.common.ip2region;

import cn.hutool.core.util.StrUtil;
import lombok.Data;

import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

@Data
public class IpInfo {

	/**
	 * 国家
	 */
	private String country;

	/**
	 * 区域
	 */
	private String region;

	/**
	 * 省
	 */
	private String province;

	/**
	 * 城市
	 */
	private String city;

	/**
	 * 运营商
	 */
	private String isp;

	/**
	 * 拼接完整的地址
	 * @return address
	 */
	public String getAddress() {
		Set<String> regionSet = new LinkedHashSet<>();
		regionSet.add(country);
		regionSet.add(region);
		regionSet.add(province);
		regionSet.add(city);
		regionSet.removeIf(Objects::isNull);
		return StrUtil.join(StrUtil.EMPTY, regionSet);
	}

	/**
	 * 拼接完整的地址
	 * @return address
	 */
	public String getAddressAndIsp() {
		Set<String> regionSet = new LinkedHashSet<>();
		regionSet.add(country);
		regionSet.add(region);
		regionSet.add(province);
		regionSet.add(city);
		regionSet.add(isp);
		regionSet.removeIf(Objects::isNull);
		return StrUtil.join(StrUtil.EMPTY, regionSet);
	}

}
package com.healthy.common.ip2region;

import lombok.SneakyThrows;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.lang.Nullable;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.regex.Pattern;

/**
 * Ip2regionSearcher
 *
 * @author healthy
 */
public class Ip2regionSearcher implements DisposableBean {

	private static final Pattern SPLIT_PATTERN = Pattern.compile("\\|");

	private final Searcher searcher;

	public Ip2regionSearcher(Searcher searcher) {
		this.searcher = searcher;
	}

	@SneakyThrows
	public String searchStr(String ip) {
		return searcher.search(ip);
	}

	public IpInfo search(String ip) {
		String region = searchStr(ip);
		if (region == null) {
			return null;
		}
		IpInfo ipInfo = new IpInfo();
		String[] splitInfos = SPLIT_PATTERN.split(region);
		// 补齐5位
		if (splitInfos.length < 5) {
			splitInfos = Arrays.copyOf(splitInfos, 5);
		}
		ipInfo.setCountry(filterZero(splitInfos[0]));
		ipInfo.setRegion(filterZero(splitInfos[1]));
		ipInfo.setProvince(filterZero(splitInfos[2]));
		ipInfo.setCity(filterZero(splitInfos[3]));
		ipInfo.setIsp(filterZero(splitInfos[4]));
		return ipInfo;
	}

	/**
	 * 数据过滤,因为 ip2Region 采用 0 填充的没有数据的字段
	 * @param info info
	 * @return info
	 */
	@Nullable
	private String filterZero(@Nullable String info) {
		// null 或 0 返回 null
		if (info == null || BigDecimal.ZERO.toString().equals(info)) {
			return null;
		}
		return info;
	}

	@Override
	public void destroy() throws Exception {
		if (this.searcher != null) {
			this.searcher.close();
		}
	}

}

3. YML配置

# ip2region
ip2region:
  enabled: true
  dbfile: data/ip2region.db

4. 如何使用

@Autowired
private Ip2regionSearcher ip2regionSearcher;

IpInfo ipInfo= ip2regionSearcher.search(ip);

6. 常见问题

项目中如果使用了 maven-resources-plugin 插件,请过滤白名单,具体配置参考如下:

<plugin>
	<artifactId>maven-resources-plugin</artifactId>
	<configuration>
		<nonFilteredFileExtensions>
			<nonFilteredFileExtension>xdb</nonFilteredFileExtension>
		</nonFilteredFileExtensions>
	</configuration>
</plugin>