ip2region - 准确率99.9%的离线IP地址定位库,0.0x毫秒级查询,ip2region.db数据库只有数MB,提供了java,php,c,python,nodejs,golang,c#等查询绑定和Binary,B树,内存三种查询算法。
数据聚合了一些知名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毫秒级别,内置了三种查询算法
任何客户端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毫秒 |
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>