根据IP地址获取城市(ip2region)
- Ip2region是什么?
- Ip2region特性
- 99.9%准确率
- 标准化的数据格式
- 体积小
- 查询速度快
- 多查询客户端的支持
- maven集成
- 小坑
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次):
- >80%, 淘宝IP地址库:http://ip.taobao.com/
- ≈10%, GeoIP:https://geoip.com/
- ≈2%, 纯真IP库:http://www.cz88.net/
ps:如果上述开放API或者数据都不给开放数据时ip2region将停止数据的更新服务。
标准化的数据格式
每条ip数据段都固定了格式:
城市Id|国家|区域|省份|城市|ISP
只有中国的数据精确到了城市,其他国家有部分数据只能定位到国家,后前的选项全部是0,已经包含了全部你能查到的大大小小的国家(请忽略前面的城市Id,个人项目需求)。
体积小
包含了全部的IP,生成的数据库文件ip2region.db只有几MB,最小的版本只有1.5MB,随着数据的详细度增加数据库的大小也慢慢增大,目前还没超过8MB。
查询速度快
全部的查询客户端单次查询都在0.x毫秒级别,内置了三种查询算法
- memory算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内,C语言的客户端单次查询在0.00x毫秒级别。
- binary算法:基于二分查找,基于ip2region.db文件,不需要载入内存,单次查询在0.x毫秒级别。
- b-tree算法:基于btree算法,基于ip2region.db文件,不需要载入内存,单词查询在0.x毫秒级别,比binary算法更快。
ps:任何客户端b-tree都比binary算法快,当然memory算法固然是最快的!
多查询客户端的支持
已经集成的客户端有:java、C#、php、c、python、nodejs、php扩展(php5和php7)、golang、rust、lua、lua_c, nginx。
maven集成
- 引入依赖
<!-- ip2region -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>1.7.2</version>
</dependency>
- 下载ip2region.db,下载链接:https://github.com/fsmuzqhybe21945/ip2region/blob/master/v1.0/data/ip2region.db
- 将ip2region.db放入项目中的src/main/resource文件夹下
- 编写工具类(内容参考”org.lionsoul.ip2region.test.TestSearcher.java“)
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;
import org.lionsoul.ip2region.Util;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
public class IPUtil {
/**
* 获取IP地址
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if ("0:0:0:0:0:0:0:1".equals(ip)) {
ip = "127.0.0.1";
}
if (ip.split(",").length > 1) {
ip = ip.split(",")[0];
}
return ip;
}
/**
* 根据IP地址获取城市
* @param ip
* @return
*/
public static String getCityInfo(String ip) {
URL url = IPUtil.class.getClassLoader().getResource("ip2region.db");
File file;
if (url != null) {
file = new File(url.getFile());
} else {
return null;
}
if (!file.exists()) {
System.out.println("Error: Invalid ip2region.db file, filePath:" + file.getPath());
return null;
}
//查询算法
int algorithm = DbSearcher.BTREE_ALGORITHM; //B-tree
//DbSearcher.BINARY_ALGORITHM //Binary
//DbSearcher.MEMORY_ALGORITYM //Memory
try {
DbConfig config = new DbConfig();
DbSearcher searcher = new DbSearcher(config, file.getPath());
Method method;
switch ( algorithm )
{
case DbSearcher.BTREE_ALGORITHM:
method = searcher.getClass().getMethod("btreeSearch", String.class);
break;
case DbSearcher.BINARY_ALGORITHM:
method = searcher.getClass().getMethod("binarySearch", String.class);
break;
case DbSearcher.MEMORY_ALGORITYM:
method = searcher.getClass().getMethod("memorySearch", String.class);
break;
default:
return null;
}
DataBlock dataBlock;
if (!Util.isIpAddress(ip)) {
System.out.println("Error: Invalid ip address");
return null;
}
dataBlock = (DataBlock) method.invoke(searcher, ip);
return dataBlock.getRegion();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- main方法测试
public static void main(String[] args) {
System.out.println(IPUtil.getCityInfo("113.105.172.33"));
}
运行结果:中国|华南|广东省|东莞市|电信
小坑
暂时遇到的坑是文件读取的问题,一开始写的是File file = new File(“src/main/resource/ip2region.db”),本地测试没问题,放到生产环境就找不到路径了,后面经过多次百度,改成上面代码那样,目前测试可以兼容生产环境和测试环境。