Java基于ip2region.xdb數(shù)據(jù)庫從IP獲取到屬地解析的全攻略
前言
在日常的Java開發(fā)中,IP地址相關的操作是非常常見的需求。無論是用戶行為分析、地域化服務,還是安全防護,都需要精準地獲取和解析IP信息。本文將詳細介紹一個功能完善的Java IP工具類,涵蓋IP地址獲取、MAC地址獲取以及IP屬地解析三大核心功能,幫助你在項目中快速落地這些能力。
一、工具類概述
1.1 核心功能
該IP工具類提供了三個核心功能模塊:
- IP地址獲取:支持多種代理場景下的客戶端IP提取,能夠穿透多層代理鏈獲取真實客戶端IP
- MAC地址獲取:通過Java原生API獲取本地網(wǎng)絡接口的MAC地址
- IP屬地解析:基于ip2region.xdb數(shù)據(jù)庫,實現(xiàn)毫秒級的IP地理位置查詢
1.2 應用場景
這些功能在實際項目中有著廣泛的應用場景:
- 用戶行為分析:通過IP屬地信息,統(tǒng)計用戶的地域分布,為產(chǎn)品決策提供數(shù)據(jù)支持
- 地域化服務:根據(jù)用戶所在省份/城市,提供個性化的內容推薦或服務
- 安全防護:識別異常IP訪問,進行地域限制或風險預警
- 日志審計:在關鍵操作日志中記錄IP和屬地信息,便于問題追溯
1.3 技術亮點
該工具類的技術亮點主要體現(xiàn)在:
- 多場景IP獲取策略:內置HTTP頭參數(shù)優(yōu)先級邏輯,能夠處理各種代理和負載均衡場景
- 高效內存緩存:ip2region.xdb數(shù)據(jù)庫采用內存緩存模式(
Searcher.newWithBuffer),查詢性能達到微秒級 - 完善的異常處理:針對不同場景設計了合理的異常捕獲和處理機制
二、核心功能詳解
2.1 IP地址獲?。╣etIpAddr)
在真實的生產(chǎn)環(huán)境中,獲取客戶端IP并非想象中那么簡單。用戶請求可能經(jīng)過多層代理、CDN、負載均衡器,因此需要從多個HTTP頭中提取真實IP。
HTTP頭參數(shù)優(yōu)先級順序
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
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("X-Real-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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
優(yōu)先級說明:
- X-Forwarded-For:Squid等代理服務器的標準,最為常用
- Proxy-Client-IP:Apache+Mod_Proxy場景
- X-Real-IP:Nginx反向代理常用
- WL-Proxy-Client-IP:WebLogic代理場景
- HTTP_CLIENT_IP:部分代理服務器使用
- HTTP_X_FORWARDED_FOR:部分特殊代理場景
- getRemoteAddr():直接獲取,作為兜底方案
代理場景下的IP提取邏輯
在實際場景中,X-Forwarded-For的值可能是多個IP的列表,格式為:client, proxy1, proxy2。其中第一個IP才是真實客戶端IP。
增強版的IP提取方法:
public static String getRealIp(HttpServletRequest request) {
String ip = getIpAddr(request);
// 處理多個IP的情況(逗號分隔)
if (ip != null && ip.contains(",")) {
String[] ips = ip.split(",");
ip = ips[0].trim(); // 取第一個IP(真實客戶端IP)
}
// 移除IPv6的端口信息
if (ip != null && ip.contains(":") && ip.indexOf(":") < ip.lastIndexOf(":")) {
ip = ip.substring(0, ip.lastIndexOf(":"));
}
return ip;
}
關鍵點解析:
- 逗號分隔處理:
X-Forwarded-For可能包含代理鏈中的所有IP,必須提取第一個 - IPv6地址兼容:將IPv6的本地回環(huán)地址
0:0:0:0:0:0:0:1轉換為127.0.0.1,便于統(tǒng)一處理 - 端口剝離:某些代理可能返回帶端口的IP格式,需要進行清理
2.2 MAC地址獲?。╣etMacAddress)
MAC地址是網(wǎng)絡接口的唯一標識,在某些場景下(如設備綁定、局域網(wǎng)通信)需要獲取。
NetworkInterface和InetAddress的協(xié)作原理
public static String getMacAddress() throws SocketException {
// 獲取本機的所有網(wǎng)絡接口
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
// 跳過回環(huán)接口和未啟用的接口
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
continue;
}
// 獲取硬件地址(MAC地址)
byte[] macBytes = networkInterface.getHardwareAddress();
if (macBytes != null) {
return formatMacAddress(macBytes);
}
}
return null;
}
/**
* 將MAC地址字節(jié)數(shù)組格式化為標準字符串
*/
private static String formatMacAddress(byte[] macBytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < macBytes.length; i++) {
sb.append(String.format("%02X%s", macBytes[i], (i < macBytes.length - 1) ? "-" : ""));
}
return sb.toString();
}
技術原理解析:
- NetworkInterface:Java提供的網(wǎng)絡接口抽象,可以枚舉本機所有網(wǎng)卡
- InetAddress:雖然方法名中包含InetAddress,但MAC地址獲取主要依賴NetworkInterface的
getHardwareAddress()方法 - 字節(jié)數(shù)組轉字符串:MAC地址本質上是6字節(jié)的十六進制數(shù),需要格式化為
XX:XX:XX:XX:XX:XX或XX-XX-XX-XX-XX-XX格式
異常處理建議
public static String getMacAddressSafe() {
try {
return getMacAddress();
} catch (SocketException e) {
log.error("獲取MAC地址失敗", e);
return null;
}
}
可能的異常場景:
- SecurityException:在受限環(huán)境中(如某些云平臺、Docker容器),可能沒有訪問網(wǎng)絡接口的權限
- SocketException:網(wǎng)絡接口配置異常或不可用
- 返回null:沒有可用的物理網(wǎng)卡(純虛擬環(huán)境或容器)
2.3 IP屬地解析(getCityInfo)
ip2region是一個高精度的IP地理位置庫,支持多種查詢模式。本文介紹基于xdb數(shù)據(jù)庫的內存緩存模式。
ip2region.xdb數(shù)據(jù)庫的加載與使用
/**
* ip2region查詢器(使用內存緩存模式,性能最優(yōu))
*/
private static Searcher searcher = null;
static {
try {
// 加載xdb數(shù)據(jù)庫文件
String dbPath = "classpath:ip2region.xdb";
InputStream inputStream = new ClassPathResource(dbPath).getInputStream();
byte[] cBuff = IoUtil.readBytes(inputStream);
// 創(chuàng)建內存緩存模式的查詢器
searcher = Searcher.newWithBuffer(cBuff);
} catch (Exception e) {
log.error("ip2region數(shù)據(jù)庫加載失敗", e);
}
}
/**
* 獲取IP屬地信息
*/
public static String getCityInfo(String ip) {
if (searcher == null) {
log.warn("ip2region查詢器未初始化");
return null;
}
try {
// 執(zhí)行查詢
return searcher.search(ip);
} catch (Exception e) {
log.error("IP屬地查詢失敗, ip: {}", ip, e);
return null;
}
}
核心流程解析:
- 數(shù)據(jù)庫加載:從classpath加載ip2region.xdb文件(約11MB)
- 內存緩存:使用
Searcher.newWithBuffer()將整個數(shù)據(jù)庫加載到內存 - 查詢執(zhí)行:通過
searcher.search(ip)方法執(zhí)行查詢
內存緩存模式的優(yōu)勢
ip2region支持三種查詢模式:
| 模式 | 內存占用 | 查詢性能 | 適用場景 |
|---|---|---|---|
| 內存模式(newWithBuffer) | 約11MB | 微秒級 | 高并發(fā)、性能敏感 |
| 文件模式(newWithFileOnly) | 極低 | 毫秒級 | 內存受限、低頻查詢 |
| 緩存模式(newWithVectorIndex) | 約200KB | 微秒級 | 內存和性能的平衡選擇 |
推薦使用內存模式:在大多數(shù)應用場景中,11MB的內存占用是可以接受的,換來的是極致的查詢性能。
返回結果格式與處理
getCityInfo返回的原始格式:中國|0|上海|上海市|電信
工具方法封裝:
/**
* 提取IP所屬省份
*/
public static String getIpPossession(String ip) {
String cityInfo = getCityInfo(ip);
if (cityInfo == null) {
return null;
}
String[] parts = cityInfo.split("\\|");
if (parts.length >= 3) {
return parts[2]; // 省份信息
}
return null;
}
/**
* 提取IP所屬城市
*/
public static String getIpCity(String ip) {
String cityInfo = getCityInfo(ip);
if (cityInfo == null) {
return null;
}
String[] parts = cityInfo.split("\\|");
if (parts.length >= 4) {
return parts[3]; // 城市信息
}
return null;
}
結果字段說明:
parts[0]:國家(如:中國、美國)parts[1]:區(qū)域編碼(0表示國內)parts[2]:省份(如:上海、廣東)parts[3]:城市(如:上海市、深圳市)parts[4]:運營商(如:電信、聯(lián)通)
三、實戰(zhàn)應用示例
3.1 控制器層集成
在Spring Boot項目中,可以方便地集成IP工具類:
@RestController
@RequestMapping("/api")
public class UserController {
/**
* 用戶登錄接口 - 記錄IP和屬地信息
*/
@PostMapping("/login")
public Result<LoginResponse> login(@RequestBody LoginRequest request,
HttpServletRequest httpRequest) {
// 獲取客戶端IP
String ip = IpUtil.getRealIp(httpRequest);
// 解析屬地信息
String province = IpUtil.getIpPossession(ip);
String city = IpUtil.getIpCity(ip);
log.info("用戶登錄 - IP: {}, 省份: {}, 城市: {}", ip, province, city);
// 業(yè)務邏輯處理
// ...
// 將屬地信息返回給前端
LoginResponse response = new LoginResponse();
response.setIp(ip);
response.setProvince(province);
response.setCity(city);
return Result.success(response);
}
/**
* 獲取當前用戶的屬地信息
*/
@GetMapping("/location")
public Result<LocationInfo> getLocation(HttpServletRequest httpRequest) {
String ip = IpUtil.getRealIp(httpRequest);
LocationInfo location = new LocationInfo();
location.setIp(ip);
location.setProvince(IpUtil.getIpPossession(ip));
location.setCity(IpUtil.getIpCity(ip));
return Result.success(location);
}
}
3.2 結果處理與業(yè)務邏輯應用
@Service
public class UserService {
/**
* 根據(jù)用戶屬地推薦內容
*/
public List<Content> recommendContent(String ip) {
String province = IpUtil.getIpPossession(ip);
String city = IpUtil.getIpCity(ip);
// 業(yè)務邏輯:根據(jù)屬地推薦不同內容
if ("上海".equals(province)) {
return contentRepository.findByRegion("華東");
} else if ("廣東".equals(province)) {
return contentRepository.findByRegion("華南");
} else {
return contentRepository.findDefault();
}
}
/**
* 風險檢測:識別異地登錄
*/
public boolean detectAbnormalLogin(Long userId, String currentIp) {
// 獲取用戶常用登錄屬地
UserLocationHistory history = locationHistoryRepository.findLatestByUserId(userId);
String currentProvince = IpUtil.getIpPossession(currentIp);
// 如果當前省份與常用省份不同,標記為異常
if (history != null && !history.getProvince().equals(currentProvince)) {
log.warn("檢測到異地登錄 - 用戶: {}, 常用省份: {}, 當前省份: {}",
userId, history.getProvince(), currentProvince);
return true;
}
return false;
}
}
3.3 完成的IpUtil工具類
package com.example.util;
import cn.hutool.core.io.IoUtil;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
/**
* IP工具類
* 提供IP地址獲取、MAC地址獲取、IP屬地解析等核心功能
*
* @author Your Name
* @since 2026-01-13
*/
@Slf4j
public class IpUtil {
/**
* ip2region查詢器(使用內存緩存模式,性能最優(yōu))
* 在類加載時初始化,支持高并發(fā)查詢
*/
private static Searcher searcher = null;
/**
* 靜態(tài)初始化塊:加載ip2region.xdb數(shù)據(jù)庫
*/
static {
try {
// 加載xdb數(shù)據(jù)庫文件
String dbPath = "classpath:ip2region.xdb";
InputStream inputStream = new ClassPathResource(dbPath).getInputStream();
byte[] cBuff = IoUtil.readBytes(inputStream);
// 創(chuàng)建內存緩存模式的查詢器
searcher = Searcher.newWithBuffer(cBuff);
log.info("ip2region數(shù)據(jù)庫加載成功,內存占用: {}MB", cBuff.length / 1024.0 / 1024.0);
} catch (Exception e) {
log.error("ip2region數(shù)據(jù)庫加載失敗,屬地查詢功能將不可用", e);
}
}
/**
* 獲取客戶端IP地址
* 支持多層代理場景,按優(yōu)先級順序從HTTP頭中提取真實IP
*
* @param request HttpServletRequest對象
* @return 客戶端IP地址,無法獲取時返回"unknown"
*/
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
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("X-Real-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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// IPv6本地回環(huán)地址轉換為IPv4
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
/**
* 獲取真實客戶端IP地址(增強版)
* 處理代理鏈中的多個IP,提取第一個作為真實客戶端IP
*
* @param request HttpServletRequest對象
* @return 真實客戶端IP地址
*/
public static String getRealIp(HttpServletRequest request) {
String ip = getIpAddr(request);
if (ip == null || "unknown".equalsIgnoreCase(ip)) {
return "127.0.0.1";
}
// 處理多個IP的情況(逗號分隔),取第一個IP(真實客戶端IP)
if (ip.contains(",")) {
String[] ips = ip.split(",");
ip = ips[0].trim();
}
// 移除IPv6的端口信息(格式如:2001:db8::1:8080)
if (ip.contains(":") && ip.indexOf(":") < ip.lastIndexOf(":")) {
ip = ip.substring(0, ip.lastIndexOf(":"));
}
return ip;
}
/**
* 獲取本地MAC地址
* 通過遍歷網(wǎng)絡接口獲取第一個非回環(huán)且已啟用的網(wǎng)卡的MAC地址
*
* @return MAC地址字符串(格式:XX-XX-XX-XX-XX-XX),獲取失敗返回null
* @throws SocketException 當訪問網(wǎng)絡接口失敗時拋出
*/
public static String getMacAddress() throws SocketException {
// 獲取本機的所有網(wǎng)絡接口
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
// 跳過回環(huán)接口和未啟用的接口
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
continue;
}
// 跳過虛擬接口(常見于Docker、VPN等)
if (networkInterface.isVirtual()) {
continue;
}
// 獲取硬件地址(MAC地址)
byte[] macBytes = networkInterface.getHardwareAddress();
if (macBytes != null && macBytes.length == 6) {
return formatMacAddress(macBytes);
}
}
return null;
}
/**
* 安全獲取本地MAC地址(帶異常處理)
*
* @return MAC地址字符串,獲取失敗返回null
*/
public static String getMacAddressSafe() {
try {
return getMacAddress();
} catch (SocketException e) {
log.error("獲取MAC地址失敗", e);
return null;
}
}
/**
* 將MAC地址字節(jié)數(shù)組格式化為標準字符串
* 格式:XX-XX-XX-XX-XX-XX(大寫十六進制)
*
* @param macBytes MAC地址字節(jié)數(shù)組(6字節(jié))
* @return 格式化后的MAC地址字符串
*/
private static String formatMacAddress(byte[] macBytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < macBytes.length; i++) {
sb.append(String.format("%02X%s", macBytes[i], (i < macBytes.length - 1) ? "-" : ""));
}
return sb.toString();
}
/**
* 獲取IP屬地信息(原始格式)
* 返回格式:中國|0|上海|上海市|電信
*
* @param ip IP地址
* @return 屬地信息字符串,查詢失敗返回null
*/
public static String getCityInfo(String ip) {
if (searcher == null) {
log.warn("ip2region查詢器未初始化");
return null;
}
if (ip == null || ip.isEmpty()) {
return null;
}
try {
return searcher.search(ip);
} catch (Exception e) {
log.error("IP屬地查詢失敗, ip: {}", ip, e);
return null;
}
}
/**
* 安全獲取IP屬地信息(帶異常處理和參數(shù)校驗)
*
* @param ip IP地址
* @return 屬地信息字符串,查詢失敗返回null
*/
public static String getCityInfoSafe(String ip) {
if (ip == null || ip.isEmpty()) {
return null;
}
return getCityInfo(ip);
}
/**
* 提取IP所屬省份
*
* @param ip IP地址
* @return 省份名稱,查詢失敗返回null
*/
public static String getIpPossession(String ip) {
String cityInfo = getCityInfo(ip);
if (cityInfo == null) {
return null;
}
String[] parts = cityInfo.split("\\|");
if (parts.length >= 3) {
String province = parts[2];
return "0".equals(province) ? null : province;
}
return null;
}
/**
* 安全提取IP所屬省份(帶異常處理)
*
* @param ip IP地址
* @return 省份名稱,查詢失敗返回null
*/
public static String getIpPossessionSafe(String ip) {
try {
return getIpPossession(ip);
} catch (Exception e) {
log.warn("提取省份信息失敗, ip: {}", ip, e);
return null;
}
}
/**
* 提取IP所屬城市
*
* @param ip IP地址
* @return 城市名稱,查詢失敗返回null
*/
public static String getIpCity(String ip) {
String cityInfo = getCityInfo(ip);
if (cityInfo == null) {
return null;
}
String[] parts = cityInfo.split("\\|");
if (parts.length >= 4) {
String city = parts[3];
return "0".equals(city) ? null : city;
}
return null;
}
/**
* 安全提取IP所屬城市(帶異常處理)
*
* @param ip IP地址
* @return 城市名稱,查詢失敗返回null
*/
public static String getIpCitySafe(String ip) {
try {
return getIpCity(ip);
} catch (Exception e) {
log.warn("提取城市信息失敗, ip: {}", ip, e);
return null;
}
}
/**
* 提取IP所屬運營商
*
* @param ip IP地址
* @return 運營商名稱,查詢失敗返回null
*/
public static String getIpIsp(String ip) {
String cityInfo = getCityInfo(ip);
if (cityInfo == null) {
return null;
}
String[] parts = cityInfo.split("\\|");
if (parts.length >= 5) {
String isp = parts[4];
return "0".equals(isp) ? null : isp;
}
return null;
}
/**
* 提取IP所屬國家
*
* @param ip IP地址
* @return 國家名稱,查詢失敗返回null
*/
public static String getIpCountry(String ip) {
String cityInfo = getCityInfo(ip);
if (cityInfo == null) {
return null;
}
String[] parts = cityInfo.split("\\|");
if (parts.length >= 1) {
String country = parts[0];
return "0".equals(country) ? null : country;
}
return null;
}
/**
* 判斷IP是否為內網(wǎng)IP
*
* @param ip IP地址
* @return true-內網(wǎng)IP,false-外網(wǎng)IP
*/
public static boolean isInternalIp(String ip) {
if (ip == null || ip.isEmpty()) {
return false;
}
// 127.0.0.1 本地回環(huán)
if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
return true;
}
// 10.0.0.0 - 10.255.255.255
if (ip.startsWith("10.")) {
return true;
}
// 172.16.0.0 - 172.31.255.255
if (ip.startsWith("172.")) {
String[] parts = ip.split("\\.");
if (parts.length >= 2) {
int second = Integer.parseInt(parts[1]);
if (second >= 16 && second <= 31) {
return true;
}
}
}
// 192.168.0.0 - 192.168.255.255
if (ip.startsWith("192.168.")) {
return true;
}
return false;
}
/**
* 驗證IP地址格式是否正確
*
* @param ip IP地址字符串
* @return true-格式正確,false-格式錯誤
*/
public static boolean isValidIp(String ip) {
if (ip == null || ip.isEmpty()) {
return false;
}
String ipv4Pattern = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
return ip.matches(ipv4Pattern);
}
/**
* 獲取本地主機名
*
* @return 主機名,獲取失敗返回"unknown"
*/
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.error("獲取主機名失敗", e);
return "unknown";
}
}
/**
* 獲取本地IP地址
*
* @return 本地IP地址,獲取失敗返回null
*/
public static String getLocalIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error("獲取本地IP失敗", e);
return null;
}
}
}
3.4 注意事項
數(shù)據(jù)庫文件放置位置
ip2region.xdb數(shù)據(jù)庫文件需要正確放置,以便程序加載:
src/main/resources/ └── ip2region.xdb
如果使用外部文件路徑,修改加載邏輯:
String dbPath = "/path/to/ip2region.xdb"; // 絕對路徑 File file = new File(dbPath); byte[] cBuff = Files.readAllBytes(file.toPath()); searcher = Searcher.newWithBuffer(cBuff);
異常捕獲建議
// 1. 初始化階段:數(shù)據(jù)庫加載失敗不應影響應用啟動
static {
try {
initIp2region();
} catch (Exception e) {
log.error("ip2region初始化失敗,屬地查詢功能將不可用", e);
}
}
// 2. 查詢階段:單個查詢失敗不應影響主流程
public static String getCityInfoSafe(String ip) {
try {
return getCityInfo(ip);
} catch (Exception e) {
log.warn("IP屬地查詢異常: {}", ip);
return null;
}
}
// 3. 業(yè)務層:優(yōu)雅降級
public void recordUserLogin(Long userId, HttpServletRequest request) {
String ip = IpUtil.getRealIp(request);
String province = IpUtil.getIpPossessionSafe(ip); // 使用安全版本
if (province != null) {
// 正常處理
loginRecordRepository.save(userId, ip, province);
} else {
// 降級處理:僅記錄IP
loginRecordRepository.save(userId, ip, "未知");
}
}
性能優(yōu)化點
- 靜態(tài)初始化:查詢器在類加載時初始化,避免每次查詢都重新加載
- 并發(fā)安全:ip2region的Searcher是線程安全的,無需額外同步
- 緩存熱點IP:對于高頻查詢的IP,可以增加一層本地緩存
- 異步日志:屬地查詢失敗時,使用異步日志避免阻塞主流程
// 熱點IP緩存示例
private static final Cache<String, String> ipCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
public static String getCityInfoWithCache(String ip) {
return ipCache.get(ip, key -> getCityInfo(key));
}
總結
本文詳細介紹了一個功能完善的Java IP工具類,涵蓋了IP地址獲取、MAC地址獲取和IP屬地解析三大核心功能。通過合理的代碼設計和異常處理,能夠在各種復雜場景下穩(wěn)定運行。
關鍵要點回顧:
- IP獲取需要考慮多層代理場景,正確處理HTTP頭優(yōu)先級
- MAC地址獲取受環(huán)境影響較大,需要做好異常處理
- ip2region的內存緩存模式性能優(yōu)異,推薦在高并發(fā)場景使用
- 業(yè)務集成時要注意異常捕獲和優(yōu)雅降級
希望這篇文章能夠幫助你在項目中更好地應用IP工具類,為業(yè)務提供更精準的地理位置服務。
以上就是Java基于ip2region.xdb數(shù)據(jù)庫從IP獲取到屬地解析的全攻略的詳細內容,更多關于Java IP獲取到屬地解析的資料請關注腳本之家其它相關文章!
相關文章
RabbitMQ在Windows環(huán)境下常見啟動失敗的完整解決方法
RabbitMQ是一個流行的開源消息代理,采用 AMQP標準,它允許應用程序之間以異步方式交換數(shù)據(jù),確保消息的可靠性和靈活性,這篇文章主要介紹了RabbitMQ在Windows環(huán)境下常見啟動失敗的整解決方法,需要的朋友可以參考下2025-11-11
SpringBoot下使用MyBatis-Puls代碼生成器的方法
這篇文章主要介紹了SpringBoot下使用MyBatis-Puls代碼生成器的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
使用Spring事件監(jiān)聽機制實現(xiàn)跨模塊調用的步驟詳解
Spring 事件監(jiān)聽機制是 Spring 框架中用于在應用程序的不同組件之間進行通信的一種機制,Spring 事件監(jiān)聽機制基于觀察者設計模式,使得應用程序的各個部分可以解耦,提高模塊化和可維護性,本文給大家介紹了使用Spring事件監(jiān)聽機制實現(xiàn)跨模塊調用,需要的朋友可以參考下2024-06-06

