Java基于ip2region.xdb數(shù)據(jù)庫從IP獲取到屬地解析的全攻略
前言
在日常的Java開發(fā)中,IP地址相關(guān)的操作是非常常見的需求。無論是用戶行為分析、地域化服務(wù),還是安全防護(hù),都需要精準(zhǔn)地獲取和解析IP信息。本文將詳細(xì)介紹一個(gè)功能完善的Java IP工具類,涵蓋IP地址獲取、MAC地址獲取以及IP屬地解析三大核心功能,幫助你在項(xiàng)目中快速落地這些能力。
一、工具類概述
1.1 核心功能
該IP工具類提供了三個(gè)核心功能模塊:
- IP地址獲取:支持多種代理場(chǎng)景下的客戶端IP提取,能夠穿透多層代理鏈獲取真實(shí)客戶端IP
- MAC地址獲取:通過Java原生API獲取本地網(wǎng)絡(luò)接口的MAC地址
- IP屬地解析:基于ip2region.xdb數(shù)據(jù)庫,實(shí)現(xiàn)毫秒級(jí)的IP地理位置查詢
1.2 應(yīng)用場(chǎng)景
這些功能在實(shí)際項(xiàng)目中有著廣泛的應(yīng)用場(chǎng)景:
- 用戶行為分析:通過IP屬地信息,統(tǒng)計(jì)用戶的地域分布,為產(chǎn)品決策提供數(shù)據(jù)支持
- 地域化服務(wù):根據(jù)用戶所在省份/城市,提供個(gè)性化的內(nèi)容推薦或服務(wù)
- 安全防護(hù):識(shí)別異常IP訪問,進(jìn)行地域限制或風(fēng)險(xiǎn)預(yù)警
- 日志審計(jì):在關(guān)鍵操作日志中記錄IP和屬地信息,便于問題追溯
1.3 技術(shù)亮點(diǎn)
該工具類的技術(shù)亮點(diǎn)主要體現(xiàn)在:
- 多場(chǎng)景IP獲取策略:內(nèi)置HTTP頭參數(shù)優(yōu)先級(jí)邏輯,能夠處理各種代理和負(fù)載均衡場(chǎng)景
- 高效內(nèi)存緩存:ip2region.xdb數(shù)據(jù)庫采用內(nèi)存緩存模式(
Searcher.newWithBuffer),查詢性能達(dá)到微秒級(jí) - 完善的異常處理:針對(duì)不同場(chǎng)景設(shè)計(jì)了合理的異常捕獲和處理機(jī)制
二、核心功能詳解
2.1 IP地址獲取(getIpAddr)
在真實(shí)的生產(chǎn)環(huán)境中,獲取客戶端IP并非想象中那么簡(jiǎn)單。用戶請(qǐng)求可能經(jīng)過多層代理、CDN、負(fù)載均衡器,因此需要從多個(gè)HTTP頭中提取真實(shí)IP。
HTTP頭參數(shù)優(yōu)先級(jí)順序
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)先級(jí)說明:
- X-Forwarded-For:Squid等代理服務(wù)器的標(biāo)準(zhǔn),最為常用
- Proxy-Client-IP:Apache+Mod_Proxy場(chǎng)景
- X-Real-IP:Nginx反向代理常用
- WL-Proxy-Client-IP:WebLogic代理場(chǎng)景
- HTTP_CLIENT_IP:部分代理服務(wù)器使用
- HTTP_X_FORWARDED_FOR:部分特殊代理場(chǎng)景
- getRemoteAddr():直接獲取,作為兜底方案
代理場(chǎng)景下的IP提取邏輯
在實(shí)際場(chǎng)景中,X-Forwarded-For的值可能是多個(gè)IP的列表,格式為:client, proxy1, proxy2。其中第一個(gè)IP才是真實(shí)客戶端IP。
增強(qiáng)版的IP提取方法:
public static String getRealIp(HttpServletRequest request) {
String ip = getIpAddr(request);
// 處理多個(gè)IP的情況(逗號(hào)分隔)
if (ip != null && ip.contains(",")) {
String[] ips = ip.split(",");
ip = ips[0].trim(); // 取第一個(gè)IP(真實(shí)客戶端IP)
}
// 移除IPv6的端口信息
if (ip != null && ip.contains(":") && ip.indexOf(":") < ip.lastIndexOf(":")) {
ip = ip.substring(0, ip.lastIndexOf(":"));
}
return ip;
}
關(guān)鍵點(diǎn)解析:
- 逗號(hào)分隔處理:
X-Forwarded-For可能包含代理鏈中的所有IP,必須提取第一個(gè) - IPv6地址兼容:將IPv6的本地回環(huán)地址
0:0:0:0:0:0:0:1轉(zhuǎn)換為127.0.0.1,便于統(tǒng)一處理 - 端口剝離:某些代理可能返回帶端口的IP格式,需要進(jìn)行清理
2.2 MAC地址獲?。╣etMacAddress)
MAC地址是網(wǎng)絡(luò)接口的唯一標(biāo)識(shí),在某些場(chǎng)景下(如設(shè)備綁定、局域網(wǎng)通信)需要獲取。
NetworkInterface和InetAddress的協(xié)作原理
public static String getMacAddress() throws SocketException {
// 獲取本機(jī)的所有網(wǎng)絡(luò)接口
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ù)組格式化為標(biāo)準(zhǔn)字符串
*/
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();
}
技術(shù)原理解析:
- NetworkInterface:Java提供的網(wǎng)絡(luò)接口抽象,可以枚舉本機(jī)所有網(wǎng)卡
- InetAddress:雖然方法名中包含InetAddress,但MAC地址獲取主要依賴NetworkInterface的
getHardwareAddress()方法 - 字節(jié)數(shù)組轉(zhuǎn)字符串:MAC地址本質(zhì)上是6字節(jié)的十六進(jìn)制數(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;
}
}
可能的異常場(chǎng)景:
- SecurityException:在受限環(huán)境中(如某些云平臺(tái)、Docker容器),可能沒有訪問網(wǎng)絡(luò)接口的權(quán)限
- SocketException:網(wǎng)絡(luò)接口配置異?;虿豢捎?/li>
- 返回null:沒有可用的物理網(wǎng)卡(純虛擬環(huán)境或容器)
2.3 IP屬地解析(getCityInfo)
ip2region是一個(gè)高精度的IP地理位置庫,支持多種查詢模式。本文介紹基于xdb數(shù)據(jù)庫的內(nèi)存緩存模式。
ip2region.xdb數(shù)據(jù)庫的加載與使用
/**
* ip2region查詢器(使用內(nèi)存緩存模式,性能最優(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)建內(nèi)存緩存模式的查詢器
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)
- 內(nèi)存緩存:使用
Searcher.newWithBuffer()將整個(gè)數(shù)據(jù)庫加載到內(nèi)存 - 查詢執(zhí)行:通過
searcher.search(ip)方法執(zhí)行查詢
內(nèi)存緩存模式的優(yōu)勢(shì)
ip2region支持三種查詢模式:
| 模式 | 內(nèi)存占用 | 查詢性能 | 適用場(chǎng)景 |
|---|---|---|---|
| 內(nèi)存模式(newWithBuffer) | 約11MB | 微秒級(jí) | 高并發(fā)、性能敏感 |
| 文件模式(newWithFileOnly) | 極低 | 毫秒級(jí) | 內(nèi)存受限、低頻查詢 |
| 緩存模式(newWithVectorIndex) | 約200KB | 微秒級(jí) | 內(nèi)存和性能的平衡選擇 |
推薦使用內(nèi)存模式:在大多數(shù)應(yīng)用場(chǎng)景中,11MB的內(nèi)存占用是可以接受的,換來的是極致的查詢性能。
返回結(jié)果格式與處理
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;
}
結(jié)果字段說明:
parts[0]:國家(如:中國、美國)parts[1]:區(qū)域編碼(0表示國內(nèi))parts[2]:省份(如:上海、廣東)parts[3]:城市(如:上海市、深圳市)parts[4]:運(yùn)營商(如:電信、聯(lián)通)
三、實(shí)戰(zhàn)應(yīng)用示例
3.1 控制器層集成
在Spring Boot項(xiàng)目中,可以方便地集成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è)務(wù)邏輯處理
// ...
// 將屬地信息返回給前端
LoginResponse response = new LoginResponse();
response.setIp(ip);
response.setProvince(province);
response.setCity(city);
return Result.success(response);
}
/**
* 獲取當(dāng)前用戶的屬地信息
*/
@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 結(jié)果處理與業(yè)務(wù)邏輯應(yīng)用
@Service
public class UserService {
/**
* 根據(jù)用戶屬地推薦內(nèi)容
*/
public List<Content> recommendContent(String ip) {
String province = IpUtil.getIpPossession(ip);
String city = IpUtil.getIpCity(ip);
// 業(yè)務(wù)邏輯:根據(jù)屬地推薦不同內(nèi)容
if ("上海".equals(province)) {
return contentRepository.findByRegion("華東");
} else if ("廣東".equals(province)) {
return contentRepository.findByRegion("華南");
} else {
return contentRepository.findDefault();
}
}
/**
* 風(fēng)險(xiǎn)檢測(cè):識(shí)別異地登錄
*/
public boolean detectAbnormalLogin(Long userId, String currentIp) {
// 獲取用戶常用登錄屬地
UserLocationHistory history = locationHistoryRepository.findLatestByUserId(userId);
String currentProvince = IpUtil.getIpPossession(currentIp);
// 如果當(dāng)前省份與常用省份不同,標(biāo)記為異常
if (history != null && !history.getProvince().equals(currentProvince)) {
log.warn("檢測(cè)到異地登錄 - 用戶: {}, 常用省份: {}, 當(dāng)前省份: {}",
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查詢器(使用內(nèi)存緩存模式,性能最優(yōu))
* 在類加載時(shí)初始化,支持高并發(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)建內(nèi)存緩存模式的查詢器
searcher = Searcher.newWithBuffer(cBuff);
log.info("ip2region數(shù)據(jù)庫加載成功,內(nèi)存占用: {}MB", cBuff.length / 1024.0 / 1024.0);
} catch (Exception e) {
log.error("ip2region數(shù)據(jù)庫加載失敗,屬地查詢功能將不可用", e);
}
}
/**
* 獲取客戶端IP地址
* 支持多層代理場(chǎng)景,按優(yōu)先級(jí)順序從HTTP頭中提取真實(shí)IP
*
* @param request HttpServletRequest對(duì)象
* @return 客戶端IP地址,無法獲取時(shí)返回"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)地址轉(zhuǎn)換為IPv4
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
/**
* 獲取真實(shí)客戶端IP地址(增強(qiáng)版)
* 處理代理鏈中的多個(gè)IP,提取第一個(gè)作為真實(shí)客戶端IP
*
* @param request HttpServletRequest對(duì)象
* @return 真實(shí)客戶端IP地址
*/
public static String getRealIp(HttpServletRequest request) {
String ip = getIpAddr(request);
if (ip == null || "unknown".equalsIgnoreCase(ip)) {
return "127.0.0.1";
}
// 處理多個(gè)IP的情況(逗號(hào)分隔),取第一個(gè)IP(真實(shí)客戶端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)絡(luò)接口獲取第一個(gè)非回環(huán)且已啟用的網(wǎng)卡的MAC地址
*
* @return MAC地址字符串(格式:XX-XX-XX-XX-XX-XX),獲取失敗返回null
* @throws SocketException 當(dāng)訪問網(wǎng)絡(luò)接口失敗時(shí)拋出
*/
public static String getMacAddress() throws SocketException {
// 獲取本機(jī)的所有網(wǎng)絡(luò)接口
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ù)組格式化為標(biāo)準(zhǔn)字符串
* 格式:XX-XX-XX-XX-XX-XX(大寫十六進(jìn)制)
*
* @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ù)校驗(yàn))
*
* @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所屬運(yùn)營商
*
* @param ip IP地址
* @return 運(yùn)營商名稱,查詢失敗返回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是否為內(nèi)網(wǎng)IP
*
* @param ip IP地址
* @return true-內(nèi)網(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;
}
/**
* 驗(yàn)證IP地址格式是否正確
*
* @param ip IP地址字符串
* @return true-格式正確,false-格式錯(cuò)誤
*/
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);
}
/**
* 獲取本地主機(jī)名
*
* @return 主機(jī)名,獲取失敗返回"unknown"
*/
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.error("獲取主機(jī)名失敗", 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 注意事項(xiàng)
數(shù)據(jù)庫文件放置位置
ip2region.xdb數(shù)據(jù)庫文件需要正確放置,以便程序加載:
src/main/resources/ └── ip2region.xdb
如果使用外部文件路徑,修改加載邏輯:
String dbPath = "/path/to/ip2region.xdb"; // 絕對(duì)路徑 File file = new File(dbPath); byte[] cBuff = Files.readAllBytes(file.toPath()); searcher = Searcher.newWithBuffer(cBuff);
異常捕獲建議
// 1. 初始化階段:數(shù)據(jù)庫加載失敗不應(yīng)影響應(yīng)用啟動(dòng)
static {
try {
initIp2region();
} catch (Exception e) {
log.error("ip2region初始化失敗,屬地查詢功能將不可用", e);
}
}
// 2. 查詢階段:?jiǎn)蝹€(gè)查詢失敗不應(yīng)影響主流程
public static String getCityInfoSafe(String ip) {
try {
return getCityInfo(ip);
} catch (Exception e) {
log.warn("IP屬地查詢異常: {}", ip);
return null;
}
}
// 3. 業(yè)務(wù)層:優(yōu)雅降級(jí)
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 {
// 降級(jí)處理:僅記錄IP
loginRecordRepository.save(userId, ip, "未知");
}
}
性能優(yōu)化點(diǎn)
- 靜態(tài)初始化:查詢器在類加載時(shí)初始化,避免每次查詢都重新加載
- 并發(fā)安全:ip2region的Searcher是線程安全的,無需額外同步
- 緩存熱點(diǎn)IP:對(duì)于高頻查詢的IP,可以增加一層本地緩存
- 異步日志:屬地查詢失敗時(shí),使用異步日志避免阻塞主流程
// 熱點(diǎn)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));
}
總結(jié)
本文詳細(xì)介紹了一個(gè)功能完善的Java IP工具類,涵蓋了IP地址獲取、MAC地址獲取和IP屬地解析三大核心功能。通過合理的代碼設(shè)計(jì)和異常處理,能夠在各種復(fù)雜場(chǎng)景下穩(wěn)定運(yùn)行。
關(guān)鍵要點(diǎn)回顧:
- IP獲取需要考慮多層代理場(chǎng)景,正確處理HTTP頭優(yōu)先級(jí)
- MAC地址獲取受環(huán)境影響較大,需要做好異常處理
- ip2region的內(nèi)存緩存模式性能優(yōu)異,推薦在高并發(fā)場(chǎng)景使用
- 業(yè)務(wù)集成時(shí)要注意異常捕獲和優(yōu)雅降級(jí)
希望這篇文章能夠幫助你在項(xiàng)目中更好地應(yīng)用IP工具類,為業(yè)務(wù)提供更精準(zhǔn)的地理位置服務(wù)。
以上就是Java基于ip2region.xdb數(shù)據(jù)庫從IP獲取到屬地解析的全攻略的詳細(xì)內(nèi)容,更多關(guān)于Java IP獲取到屬地解析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中NoClassDefFoundError異常的原因及解決方法
在 Java 開發(fā)中,java.lang.NoClassDefFoundError 是運(yùn)行時(shí)異常中最常見的問題之一,它通常出現(xiàn)在程序編譯成功后,卻在運(yùn)行時(shí)因 JVM 無法找到某個(gè)類的定義 而拋出,本文給大家介紹了Java中NoClassDefFoundError異常的原因及解決方法,需要的朋友可以參考下2025-05-05
Java實(shí)現(xiàn)簡(jiǎn)單學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Java通過值查找對(duì)應(yīng)的枚舉的實(shí)現(xiàn)
本文主要介紹了Java通過值查找對(duì)應(yīng)的枚舉的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02
Java線程池submit阻塞獲取結(jié)果的實(shí)現(xiàn)原理詳解
Java線程池中提交任務(wù)運(yùn)行,通常使用execute()方法就足夠了。那如果想要實(shí)現(xiàn)在主線程中阻塞獲取線程池任務(wù)運(yùn)行的結(jié)果,該怎么辦呢?本文就來和大家一起討論討論2022-10-10
java synchronized同步靜態(tài)方法和同步非靜態(tài)方法的異同
這篇文章主要介紹了java synchronized同步靜態(tài)方法和同步非靜態(tài)方法的異同的相關(guān)資料,需要的朋友可以參考下2017-01-01
詳解Java的TCP/IP編程學(xué)習(xí)--基于定界符的成幀
這篇文章主要介紹了Java的TCP/IP編程學(xué)習(xí)--基于定界符的成幀,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04

