在當(dāng)今互聯(lián)的世界中,了解客戶端的地理位置對于提供個性化服務(wù)和增強用戶體驗至關(guān)重要。無論是根據(jù)地區(qū)偏好定制內(nèi)容,還是確保符合本地法規(guī),訪問客戶端IP位置都是一項寶貴的資產(chǎn)。如抖音評論區(qū)、用戶頁都會展示用戶的IP屬地信息。
在本文中,我們將探討一個Spring Boot項目,它能夠高效地獲取客戶端IP地址的地理位置,并討論其應(yīng)用場景和實現(xiàn)方式。
項目開源地址
home_place
我已開源,點擊即可查看完整代碼實現(xiàn)。
項目概覽
該項目的結(jié)構(gòu)如下:
- common:包含一個ResultResponse類,用于統(tǒng)一處理響應(yīng)。
- rest:負(fù)責(zé)處理客戶端請求以獲取IP地理位置的控制層。
- service:實現(xiàn)業(yè)務(wù)邏輯,利用ip2region庫獲取IP位置信息。
- util:包含主要工具類IPUtils,用于從客戶端請求中獲取IP地理位置。
項目依賴
該項目利用了開源的ip2region庫,該庫提供了離線IP地址定位和數(shù)據(jù)管理的高效API。該庫具有微秒級的查詢效率,支持多種編程語言。您可以在這里找到ip2region庫的GitHub倉庫。
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.6.5</version>
</dependency>
使用方法
為了使用該項目,需下載ip2region.xdb文件并將其放置在服務(wù)器或本地機器上的合適位置。文件路徑在項目中配置如下:
private static final String DB_PATH = "/root/home_place/ip2region.xdb";
配置靈活,可使用YAML或其他配置文件進行修改。
請求處理
要獲取IP地理位置,使用javax.servlet.http.HttpServletRequest作為請求參數(shù)。調(diào)用IPUtils類的getIPRegion方法即可獲取IP位置信息:
String ipRegion = IPUtils.getIPRegion(request);
ThreadLocal的作用
ThreadLocal是Java中一個強大的工具,它提供了線程局部變量的支持。對于需要在多線程環(huán)境中保持獨立狀態(tài)的對象,ThreadLocal是一個理想的選擇。每個線程都可以通過ThreadLocal獲得自己的獨立副本,而不受其他線程的影響。
工具類
/**
* @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 歸屬地錯誤,錯誤原因: {}", 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在不同的線程中需要創(chuàng)建單獨的對象,因此我們使用ThreadLocal存儲,保證不同線程間的獨立性。
測試類
public static void main(String[] args) {
String ip = "192.168.31.1";
try {
// 1、創(chuàng)建 searcher 對象
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ā)使用,每個線程需要創(chuàng)建一個獨立的 searcher 對象單獨使用。
} catch (Exception e) {
log.error("IP:{}獲取IP歸屬地錯誤,錯誤原因:", ip, e);
}
}
響應(yīng)格式
API響應(yīng)和方法返回值的格式保持一致:
API響應(yīng):
{
"success": true,
"trace": "023c71f9-f483-466d-b650-a30fa097b64c",
"code": "OK",
"message": "獲取成功",
"data": "中國|0|山東省|青島市|移動"
}
方法返回值:
中國|0|山東省|青島市|移動
性能測試
該項目在以下條件下進行了性能評估:
- CPU:2核
- RAM:2GB
- 存儲:3MB
測試工具:ApiPost 7
并發(fā)數(shù):100
時長:10秒
致謝
該項目的功能要歸功于獅子的魂(@lionsoul)的開源貢獻,他的無私奉獻精神使我們能夠輕松地實現(xiàn)這一功能。正如俗話所說,我們都站在巨人的肩膀上。
這個基于Spring Boot的項目,結(jié)合強大的ip2region庫,為獲取客戶端IP地理位置提供了強大的解決方案。無論是定制內(nèi)容、確保地區(qū)合規(guī)性,還是分析用戶人口統(tǒng)計信息,將IP地理位置集成到您的應(yīng)用程序中都可以顯著增強其功能。隨時探索該項目,貢獻代碼,充分發(fā)揮IP地理位置在應(yīng)用程序中的威力。
后續(xù)內(nèi)容文章持續(xù)更新中…
近期發(fā)布。
關(guān)于我
????你好,我是Debug.c。微信公眾號:種顆代碼技術(shù)樹 的維護者,一個跨專業(yè)自學(xué)Java,對技術(shù)保持熱愛的bug猿,同樣也是在某二線城市打拼四年余的Java Coder。
??在掘金、CSDN、公眾號我將分享我最近學(xué)習(xí)的內(nèi)容、踩過的坑以及自己對技術(shù)的理解。
??如果您對我感興趣,請聯(lián)系我。文章來源:http://www.zghlxwxcb.cn/news/detail-828456.html
若有收獲,就點個贊吧,喜歡原圖請私信我。文章來源地址http://www.zghlxwxcb.cn/news/detail-828456.html
到了這里,關(guān)于利用Spring Boot實現(xiàn)客戶端IP地理位置獲取的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!