Java獲取IP地址及對應(yīng)的歸屬地的方法詳解
前言
細(xì)心的朋友們可能已經(jīng)發(fā)現(xiàn)了,先在抖音、知乎、快手、小紅書等這些平臺已經(jīng)上線了“網(wǎng)絡(luò)用戶顯示 IP 的功能”,境外用戶顯示的是國家,國內(nèi)的用戶顯示的省份,而且此項(xiàng)顯示無法關(guān)閉,歸屬地強(qiáng)制顯示。
作為一個(gè)努力搬磚的碼農(nóng),我們肯定要來看一下這個(gè)功能是如何實(shí)現(xiàn)的,今天這篇文章,就來講述一下這個(gè)功能是怎么實(shí)現(xiàn)的。
一、獲取訪問的IP地址
HttpServletRequest 獲取 IP
首先我們來看一下,在 Java 中,是如何獲取到 IP 屬地的,主要有以下兩步:
通過 HttpServletRequest 對象,獲取用戶的 【IP】 地址
通過 IP 地址,獲取對應(yīng)的【省份、城市】
我這里寫一個(gè)工具類用于獲取 IP 地址,因?yàn)橛脩舻拿看?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)) {
// 根據(jù)網(wǎng)卡取本機(jī)配置的 IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (Exception e) {
e.printStackTrace();
log.error("獲取IP地址異常,{}", e.getMessage());
}
if (inet != null) {
ip = inet.getHostAddress();
}
}
}
// 多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)IP按照','分割
if (ip != null && ip.length() > 15) {
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
// 本機(jī)訪問
if ("localhost".equalsIgnoreCase(ip) || "127.0.0.1".equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)) {
// 根據(jù)網(wǎng)卡取本機(jī)配置的IP
InetAddress inet;
try {
inet = InetAddress.getLocalHost();
ip = inet.getHostAddress();
} catch (Exception e) {
e.printStackTrace();
log.error("獲取本機(jī)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)) {
// 根據(jù)網(wǎng)卡取本機(jī)配置的IP
try {
InetAddress inet = InetAddress.getLocalHost();
ipAddress = inet.getHostAddress();
} catch (Exception e) {
log.error("獲取IP地址異常,{}", e.getMessage());
}
}
}
// 對于通過多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)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轉(zhuǎn)化為正整數(shù)
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 "";
}
}這里出現(xiàn)了三個(gè)名詞:
- X-Forwarded-For:一個(gè) HTTP 擴(kuò)展頭部,主要是為了讓 Web 服務(wù)器獲取訪問用戶的真實(shí) IP 地址。每個(gè) IP 地址,每個(gè)值通過逗號+空格分開,最左邊是最原始客戶端的 IP 地址,中間如果有多層代理,每?層代理會將連接它的客戶端 IP 追加在 X-Forwarded-For 右邊
- X-Real-IP:一般只記錄真實(shí)發(fā)出請求的客戶端IP
- Proxy-Client-IP:這個(gè)一般是經(jīng)過 Apache http 服務(wù)器的請求才會有,用 Apache http 做代理時(shí)一般會加上 Proxy-Client-IP 請求頭
- WL-Proxy-Client-IP:也是通過 Apache http 服務(wù)器,在 weblogic 插件加上的頭
二、通過IP地址獲取對應(yīng)的歸屬地
通過第三方地址庫 Ip2region,獲取IP歸屬地。
2.1 Ip2region
Ip2region 是一個(gè) Gthub 的開源項(xiàng)目,即 Ip2region 開源項(xiàng)目。
github地址:https://github.com/lionsoul2014/ip2region
這個(gè)開源庫目前已經(jīng)更新到了 V2 的版本,現(xiàn)在的它是一個(gè)強(qiáng)大的離線IP地址定位庫和IP定位數(shù)據(jù)管理框架,其達(dá)到了微秒級別的查詢效率,還提供了眾多主流編程語言的 xdb 數(shù)據(jù)生成和查詢客戶端實(shí)現(xiàn),可以說是非常得好用
2.1.1 高達(dá) 99.9 % 的查詢準(zhǔn)確率
數(shù)據(jù)聚合了一些知名 ip 到地名查詢提供商的數(shù)據(jù),這些是他們官方的準(zhǔn)確率,經(jīng)測試著實(shí)比經(jīng)典的純真 IP 定位準(zhǔn)確一些。
ip2region 的數(shù)據(jù)聚合自以下服務(wù)商的開放 API 或者數(shù)據(jù)(升級程序每秒請求次數(shù) 2 到 4 次),比例如下:
80%, 淘寶 IP 地址庫, ip.taobao.com/
≈10%, GeoIP, geoip.com/
≈2%, 純真 IP 庫, www.cz88.net/
2.1.2 Ip2region V2.0 特性
1.IP 數(shù)據(jù)管理框架
xdb 支持億級別的 IP 數(shù)據(jù)段行數(shù),默認(rèn)的 region 信息都固定了格式:國家|區(qū)域|省份|城市|ISP,缺省的地域信息默認(rèn)是0。
只有中國的數(shù)據(jù)精確到了城市,其他國家有部分?jǐn)?shù)據(jù)只能定位到國家,后前的選項(xiàng)全部是 0,已經(jīng)包含了全部你能查到的大大小小的國家
生成的數(shù)據(jù)庫文件 ip2region.db 只有幾 MB,最小的版本只有 1.5MB,隨著數(shù)據(jù)的詳細(xì)度增加數(shù)據(jù)庫的大小也慢慢增大,目前還沒超過 8MB。
region 信息支持完全自定義,例如:你可以在 region 中追加特定業(yè)務(wù)需求的數(shù)據(jù),例如:GPS信息/國際統(tǒng)一地域信息編碼/郵編等。也就是你完全可以使用 ip2region 來管理你自己的 IP 定位數(shù)據(jù)。
2.數(shù)據(jù)去重和壓縮
xdb 格式生成程序會自動去重和壓縮部分?jǐn)?shù)據(jù),默認(rèn)的全部 IP 數(shù)據(jù),生成的 ip2region.xdb 數(shù)據(jù)庫是 11MiB,隨著數(shù)據(jù)的詳細(xì)度增加數(shù)據(jù)庫的大小也慢慢增大。
3.極速查詢響應(yīng)
即使是完全基于 xdb 文件的查詢,單次查詢響應(yīng)時(shí)間在十微秒級別,可通過如下兩種方式開啟內(nèi)存加速查詢:
- vIndex 索引緩存:使用固定的 512KiB 的內(nèi)存空間緩存 vector index 數(shù)據(jù),減少一次 IO 磁盤操作,保持平均查詢效率穩(wěn)定在10-20微秒之間
- xdb 整個(gè)文件緩存:將整個(gè) xdb 文件全部加載到內(nèi)存,內(nèi)存占用等同于 xdb 文件大小,無磁盤 IO 操作,保持微秒級別的查詢效率。
4.內(nèi)置的三種查詢算法
全部的查詢客戶端單次查詢都在 0.x 毫秒級別,內(nèi)置了三種查詢算法:
- memory 算法:整個(gè)數(shù)據(jù)庫全部載入內(nèi)存,單次查詢都在0.1x毫秒內(nèi),C語言的客戶端單次查詢在0.00x毫秒級別。
- binary 算法:基于二分查找,基于ip2region.db文件,不需要載入內(nèi)存,單次查詢在0.x毫秒級別。
- b-tree 算法:基于btree算法,基于ip2region.db文件,不需要載入內(nèi)存,單詞查詢在0.x毫秒級別,比binary算法更快。
2.1.3 多語言以及查詢客戶端的支持
已經(jīng)有的客戶端:Java、C#、php、C、Python、Node.js、PHP 拓展(PHP 5 和 PHP 7)等,主要如下:

2.2 Ip2region xdb Java 查詢客戶端實(shí)現(xiàn)
這里簡單展示一下 Java 的實(shí)現(xiàn),這里使用開發(fā)中常用的 Maven 實(shí)現(xiàn)的方式:
2.2.1 引入 Maven 倉庫
<!-- IP地址轉(zhuǎn)歸屬地 --> <dependency> <groupId>org.lionsoul</groupId> <artifactId>ip2region</artifactId> <version>2.6.4</version> </dependency>
2.2.2 ip2region.xdb 文件,放到工程resources目錄下

2.2.3 實(shí)現(xiàn)方式
基于文件查詢
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地址進(jìn)行轉(zhuǎn)換
* 注:并發(fā)調(diào)用時(shí),每個(gè)線程需創(chuàng)建一個(gè)獨(dú)立的searcher對象 單獨(dú)使用。
*/
public static String getIpPossessionByFile(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、創(chuàng)建 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("{地區(qū): {}, IO操作數(shù): {}, 耗時(shí): {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("獲取IP地址異常:{} ", e.getMessage());
throw new RuntimeException("獲取IP地址異常");
}
}
return "未知";
}
}緩存VectorIndex索引
我們可以提前從 xdb 文件中加載出來 VectorIndex 數(shù)據(jù),然后全局緩存,每次創(chuàng)建 Searcher 對象的時(shí)候使用全局的 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地址進(jìn)行轉(zhuǎn)換
* 注:每個(gè)線程需要單獨(dú)創(chuàng)建一個(gè)獨(dú)立的 Searcher 對象,但是都共享全局變量 vIndex 緩存。
*/
public static String getCityInfoByVectorIndex(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、從 XDB_PATH 中預(yù)先加載 VectorIndex 緩存,并且作為全局變量,后續(xù)反復(fù)使用。
byte[] vIndex = Searcher.loadVectorIndexFromFile(XDB_PATH);
// 2、使用全局的 vIndex 創(chuàng)建帶 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("{地區(qū): {}, IO操作數(shù): {}, 耗時(shí): {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("獲取IP地址異常:{} ", e.getMessage());
throw new RuntimeException("獲取IP地址異常");
}
}
return "未知";
}
}緩存整個(gè) xdb 數(shù)據(jù)
我們也可以預(yù)先加載整個(gè) ip2region.xdb 的數(shù)據(jù)到內(nèi)存,然后基于這個(gè)數(shù)據(jù)創(chuàng)建查詢對象來實(shí)現(xiàn)完全基于文件的查詢,類似之前的 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";
/**
* 緩存整個(gè) xdb 數(shù)據(jù),對用戶ip地址進(jìn)行轉(zhuǎn)換
* 注:并發(fā)使用時(shí),用整個(gè) xdb 數(shù)據(jù)緩存創(chuàng)建的查詢對象可以安全的用于并發(fā),也就是你可以把這個(gè) searcher 對象做成全局對象去跨線程訪問。
*/
public static String getCityInfoByMemorySearch(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、從 XDB_PATH 加載整個(gè) xdb 到內(nèi)存。
byte[] cBuff = Searcher.loadContentFromFile(XDB_PATH);
// 2、使用上述的 cBuff 創(chuàng)建一個(gè)完全基于內(nèi)存的查詢對象。
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("{地區(qū): {}, IO操作數(shù): {}, 耗時(shí): {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("獲取IP地址異常:{} ", e.getMessage());
throw new RuntimeException("獲取IP地址異常");
}
}
return "未知";
}
}通過第三方API查詢(在線查詢)
前面介紹的3種方法都是離線查詢,該方法主要通過第三方提供的官網(wǎng)或API接口去實(shí)現(xiàn)在線查詢的功能,但有個(gè)弊端就是特別依賴對方的服務(wù)器,一旦對方的服務(wù)器宕機(jī)就無法訪問了。具體實(shí)現(xiàn)效果跟之前介紹的離線查詢方法是一樣的。
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、創(chuàng)建 URLConnction
URL url = new URL("http://ip-api.com/json/" + ip + "?lang=zh-CN");
//2、設(shè)置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.獲取內(nèi)容
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轉(zhuǎn)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("地區(qū):" + regionName);
return country + "|" + city + "|" + regionName;
}
} catch (Exception e) {
log.error("在線查詢IP地址異常,{}", e.getMessage());
throw new RuntimeException(e.getMessage());
}
return null;
}
}最優(yōu)方案
其實(shí)我推薦可以將方法結(jié)合使用。先采用離線查詢,如果發(fā)現(xiàn)地址為null的話,則調(diào)用在線查詢方法。這樣在一定的程度上能夠保證數(shù)據(jù)的完整性。完整的工具類如下:
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)) {
// 多次反向代理后會有多個(gè)ip值,第一個(gè)ip才是真實(shí)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轉(zhuǎn)化為正整數(shù)
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地址進(jìn)行轉(zhuǎn)換
* 注:并發(fā)調(diào)用時(shí),每個(gè)線程需創(chuàng)建一個(gè)獨(dú)立的searcher對象 單獨(dú)使用。
*/
public static String getIpPossessionByFile(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、創(chuàng)建 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("{地區(qū): {}, IO操作數(shù): {}, 耗時(shí): {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("獲取IP地址異常:{} ", e.getMessage());
throw new RuntimeException("獲取IP地址異常");
}
}
return "未知";
}
/**
* 方法二:緩存 VectorIndex 索引,對用戶ip地址進(jìn)行轉(zhuǎn)換
* 注:每個(gè)線程需要單獨(dú)創(chuàng)建一個(gè)獨(dú)立的 Searcher 對象,但是都共享全局變量 vIndex 緩存。
*/
public static String getCityInfoByVectorIndex(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、從 XDB_PATH 中預(yù)先加載 VectorIndex 緩存,并且作為全局變量,后續(xù)反復(fù)使用。
byte[] vIndex = Searcher.loadVectorIndexFromFile(XDB_PATH);
// 2、使用全局的 vIndex 創(chuàng)建帶 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("{地區(qū): {}, IO操作數(shù): {}, 耗時(shí): {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("獲取IP地址異常:{} ", e.getMessage());
throw new RuntimeException("獲取IP地址異常");
}
}
return "未知";
}
/**
* 方法三:緩存整個(gè) xdb 數(shù)據(jù),對用戶ip地址進(jìn)行轉(zhuǎn)換
* 注:并發(fā)使用時(shí),用整個(gè) xdb 數(shù)據(jù)緩存創(chuàng)建的查詢對象可以安全的用于并發(fā),也就是你可以把這個(gè) searcher 對象做成全局對象去跨線程訪問。
*/
public static String getCityInfoByMemorySearch(String ip) {
if (StringUtils.isNotEmpty(ip)) {
try {
// 1、從 XDB_PATH 加載整個(gè) xdb 到內(nèi)存。
byte[] cBuff = Searcher.loadContentFromFile(XDB_PATH);
// 2、使用上述的 cBuff 創(chuàng)建一個(gè)完全基于內(nèi)存的查詢對象。
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("{地區(qū): {}, IO操作數(shù): {}, 耗時(shí): {} μs}", region, searcher.getIOCount(), cost);
return region;
} catch (Exception e) {
log.error("獲取IP地址異常:{} ", e.getMessage());
throw new RuntimeException("獲取IP地址異常");
}
}
return "未知";
}
/**
* 方法四:在線獲取IP地址
* 注:通過別人或者官網(wǎng)提供的API接口去實(shí)現(xiàn)查詢的功能,弊端就是特別依賴別人的服務(wù)器,一旦服務(wù)器宕機(jī)就無法訪問了。
*/
public static String getIpAddressByOnline(String ip) {
try {
//1、創(chuàng)建 URLConnction
URL url = new URL("http://ip-api.com/json/" + ip + "?lang=zh-CN");
//2、設(shè)置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.獲取內(nèi)容
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轉(zhuǎn)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("【國家】{},【城市】{},【地區(qū)】{}", country, city, regionName);
return country + "|" + city + "|" + regionName;
}
} catch (Exception e) {
log.error("在線查詢IP地址異常,{}", e.getMessage());
throw new RuntimeException("在線查詢IP地址異常");
}
return null;
}
/**
* 根據(jù)IP地址 獲取歸屬地
*/
public static String getIpPossession(String ipAddress) {
if (StringUtils.isNotEmpty(ipAddress)) {
ipAddress = ipAddress.replace("|", " ");
String[] cityList = ipAddress.split(" ");
if (cityList.length > 0) {
// 國內(nèi)的顯示到具體的省
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";// 國內(nèi)IP
String abroadIp = "48.119.248.100"; // 國外IP
System.out.println("方法一(國內(nèi)):" + getIpPossessionByFile(ip));
System.out.println("方法二(國內(nèi)):" + getCityInfoByVectorIndex(ip));
System.out.println("方法三(國內(nèi)):" + getCityInfoByMemorySearch(ip));
System.out.println("方法四(國內(nèi)):" + 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("歸屬地(國內(nèi)):" + getIpPossession(getCityInfoByVectorIndex(ip)));
//System.out.println("歸屬地(國外):" + getIpPossession(getCityInfoByVectorIndex(abroadIp)));
}
}以上就是Java獲取IP地址及對應(yīng)的歸屬地的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Java獲取IP地址和歸屬地的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
后端返回各種圖片形式在前端的轉(zhuǎn)換及展示方法對比
這篇文章主要給大家介紹了關(guān)于后端返回各種圖片形式在前端的轉(zhuǎn)換及展示方法對比的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-06-06
SpringMVC中ModelAndView用法小結(jié)
本文主要介紹了SpringMVC中ModelAndView用法小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12
JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法
這篇文章主要介紹了JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2016-10-10
SpringBoot Entity中枚舉類型詳細(xì)使用介紹
本文介紹SpringBoot如何在Entity(DAO)中使用枚舉類型。(本文使用MyBatis-Plus)。在實(shí)際開發(fā)中,經(jīng)常會遇到表示類型或者狀態(tài)的情況,比如:有三種支付方式:微信、支付寶、銀聯(lián)。本文介紹如何這種場景的方案對比,并用實(shí)例來介紹如何用枚舉這種最優(yōu)雅的來表示2022-10-10
gateway與spring-boot-starter-web沖突問題的解決
這篇文章主要介紹了gateway與spring-boot-starter-web沖突問題的解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
SpringBoot如何實(shí)現(xiàn)緩存預(yù)熱
緩存預(yù)熱是指在 Spring Boot 項(xiàng)目啟動時(shí),預(yù)先將數(shù)據(jù)加載到緩存系統(tǒng)(如 Redis)中的一種機(jī)制,本文主要介紹了SpringBoot如何實(shí)現(xiàn)緩存預(yù)熱,感興趣的可以了解下2024-12-12

