SpringBoot實(shí)現(xiàn)獲取客戶端IP地理位置
在當(dāng)今互聯(lián)的世界中,了解客戶端的地理位置對(duì)于提供個(gè)性化服務(wù)和增強(qiáng)用戶體驗(yàn)至關(guān)重要。無(wú)論是根據(jù)地區(qū)偏好定制內(nèi)容,還是確保符合本地法規(guī),訪問(wèn)客戶端IP位置都是一項(xiàng)寶貴的資產(chǎn)。如抖音評(píng)論區(qū)、用戶頁(yè)都會(huì)展示用戶的IP屬地信息。
在本文中,我們將探討一個(gè)Spring Boot項(xiàng)目,它能夠高效地獲取客戶端IP地址的地理位置,并討論其應(yīng)用場(chǎng)景和實(shí)現(xiàn)方式。
項(xiàng)目開源地址
我已開源,點(diǎn)擊即可查看完整代碼實(shí)現(xiàn)。
項(xiàng)目概覽
該項(xiàng)目的結(jié)構(gòu)如下:
- common:包含一個(gè)ResultResponse類,用于統(tǒng)一處理響應(yīng)。
- rest:負(fù)責(zé)處理客戶端請(qǐng)求以獲取IP地理位置的控制層。
- service:實(shí)現(xiàn)業(yè)務(wù)邏輯,利用ip2region庫(kù)獲取IP位置信息。
- util:包含主要工具類IPUtils,用于從客戶端請(qǐng)求中獲取IP地理位置。
項(xiàng)目依賴
該項(xiàng)目利用了開源的ip2region庫(kù),該庫(kù)提供了離線IP地址定位和數(shù)據(jù)管理的高效API。該庫(kù)具有微秒級(jí)的查詢效率,支持多種編程語(yǔ)言。您可以在這里找到ip2region庫(kù)的GitHub倉(cāng)庫(kù)。
<dependency> <groupId>org.lionsoul</groupId> <artifactId>ip2region</artifactId> <version>2.6.5</version> </dependency>
使用方法
為了使用該項(xiàng)目,需下載ip2region.xdb文件并將其放置在服務(wù)器或本地機(jī)器上的合適位置。文件路徑在項(xiàng)目中配置如下:
private static final String DB_PATH = "/root/home_place/ip2region.xdb";
配置靈活,可使用YAML或其他配置文件進(jìn)行修改。
請(qǐng)求處理
要獲取IP地理位置,使用javax.servlet.http.HttpServletRequest作為請(qǐng)求參數(shù)。調(diào)用IPUtils類的getIPRegion方法即可獲取IP位置信息:
String ipRegion = IPUtils.getIPRegion(request);
ThreadLocal的作用
ThreadLocal是Java中一個(gè)強(qiáng)大的工具,它提供了線程局部變量的支持。對(duì)于需要在多線程環(huán)境中保持獨(dú)立狀態(tài)的對(duì)象,ThreadLocal是一個(gè)理想的選擇。每個(gè)線程都可以通過(guò)ThreadLocal獲得自己的獨(dú)立副本,而不受其他線程的影響。
工具類
/** * @author Liutx * @since 2023-11-28 10:05 */ public class IPUtils { private static final Logger log = LogManager.getLogger(IPUtils.class); private static final String DB_PATH = "/root/home_place/ip2region.xdb"; private static final ThreadLocal<Searcher> searcherThreadLocal = ThreadLocal.withInitial(() -> { try { return Searcher.newWithFileOnly(DB_PATH); } catch (Exception e) { log.error("初始化 IP 歸屬地查詢失敗: {}", e.getMessage()); return null; } }); public static String getIPRegion(HttpServletRequest request) { String ip = getIPAddress(request); Searcher searcher = searcherThreadLocal.get(); if (searcher == null) { log.error("IP 歸屬地查詢失敗,返回空"); return null; } try { long startTime = System.nanoTime(); String region = searcher.search(ip); long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - startTime); log.info("IP: {}, Region: {}, IO Count: {}, Took: {} μs", ip, region, searcher.getIOCount(), cost); return region; } catch (Exception e) { log.error("IP: {} 獲取 IP 歸屬地錯(cuò)誤,錯(cuò)誤原因: {}", ip, e.getMessage()); return null; } finally { closeSearcher(); } } private static String getIPAddress(HttpServletRequest request) { String ipAddress = request.getHeader("X-Forwarded-For"); if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("HTTP_CLIENT_IP"); } if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); } return ipAddress; } public static void closeSearcher() { try { Searcher searcher = searcherThreadLocal.get(); if (Objects.nonNull(searcher)) { searcher.close(); searcherThreadLocal.remove(); } } catch (Exception e) { log.error("關(guān)閉異常", e); } } }
Searcher在不同的線程中需要?jiǎng)?chuàng)建單獨(dú)的對(duì)象,因此我們使用ThreadLocal存儲(chǔ),保證不同線程間的獨(dú)立性。
測(cè)試類
public static void main(String[] args) { String ip = "192.168.31.1"; try { // 1、創(chuàng)建 searcher 對(duì)象 String dbPath = "src/main/resources/ipdata/ip2region.xdb"; Searcher searcher = null; searcher = Searcher.newWithFileOnly(dbPath); // 2、查詢 long sTime = System.nanoTime(); String region = searcher.search(ip); long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime)); log.info("{region: {}, ioCount: {}, took: {} μs}", region, searcher.getIOCount(), cost); // 3、關(guān)閉資源 searcher.close(); // 備注:并發(fā)使用,每個(gè)線程需要?jiǎng)?chuàng)建一個(gè)獨(dú)立的 searcher 對(duì)象單獨(dú)使用。 } catch (Exception e) { log.error("IP:{}獲取IP歸屬地錯(cuò)誤,錯(cuò)誤原因:", ip, e); } }
響應(yīng)格式
API響應(yīng)和方法返回值的格式保持一致:
API響應(yīng):
{ "success": true, "trace": "023c71f9-f483-466d-b650-a30fa097b64c", "code": "OK", "message": "獲取成功", "data": "中國(guó)|0|山東省|青島市|移動(dòng)" }
方法返回值:
中國(guó)|0|山東省|青島市|移動(dòng)
性能測(cè)試
該項(xiàng)目在以下條件下進(jìn)行了性能評(píng)估:
- CPU:2核
- RAM:2GB
- 存儲(chǔ):3MB
測(cè)試工具:ApiPost 7
并發(fā)數(shù):100
時(shí)長(zhǎng):10秒
總結(jié)
這個(gè)基于Spring Boot的項(xiàng)目,結(jié)合強(qiáng)大的ip2region庫(kù),為獲取客戶端IP地理位置提供了強(qiáng)大的解決方案。無(wú)論是定制內(nèi)容、確保地區(qū)合規(guī)性,還是分析用戶人口統(tǒng)計(jì)信息,將IP地理位置集成到您的應(yīng)用程序中都可以顯著增強(qiáng)其功能。隨時(shí)探索該項(xiàng)目,貢獻(xiàn)代碼,充分發(fā)揮IP地理位置在應(yīng)用程序中的威力。
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)獲取客戶端IP地理位置的文章就介紹到這了,更多相關(guān)SpringBoot獲取地理位置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot異步@Async的使用及失效場(chǎng)景介紹
本文主要介紹了springboot異步@Async的使用及失效場(chǎng)景介紹,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12使用Java和高德地圖API將經(jīng)緯度轉(zhuǎn)換為地理位置信息的步驟
這篇文章詳細(xì)介紹了如何將GPS坐標(biāo)轉(zhuǎn)換為人類可讀的地理位置,介紹了環(huán)境準(zhǔn)備、代碼實(shí)現(xiàn)、異常處理及優(yōu)化步驟,首先創(chuàng)建LocationFinder類,實(shí)現(xiàn)getLocationFromCoordinates方法,利用高德逆地理編碼API轉(zhuǎn)換坐標(biāo),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09使用Java的Graphics類進(jìn)行繪圖的方法詳解
這篇文章主要介紹了使用Java的Graphics類進(jìn)行繪圖的方法,是Java的GUI編程的基礎(chǔ),需要的朋友可以參考下2015-10-10Spring之InitializingBean接口和DisposableBean接口的使用
這篇文章主要介紹了Spring之InitializingBean接口和DisposableBean接口的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01SpringCloud之微服務(wù)容錯(cuò)的實(shí)現(xiàn)
這篇文章主要介紹了SpringCloud之微服務(wù)容錯(cuò)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05