最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java基于ip2region.xdb數(shù)據(jù)庫從IP獲取到屬地解析的全攻略

 更新時間:2026年02月04日 09:30:50   作者:J_liaty  
在日常的Java開發(fā)中,IP地址相關(guān)的操作是非常常見的需求,無論是用戶行為分析、地域化服務(wù),還是安全防護,都需要精準地獲取和解析IP信息,本文將詳細介紹一個功能完善的Java IP工具類,需要的朋友可以參考下

前言

在日常的Java開發(fā)中,IP地址相關(guān)的操作是非常常見的需求。無論是用戶行為分析、地域化服務(wù),還是安全防護,都需要精準地獲取和解析IP信息。本文將詳細介紹一個功能完善的Java IP工具類,涵蓋IP地址獲取、MAC地址獲取以及IP屬地解析三大核心功能,幫助你在項目中快速落地這些能力。

一、工具類概述

1.1 核心功能

該IP工具類提供了三個核心功能模塊:

  • IP地址獲取:支持多種代理場景下的客戶端IP提取,能夠穿透多層代理鏈獲取真實客戶端IP
  • MAC地址獲取:通過Java原生API獲取本地網(wǎng)絡(luò)接口的MAC地址
  • IP屬地解析:基于ip2region.xdb數(shù)據(jù)庫,實現(xiàn)毫秒級的IP地理位置查詢

1.2 應(yīng)用場景

這些功能在實際項目中有著廣泛的應(yīng)用場景:

  • 用戶行為分析:通過IP屬地信息,統(tǒng)計用戶的地域分布,為產(chǎn)品決策提供數(shù)據(jù)支持
  • 地域化服務(wù):根據(jù)用戶所在省份/城市,提供個性化的內(nèi)容推薦或服務(wù)
  • 安全防護:識別異常IP訪問,進行地域限制或風(fēng)險預(yù)警
  • 日志審計:在關(guān)鍵操作日志中記錄IP和屬地信息,便于問題追溯

1.3 技術(shù)亮點

該工具類的技術(shù)亮點主要體現(xiàn)在:

  • 多場景IP獲取策略:內(nèi)置HTTP頭參數(shù)優(yōu)先級邏輯,能夠處理各種代理和負載均衡場景
  • 高效內(nèi)存緩存:ip2region.xdb數(shù)據(jù)庫采用內(nèi)存緩存模式(Searcher.newWithBuffer),查詢性能達到微秒級
  • 完善的異常處理:針對不同場景設(shè)計了合理的異常捕獲和處理機制

二、核心功能詳解

2.1 IP地址獲取(getIpAddr)

在真實的生產(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)先級說明

  1. X-Forwarded-For:Squid等代理服務(wù)器的標準,最為常用
  2. Proxy-Client-IP:Apache+Mod_Proxy場景
  3. X-Real-IP:Nginx反向代理常用
  4. WL-Proxy-Client-IP:WebLogic代理場景
  5. HTTP_CLIENT_IP:部分代理服務(wù)器使用
  6. HTTP_X_FORWARDED_FOR:部分特殊代理場景
  7. 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;
}

關(guān)鍵點解析

  • 逗號分隔處理X-Forwarded-For可能包含代理鏈中的所有IP,必須提取第一個
  • IPv6地址兼容:將IPv6的本地回環(huán)地址0:0:0:0:0:0:0:1轉(zhuǎn)換為127.0.0.1,便于統(tǒng)一處理
  • 端口剝離:某些代理可能返回帶端口的IP格式,需要進行清理

2.2 MAC地址獲?。╣etMacAddress)

MAC地址是網(wǎng)絡(luò)接口的唯一標識,在某些場景下(如設(shè)備綁定、局域網(wǎng)通信)需要獲取。

NetworkInterface和InetAddress的協(xié)作原理

public static String getMacAddress() throws SocketException {
    // 獲取本機的所有網(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ù)組格式化為標準字符串
 */
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ò)接口抽象,可以枚舉本機所有網(wǎng)卡
  • InetAddress:雖然方法名中包含InetAddress,但MAC地址獲取主要依賴NetworkInterface的getHardwareAddress()方法
  • 字節(jié)數(shù)組轉(zhuǎn)字符串:MAC地址本質(zhì)上是6字節(jié)的十六進制數(shù),需要格式化為XX:XX:XX:XX:XX:XXXX-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)絡(luò)接口的權(quán)限
  • SocketException:網(wǎng)絡(luò)接口配置異?;虿豢捎?/li>
  • 返回null:沒有可用的物理網(wǎng)卡(純虛擬環(huán)境或容器)

2.3 IP屬地解析(getCityInfo)

ip2region是一個高精度的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;
    }
}

核心流程解析

  1. 數(shù)據(jù)庫加載:從classpath加載ip2region.xdb文件(約11MB)
  2. 內(nèi)存緩存:使用Searcher.newWithBuffer()將整個數(shù)據(jù)庫加載到內(nèi)存
  3. 查詢執(zhí)行:通過searcher.search(ip)方法執(zhí)行查詢

內(nèi)存緩存模式的優(yōu)勢

ip2region支持三種查詢模式:

模式內(nèi)存占用查詢性能適用場景
內(nèi)存模式(newWithBuffer)約11MB微秒級高并發(fā)、性能敏感
文件模式(newWithFileOnly)極低毫秒級內(nèi)存受限、低頻查詢
緩存模式(newWithVectorIndex)約200KB微秒級內(nèi)存和性能的平衡選擇

推薦使用內(nèi)存模式:在大多數(shù)應(yī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]:運營商(如:電信、聯(lián)通)

三、實戰(zhàn)應(yīng)用示例

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è)務(wù)邏輯處理
        // ...
        
        // 將屬地信息返回給前端
        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 結(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)險檢測:識別異地登錄
     */
    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查詢器(使用內(nèi)存緩存模式,性能最優(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)建內(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地址
     * 支持多層代理場景,按優(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)地址轉(zhuǎ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)絡(luò)接口獲取第一個非回環(huán)且已啟用的網(wǎng)卡的MAC地址
     *
     * @return MAC地址字符串(格式:XX-XX-XX-XX-XX-XX),獲取失敗返回null
     * @throws SocketException 當訪問網(wǎng)絡(luò)接口失敗時拋出
     */
    public static String getMacAddress() throws SocketException {
        // 獲取本機的所有網(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ù)組格式化為標準字符串
     * 格式: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是否為內(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;
    }

    /**
     * 驗證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ù)庫加載失敗不應(yīng)影響應(yīng)用啟動
static {
    try {
        initIp2region();
    } catch (Exception e) {
        log.error("ip2region初始化失敗,屬地查詢功能將不可用", e);
    }
}

// 2. 查詢階段:單個查詢失敗不應(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)雅降級
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)化點

  1. 靜態(tài)初始化:查詢器在類加載時初始化,避免每次查詢都重新加載
  2. 并發(fā)安全:ip2region的Searcher是線程安全的,無需額外同步
  3. 緩存熱點IP:對于高頻查詢的IP,可以增加一層本地緩存
  4. 異步日志:屬地查詢失敗時,使用異步日志避免阻塞主流程
// 熱點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é)

本文詳細介紹了一個功能完善的Java IP工具類,涵蓋了IP地址獲取、MAC地址獲取和IP屬地解析三大核心功能。通過合理的代碼設(shè)計和異常處理,能夠在各種復(fù)雜場景下穩(wěn)定運行。

關(guān)鍵要點回顧

  • IP獲取需要考慮多層代理場景,正確處理HTTP頭優(yōu)先級
  • MAC地址獲取受環(huán)境影響較大,需要做好異常處理
  • ip2region的內(nèi)存緩存模式性能優(yōu)異,推薦在高并發(fā)場景使用
  • 業(yè)務(wù)集成時要注意異常捕獲和優(yōu)雅降級

希望這篇文章能夠幫助你在項目中更好地應(yīng)用IP工具類,為業(yè)務(wù)提供更精準的地理位置服務(wù)。

以上就是Java基于ip2region.xdb數(shù)據(jù)庫從IP獲取到屬地解析的全攻略的詳細內(nèi)容,更多關(guān)于Java IP獲取到屬地解析的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java中NoClassDefFoundError異常的原因及解決方法

    Java中NoClassDefFoundError異常的原因及解決方法

    在 Java 開發(fā)中,java.lang.NoClassDefFoundError 是運行時異常中最常見的問題之一,它通常出現(xiàn)在程序編譯成功后,卻在運行時因 JVM 無法找到某個類的定義 而拋出,本文給大家介紹了Java中NoClassDefFoundError異常的原因及解決方法,需要的朋友可以參考下
    2025-05-05
  • Java實現(xiàn)簡單學(xué)生信息管理系統(tǒng)

    Java實現(xiàn)簡單學(xué)生信息管理系統(tǒng)

    這篇文章主要為大家詳細介紹了Java實現(xiàn)簡單學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Java通過值查找對應(yīng)的枚舉的實現(xiàn)

    Java通過值查找對應(yīng)的枚舉的實現(xiàn)

    本文主要介紹了Java通過值查找對應(yīng)的枚舉的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-02-02
  • Java中double保留兩位小數(shù)的多種方法

    Java中double保留兩位小數(shù)的多種方法

    這篇文章主要給大家介紹了關(guān)于Java中double保留兩位小數(shù)的多種方法,對于double數(shù)據(jù)類型進行計算發(fā)生的精度丟失的情況,可以按照自己的需求選擇任意方式,需要的朋友可以參考下
    2023-07-07
  • Java線程池submit阻塞獲取結(jié)果的實現(xiàn)原理詳解

    Java線程池submit阻塞獲取結(jié)果的實現(xiàn)原理詳解

    Java線程池中提交任務(wù)運行,通常使用execute()方法就足夠了。那如果想要實現(xiàn)在主線程中阻塞獲取線程池任務(wù)運行的結(jié)果,該怎么辦呢?本文就來和大家一起討論討論
    2022-10-10
  • Java實現(xiàn)PDF添加水印的完整方案

    Java實現(xiàn)PDF添加水印的完整方案

    在企業(yè)級應(yīng)用中,PDF水印是文檔安全管理的必備功能,無論是版權(quán)保護、版本標識,還是敏感信息管控,水印都扮演著重要角色,本文將提供一個生產(chǎn)級的PDF水印解決方案,需要的朋友可以參考下
    2026-02-02
  • Maven工程打包jar的多種方式

    Maven工程打包jar的多種方式

    Maven打包一般可以生成兩種包一種是可以直接運行的包,一種是依賴包(只是編譯包),這篇文章主要介紹了Maven工程打包jar的多種方式步驟詳解,需要的朋友可以參考下
    2023-04-04
  • java synchronized同步靜態(tài)方法和同步非靜態(tài)方法的異同

    java synchronized同步靜態(tài)方法和同步非靜態(tài)方法的異同

    這篇文章主要介紹了java synchronized同步靜態(tài)方法和同步非靜態(tài)方法的異同的相關(guān)資料,需要的朋友可以參考下
    2017-01-01
  • 淺談Java8 判空新寫法

    淺談Java8 判空新寫法

    在開發(fā)過程中很多時候會遇到判空校驗,如果不做判空校驗則會產(chǎn)生NullPointerException異常,本文就來介紹一下Java8 判空新寫法,感興趣的可以了解一下
    2021-09-09
  • 詳解Java的TCP/IP編程學(xué)習(xí)--基于定界符的成幀

    詳解Java的TCP/IP編程學(xué)習(xí)--基于定界符的成幀

    這篇文章主要介紹了Java的TCP/IP編程學(xué)習(xí)--基于定界符的成幀,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04

最新評論

双辽市| 克拉玛依市| 乌恰县| 镇原县| 克东县| 扎囊县| 津市市| 田阳县| 平远县| 隆昌县| 孝感市| 辽阳市| 华蓥市| 涿州市| 淮滨县| 武义县| 慈利县| 北辰区| 新河县| 南平市| 临泽县| 渑池县| 永平县| 咸丰县| 上蔡县| 霍邱县| 仁寿县| 扬州市| 龙山县| 遵义县| 安多县| 青海省| 三原县| 隆德县| 鄂州市| 祁阳县| 南漳县| 济源市| 连南| 通渭县| 郯城县|