Java獲取IP地址及對應(yīng)的歸屬地的方法詳解
前言
細(xì)心的朋友們可能已經(jīng)發(fā)現(xiàn)了,先在抖音、知乎、快手、小紅書等這些平臺已經(jīng)上線了“網(wǎng)絡(luò)用戶顯示 IP 的功能”,境外用戶顯示的是國家,國內(nèi)的用戶顯示的省份,而且此項顯示無法關(guān)閉,歸屬地強制顯示。
作為一個努力搬磚的碼農(nóng),我們肯定要來看一下這個功能是如何實現(xiàn)的,今天這篇文章,就來講述一下這個功能是怎么實現(xiàn)的。
一、獲取訪問的IP地址
HttpServletRequest 獲取 IP
首先我們來看一下,在 Java 中,是如何獲取到 IP 屬地的,主要有以下兩步:
通過 HttpServletRequest 對象,獲取用戶的 【IP】 地址
通過 IP 地址,獲取對應(yīng)的【省份、城市】
我這里寫一個工具類用于獲取 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)) { // 根據(jù)網(wǎng)卡取本機配置的 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)) { // 根據(jù)網(wǎng)卡取本機配置的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)) { // 根據(jù)網(wǎng)卡取本機配置的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轉(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)了三個名詞:
- X-Forwarded-For:一個 HTTP 擴展頭部,主要是為了讓 Web 服務(wù)器獲取訪問用戶的真實 IP 地址。每個 IP 地址,每個值通過逗號+空格分開,最左邊是最原始客戶端的 IP 地址,中間如果有多層代理,每?層代理會將連接它的客戶端 IP 追加在 X-Forwarded-For 右邊
- X-Real-IP:一般只記錄真實發(fā)出請求的客戶端IP
- Proxy-Client-IP:這個一般是經(jīng)過 Apache http 服務(wù)器的請求才會有,用 Apache http 做代理時一般會加上 Proxy-Client-IP 請求頭
- WL-Proxy-Client-IP:也是通過 Apache http 服務(wù)器,在 weblogic 插件加上的頭
二、通過IP地址獲取對應(yīng)的歸屬地
通過第三方地址庫 Ip2region,獲取IP歸屬地。
2.1 Ip2region
Ip2region 是一個 Gthub 的開源項目,即 Ip2region 開源項目。
github地址:https://github.com/lionsoul2014/ip2region
這個開源庫目前已經(jīng)更新到了 V2 的版本,現(xiàn)在的它是一個強大的離線IP地址定位庫和IP定位數(shù)據(jù)管理框架,其達到了微秒級別的查詢效率,還提供了眾多主流編程語言的 xdb 數(shù)據(jù)生成和查詢客戶端實現(xiàn),可以說是非常得好用
2.1.1 高達 99.9 % 的查詢準(zhǔn)確率
數(shù)據(jù)聚合了一些知名 ip 到地名查詢提供商的數(shù)據(jù),這些是他們官方的準(zhǔn)確率,經(jīng)測試著實比經(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ù)只能定位到國家,后前的選項全部是 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)時間在十微秒級別,可通過如下兩種方式開啟內(nèi)存加速查詢:
- vIndex 索引緩存:使用固定的 512KiB 的內(nèi)存空間緩存 vector index 數(shù)據(jù),減少一次 IO 磁盤操作,保持平均查詢效率穩(wěn)定在10-20微秒之間
- xdb 整個文件緩存:將整個 xdb 文件全部加載到內(nèi)存,內(nèi)存占用等同于 xdb 文件大小,無磁盤 IO 操作,保持微秒級別的查詢效率。
4.內(nèi)置的三種查詢算法
全部的查詢客戶端單次查詢都在 0.x 毫秒級別,內(nèi)置了三種查詢算法:
- memory 算法:整個數(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 查詢客戶端實現(xiàn)
這里簡單展示一下 Java 的實現(xiàn),這里使用開發(fā)中常用的 Maven 實現(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 實現(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地址進行轉(zhuǎn)換 * 注:并發(fā)調(diào)用時,每個線程需創(chuàng)建一個獨立的searcher對象 單獨使用。 */ 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ù): {}, 耗時: {} μ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 對象的時候使用全局的 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地址進行轉(zhuǎn)換 * 注:每個線程需要單獨創(chuàng)建一個獨立的 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ù): {}, 耗時: {} μs}", region, searcher.getIOCount(), cost); return region; } catch (Exception e) { log.error("獲取IP地址異常:{} ", e.getMessage()); throw new RuntimeException("獲取IP地址異常"); } } return "未知"; } }
緩存整個 xdb 數(shù)據(jù)
我們也可以預(yù)先加載整個 ip2region.xdb 的數(shù)據(jù)到內(nèi)存,然后基于這個數(shù)據(jù)創(chuàng)建查詢對象來實現(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"; /** * 緩存整個 xdb 數(shù)據(jù),對用戶ip地址進行轉(zhuǎn)換 * 注:并發(fā)使用時,用整個 xdb 數(shù)據(jù)緩存創(chuàng)建的查詢對象可以安全的用于并發(fā),也就是你可以把這個 searcher 對象做成全局對象去跨線程訪問。 */ public static String getCityInfoByMemorySearch(String ip) { if (StringUtils.isNotEmpty(ip)) { try { // 1、從 XDB_PATH 加載整個 xdb 到內(nèi)存。 byte[] cBuff = Searcher.loadContentFromFile(XDB_PATH); // 2、使用上述的 cBuff 創(chuàng)建一個完全基于內(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ù): {}, 耗時: {} μ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接口去實現(xiàn)在線查詢的功能,但有個弊端就是特別依賴對方的服務(wù)器,一旦對方的服務(wù)器宕機就無法訪問了。具體實現(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)方案
其實我推薦可以將方法結(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)) { // 多次反向代理后會有多個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轉(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地址進行轉(zhuǎn)換 * 注:并發(fā)調(diào)用時,每個線程需創(chuàng)建一個獨立的searcher對象 單獨使用。 */ 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ù): {}, 耗時: {} μs}", region, searcher.getIOCount(), cost); return region; } catch (Exception e) { log.error("獲取IP地址異常:{} ", e.getMessage()); throw new RuntimeException("獲取IP地址異常"); } } return "未知"; } /** * 方法二:緩存 VectorIndex 索引,對用戶ip地址進行轉(zhuǎn)換 * 注:每個線程需要單獨創(chuàng)建一個獨立的 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ù): {}, 耗時: {} μs}", region, searcher.getIOCount(), cost); return region; } catch (Exception e) { log.error("獲取IP地址異常:{} ", e.getMessage()); throw new RuntimeException("獲取IP地址異常"); } } return "未知"; } /** * 方法三:緩存整個 xdb 數(shù)據(jù),對用戶ip地址進行轉(zhuǎn)換 * 注:并發(fā)使用時,用整個 xdb 數(shù)據(jù)緩存創(chuàng)建的查詢對象可以安全的用于并發(fā),也就是你可以把這個 searcher 對象做成全局對象去跨線程訪問。 */ public static String getCityInfoByMemorySearch(String ip) { if (StringUtils.isNotEmpty(ip)) { try { // 1、從 XDB_PATH 加載整個 xdb 到內(nèi)存。 byte[] cBuff = Searcher.loadContentFromFile(XDB_PATH); // 2、使用上述的 cBuff 創(chuàng)建一個完全基于內(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ù): {}, 耗時: {} μs}", region, searcher.getIOCount(), cost); return region; } catch (Exception e) { log.error("獲取IP地址異常:{} ", e.getMessage()); throw new RuntimeException("獲取IP地址異常"); } } return "未知"; } /** * 方法四:在線獲取IP地址 * 注:通過別人或者官網(wǎng)提供的API接口去實現(xiàn)查詢的功能,弊端就是特別依賴別人的服務(wù)器,一旦服務(wù)器宕機就無法訪問了。 */ 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í)價值,需要的朋友可以參考下2023-06-06SpringMVC中ModelAndView用法小結(jié)
本文主要介紹了SpringMVC中ModelAndView用法小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12SpringBoot Entity中枚舉類型詳細(xì)使用介紹
本文介紹SpringBoot如何在Entity(DAO)中使用枚舉類型。(本文使用MyBatis-Plus)。在實際開發(fā)中,經(jīng)常會遇到表示類型或者狀態(tài)的情況,比如:有三種支付方式:微信、支付寶、銀聯(lián)。本文介紹如何這種場景的方案對比,并用實例來介紹如何用枚舉這種最優(yōu)雅的來表示2022-10-10gateway與spring-boot-starter-web沖突問題的解決
這篇文章主要介紹了gateway與spring-boot-starter-web沖突問題的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07