目录
前言
一、获取访问的IP地址
二、通过IP地址获取对应的归属地
2.1 Ip2region
2.1.1 高达 99.9 % 的查询准确率
2.1.2 Ip2region V2.0 特性
2.1.3 多语言以及查询客户端的支持
2.2 Ip2region xdb Java 查询客户端实现
2.2.1 引入 Maven 仓库
2.2.2 ip2region.xdb 文件,放到工程resources目录下
2.2.3 实现方式
2.2.3.1 基于文件查询
2.2.3.2 缓存VectorIndex索引
2.2.3.3 缓存整个 xdb 数据
2.2.3.4 通过第三方API查询(在线查询)
2.2.3.5 最优方案
前言
细心的朋友们可能已经发现了,先在抖音、知乎、快手、小红书等这些平台已经上线了“网络用户显示 IP 的功能”,境外用户显示的是国家,国内的用户显示的省份,而且此项显示无法关闭,归属地强制显示。
作为一个努力搬砖的码农,我们肯定要来看一下这个功能是如何实现的,今天这篇文章,就来讲述一下这个功能是怎么实现的。
一、获取访问的IP地址
HttpServletRequest 获取 IP
首先我们来看一下,在 Java 中,是如何获取到 IP 属地的,主要有以下两步:
-
通过 HttpServletRequest 对象,获取用户的 【IP】 地址
-
通过 IP 地址,获取对应的【省份、城市】
我这里写一个工具类用于获取 IP 地址,因为用户的每次 Request 请求都会携带请求的 IP 地址放到请求头中,所以我们可以通过截取请求中的 IP 来获取 IP 地址,代码如下:
package com.test.java.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Objects;
/**
* IP地址Util
*/
@Slf4j
public class IpAddressUtil {
/**
* 获取请求的 IP 地址
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
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 ("127.0.0.1".equals(ip)) {
// 根据网卡取本机配置的 IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (Exception e) {
e.printStackTrace();
log.error("获取IP地址异常,{}", e.getMessage());
}
if (inet != null) {
ip = inet.getHostAddress();
}
}
}
// 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ip != null && ip.length() > 15) {
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
// 本机访问
if ("localhost".equalsIgnoreCase(ip) || "127.0.0.1".equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)) {
// 根据网卡取本机配置的IP
InetAddress inet;
try {
inet = InetAddress.getLocalHost();
ip = inet.getHostAddress();
} catch (Exception e) {
e.printStackTrace();
log.error("获取本机IP地址异常,{}", e.getMessage());
}
}
// 如果查找不到 IP,可以返回 127.0.0.1,可以做一定的处理,但是这里不考虑
// if (ip == null) {
// return "127.0.0.1";
// }
return ip;
}
/**
* 获取IP地址
*/
public static String getIpAddress(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ipAddress = headers.getFirst("X-Forwarded-For");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = headers.getFirst("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = headers.getFirst("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();
if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
// 根据网卡取本机配置的IP
try {
InetAddress inet = InetAddress.getLocalHost();
ipAddress = inet.getHostAddress();
} catch (Exception e) {
log.error("获取IP地址异常,{}", e.getMessage());
}
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.split(",")[0];
}
return ipAddress;
}
/**
* 获取mac地址
*/
public static String getMacIpAddress() {
try {
InetAddress inetAddress = InetAddress.getLocalHost();
byte[] macAddressBytes = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress();
// 将mac地址拼装成String
StringBuilder sb = new StringBuilder();
for (int i = 0; i < macAddressBytes.length; i++) {
if (i != 0) {
sb.append("-");
}
// mac[i] & 0xFF 是为了把byte转化为正整数
String s = Integer.toHexString(macAddressBytes[i] & 0xFF);
sb.append(s.length() == 1 ? 0 + s : s);
}
return sb.toString().trim().toUpperCase();
} catch (Exception e) {
log.error("Mac获取IP地址异常,{}", e.getMessage());
}
return "";
}
}
这里出现了三个名词:
- X-Forwarded-For:一个 HTTP 扩展头部,主要是为了让 Web 服务器获取访问用户的真实 IP 地址。每个 IP 地址,每个值通过逗号+空格分开,最左边是最原始客户端的 IP 地址,中间如果有多层代理,每⼀层代理会将连接它的客户端 IP 追加在 X-Forwarded-For 右边
-
X-Real-IP:一般只记录真实发出请求的客户端IP
-
Proxy-Client-IP:这个一般是经过 Apache http 服务器的请求才会有,用 Apache http 做代理时一般会加上 Proxy-Client-IP 请求头
-
WL-Proxy-Client-IP:也是通过 Apache http 服务器,在 weblogic 插件加上的头
二、通过IP地址获取对应的归属地
通过第三方地址库 Ip2region,获取IP归属地。
2.1 Ip2region
Ip2region 是一个 Gthub 的开源项目,即 Ip2region 开源项目。
github地址:https://github.com/lionsoul2014/ip2region
这个开源库目前已经更新到了 V2 的版本,现在的它是一个强大的离线IP地址定位库和IP定位数据管理框架,其达到了微秒级别的查询效率,还提供了众多主流编程语言的 xdb 数据生成和查询客户端实现,可以说是非常得好用👍👍👍👍
2.1.1 高达 99.9 % 的查询准确率
数据聚合了一些知名 ip 到地名查询提供商的数据,这些是他们官方的准确率,经测试着实比经典的纯真 IP 定位准确一些。
ip2region 的数据聚合自以下服务商的开放 API 或者数据(升级程序每秒请求次数 2 到 4 次),比例如下:
80%, 淘宝 IP 地址库, ip.taobao.com/
≈10%, GeoIP, geoip.com/
≈2%, 纯真 IP 库, www.cz88.net/
2.1.2 Ip2region V2.0 特性
-
IP 数据管理框架
xdb 支持亿级别的 IP 数据段行数,默认的 region 信息都固定了格式:国家|区域|省份|城市|ISP,缺省的地域信息默认是0。
只有中国的数据精确到了城市,其他国家有部分数据只能定位到国家,后前的选项全部是 0,已经包含了全部你能查到的大大小小的国家
生成的数据库文件 ip2region.db 只有几 MB,最小的版本只有 1.5MB,随着数据的详细度增加数据库的大小也慢慢增大,目前还没超过 8MB。
region 信息支持完全自定义,例如:你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编等。也就是你完全可以使用 ip2region 来管理你自己的 IP 定位数据。
-
数据去重和压缩
xdb 格式生成程序会自动去重和压缩部分数据,默认的全部 IP 数据,生成的 ip2region.xdb 数据库是 11MiB,随着数据的详细度增加数据库的大小也慢慢增大。
-
极速查询响应
即使是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:
- vIndex 索引缓存:使用固定的 512KiB 的内存空间缓存 vector index 数据,减少一次 IO 磁盘操作,保持平均查询效率稳定在10-20微秒之间
-
xdb 整个文件缓存:将整个 xdb 文件全部加载到内存,内存占用等同于 xdb 文件大小,无磁盘 IO 操作,保持微秒级别的查询效率。
-
内置的三种查询算法
全部的查询客户端单次查询都在 0.x 毫秒级别,内置了三种查询算法:
-
memory 算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内,C语言的客户端单次查询在0.00x毫秒级别。
-
binary 算法:基于二分查找,基于ip2region.db文件,不需要载入内存,单次查询在0.x毫秒级别。
-
b-tree 算法:基于btree算法,基于ip2region.db文件,不需要载入内存,单词查询在0.x毫秒级别,比binary算法更快。
2.1.3 多语言以及查询客户端的支持
已经有的客户端:Java、C#、php、C、Python、Node.js、PHP 拓展(PHP 5 和 PHP 7)等,主要如下:
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.0x毫秒 | 0.0x毫秒 | 0.00x毫秒 |
python | python bindng | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.x毫秒 |
rust | rust binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.x毫秒 |
2.2 Ip2region xdb Java 查询客户端实现
这里简单展示一下 Java 的实现,这里使用开发中常用的 Maven 实现的方式:
2.2.1 引入 Maven 仓库
<!-- IP地址转归属地 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.6.4</version>
</dependency>
2.2.2 ip2region.xdb 文件,放到工程resources目录下
这个xdb文件我放到网盘里,大伙可以直接下载,或者直接去GitHub上访问下载。
百度网盘地址:ip2region.xdb
提取码:4399
2.2.3 实现方式
2.2.3.1 基于文件查询
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import java.util.concurrent.TimeUnit;
/**
* IP地址Util
*/
@Slf4j
public class IpAddressUtil {
// ip2region.xdb 文件地址常量(本地xdb文件路径)
public static String XDB_PATH = "D:\\IDEA2022.2.3\\workspace\\java\\src\\main\\resources\\ip\\ip2region.xdb";
/**
* 完全基于ip2region.xdb文件,对用户ip地址进行转换
* 注:并发调用时,每个线程需创建一个独立的searcher对象 单独使用。
*/
public static String getIpPossessionByFile(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、创建 searcher 对象
Searcher searcher = Searcher.newWithFileOnly(XDB_PATH);
// 2、查询
long sTime = System.nanoTime();
String region = searcher.search(ip);
long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
region = region.replace("|0", "");
log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("获取IP地址异常:{} ", e.getMessage());
throw new RuntimeException("获取IP地址异常");
}
}
return "未知";
}
}
2.2.3.2 缓存VectorIndex索引
我们可以提前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import java.util.concurrent.TimeUnit;
/**
* IP地址Util
*/
@Slf4j
public class IpAddressUtil {
// ip2region.xdb 文件地址常量(本地xdb文件路径)
public static String XDB_PATH = "D:\\IDEA2022.2.3\\workspace\\java\\src\\main\\resources\\ip\\ip2region.xdb";
/**
* 缓存 VectorIndex 索引,对用户ip地址进行转换
* 注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局变量 vIndex 缓存。
*/
public static String getCityInfoByVectorIndex(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、从 XDB_PATH 中预先加载 VectorIndex 缓存,并且作为全局变量,后续反复使用。
byte[] vIndex = Searcher.loadVectorIndexFromFile(XDB_PATH);
// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
Searcher searcher = Searcher.newWithVectorIndex(XDB_PATH, vIndex);
// 3、查询
long sTime = System.nanoTime();
String region = searcher.search(ip);
long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
region = region.replace("|0", "");
log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("获取IP地址异常:{} ", e.getMessage());
throw new RuntimeException("获取IP地址异常");
}
}
return "未知";
}
}
2.2.3.3 缓存整个 xdb 数据
我们也可以预先加载整个 ip2region.xdb 的数据到内存,然后基于这个数据创建查询对象来实现完全基于文件的查询,类似之前的 memory search。
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import java.util.concurrent.TimeUnit;
/**
* IP地址Util
*/
@Slf4j
public class IpAddressUtil {
// ip2region.xdb 文件地址常量
public static String XDB_PATH = "D:\\java\\src\\main\\resources\\ip\\ip2region.xdb";
/**
* 缓存整个 xdb 数据,对用户ip地址进行转换
* 注:并发使用时,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。
*/
public static String getCityInfoByMemorySearch(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、从 XDB_PATH 加载整个 xdb 到内存。
byte[] cBuff = Searcher.loadContentFromFile(XDB_PATH);
// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
Searcher searcher = Searcher.newWithBuffer(cBuff);
// 3、查询
long sTime = System.nanoTime();
String region = searcher.search(ip);
long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
region = region.replace("|0", "");
log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("获取IP地址异常:{} ", e.getMessage());
throw new RuntimeException("获取IP地址异常");
}
}
return "未知";
}
}
2.2.3.4 通过第三方API查询(在线查询)
前面介绍的3种方法都是离线查询,该方法主要通过第三方提供的官网或API接口去实现在线查询的功能,但有个弊端就是特别依赖对方的服务器,一旦对方的服务器宕机就无法访问了。具体实现效果跟之前介绍的离线查询方法是一样的。
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* IP地址Util
*/
@Slf4j
public class IpAddressUtil {
/**
* 在线查询IP归属地
*/
public static String getIpAddressByOnline(String ip) {
try {
//1、创建 URLConnction
URL url = new URL("http://ip-api.com/json/" + ip + "?lang=zh-CN");
//2、设置connection的属性
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(20000);
connection.setReadTimeout(20000);
connection.setRequestProperty("content-type", "application/json; charset=utf-8");
//3.连接
connection.connect();
//4.获取内容
InputStream inputStream = connection.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
//System.out.println(sb);
String str = sb.toString();
if (StringUtils.isNotEmpty(str)) {
// string转map
Gson gson = new Gson();
Map<String, Object> map = new HashMap<>();
map = gson.fromJson(str, map.getClass());
String country = (String) map.get("country");
String city = (String) map.get("city");
String regionName = (String) map.get("regionName");
System.out.println("国家:" + country);
System.out.println("城市:" + city);
System.out.println("地区:" + regionName);
return country + "|" + city + "|" + regionName;
}
} catch (Exception e) {
log.error("在线查询IP地址异常,{}", e.getMessage());
throw new RuntimeException(e.getMessage());
}
return null;
}
}
2.2.3.5 最优方案
其实我推荐可以将方法结合使用。先采用离线查询,如果发现地址为null的话,则调用在线查询方法。这样在一定的程度上能够保证数据的完整性。完整的工具类如下:
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* IP地址Util
*/
@Slf4j
public class IpAddressUtil {
// ip2region.xdb 文件地址常量(本地xdb文件路径)
public static String XDB_PATH = "D:\\IDEA2022.2.3\\workspace\\java\\src\\main\\resources\\ip\\ip2region.xdb";
/**
* 获取IP地址:
*/
public static String getIpAddress(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("X-Forwarded-For");
if (ipAddress != null && ipAddress.length() != 0 && !"unknown".equalsIgnoreCase(ipAddress)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ipAddress.contains(",")) {
ipAddress = ipAddress.split(",")[0];
}
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("HTTP_CLIENT_IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
}
} catch (Exception e) {
log.error("获取IP地址异常,{}", e.getMessage());
}
return ipAddress;
}
/**
* 获取mac地址
*/
public static String getMacIpAddress() {
try {
InetAddress inetAddress = InetAddress.getLocalHost();
byte[] macAddressBytes = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress();
// 将mac地址拼装成String
StringBuilder sb = new StringBuilder();
for (int i = 0; i < macAddressBytes.length; i++) {
if (i != 0) {
sb.append("-");
}
// mac[i] & 0xFF 是为了把byte转化为正整数
String s = Integer.toHexString(macAddressBytes[i] & 0xFF);
sb.append(s.length() == 1 ? 0 + s : s);
}
return sb.toString().trim().toUpperCase();
} catch (Exception e) {
log.error("Mac获取IP地址异常,{}", e.getMessage());
}
return "";
}
/**
* 方法一:完全基于ip2region.xdb文件,对用户ip地址进行转换
* 注:并发调用时,每个线程需创建一个独立的searcher对象 单独使用。
*/
public static String getIpPossessionByFile(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、创建 searcher 对象
Searcher searcher = Searcher.newWithFileOnly(XDB_PATH);
// 2、查询
long sTime = System.nanoTime();
String region = searcher.search(ip);
long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
region = region.replace("|0", "");
//log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("获取IP地址异常:{} ", e.getMessage());
throw new RuntimeException("获取IP地址异常");
}
}
return "未知";
}
/**
* 方法二:缓存 VectorIndex 索引,对用户ip地址进行转换
* 注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局变量 vIndex 缓存。
*/
public static String getCityInfoByVectorIndex(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、从 XDB_PATH 中预先加载 VectorIndex 缓存,并且作为全局变量,后续反复使用。
byte[] vIndex = Searcher.loadVectorIndexFromFile(XDB_PATH);
// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
Searcher searcher = Searcher.newWithVectorIndex(XDB_PATH, vIndex);
// 3、查询
long sTime = System.nanoTime();
String region = searcher.search(ip);
long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
region = region.replace("|0", "");
//log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("获取IP地址异常:{} ", e.getMessage());
throw new RuntimeException("获取IP地址异常");
}
}
return "未知";
}
/**
* 方法三:缓存整个 xdb 数据,对用户ip地址进行转换
* 注:并发使用时,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。
*/
public static String getCityInfoByMemorySearch(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、从 XDB_PATH 加载整个 xdb 到内存。
byte[] cBuff = Searcher.loadContentFromFile(XDB_PATH);
// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
Searcher searcher = Searcher.newWithBuffer(cBuff);
// 3、查询
long sTime = System.nanoTime();
String region = searcher.search(ip);
long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
region = region.replace("|0", "");
//log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("获取IP地址异常:{} ", e.getMessage());
throw new RuntimeException("获取IP地址异常");
}
}
return "未知";
}
/**
* 方法四:在线获取IP地址
* 注:通过别人或者官网提供的API接口去实现查询的功能,弊端就是特别依赖别人的服务器,一旦服务器宕机就无法访问了。
*/
public static String getIpAddressByOnline(String ip) {
try {
//1、创建 URLConnction
URL url = new URL("http://ip-api.com/json/" + ip + "?lang=zh-CN");
//2、设置connection的属性
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(20000);
connection.setReadTimeout(20000);
connection.setRequestProperty("content-type", "application/json; charset=utf-8");
//3.连接
connection.connect();
//4.获取内容
InputStream inputStream = connection.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
//System.out.println(sb);
String str = sb.toString();
if (StringUtils.isNotEmpty(str)) {
// string转map
Gson gson = new Gson();
Map<String, Object> map = new HashMap<>();
map = gson.fromJson(str, map.getClass());
String country = (String) map.get("country");
String city = (String) map.get("city");
String regionName = (String) map.get("regionName");
//log.info("【国家】{},【城市】{},【地区】{}", country, city, regionName);
return country + "|" + city + "|" + regionName;
}
} catch (Exception e) {
log.error("在线查询IP地址异常,{}", e.getMessage());
throw new RuntimeException("在线查询IP地址异常");
}
return null;
}
/**
* 根据IP地址 获取归属地
*/
public static String getIpPossession(String ipAddress) {
if (StringUtils.isNotEmpty(ipAddress)) {
ipAddress = ipAddress.replace("|", " ");
String[] cityList = ipAddress.split(" ");
if (cityList.length > 0) {
// 国内的显示到具体的省
if ("中国".equals(cityList[0])) {
if (cityList.length > 1) {
return cityList[1];
}
}
// 国外显示到国家
return cityList[0];
}
}
return "未知";
}
public static void main(String[] args) {
String ip = "183.162.252.0";// 国内IP
String abroadIp = "48.119.248.100"; // 国外IP
System.out.println("方法一(国内):" + getIpPossessionByFile(ip));
System.out.println("方法二(国内):" + getCityInfoByVectorIndex(ip));
System.out.println("方法三(国内):" + getCityInfoByMemorySearch(ip));
System.out.println("方法四(国内):" + getIpAddressByOnline(ip));
System.out.println("方法一(国外):" + getIpPossessionByFile(abroadIp));
System.out.println("方法二(国外):" + getCityInfoByVectorIndex(abroadIp));
System.out.println("方法三(国外):" + getCityInfoByMemorySearch(abroadIp));
System.out.println("方法四(国外):" + getIpAddressByOnline(abroadIp));
//System.out.println("归属地(国内):" + getIpPossession(getCityInfoByVectorIndex(ip)));
//System.out.println("归属地(国外):" + getIpPossession(getCityInfoByVectorIndex(abroadIp)));
}
}
如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。