引入pom.xml

<!-- ip2region 离线IP归属地查询 -->
<dependency>
    <groupId>org.lionsoul</groupId>
    <artifactId>ip2region</artifactId>
    <version>2.7.0</version>
</dependency>

并且下载xdb文件 https://github.com/lionsoul2014/ip2region/tree/master/data

创建工具类

package com.tb.tbsurvey.common.util;

import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;

import java.io.InputStream;

/**
 * IP 工具类:获取客户端真实 IP 及归属地查询
 */
@Slf4j
@Component
public class IpUtils {

    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST_IPV4 = "127.0.0.1";
    private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";

    private Searcher searcher;

    @PostConstruct
    public void init() {
        try {
            ClassPathResource resource = new ClassPathResource("ip2region/ip2region_v4.xdb");
            InputStream inputStream = resource.getInputStream();
            byte[] dbBuff = FileCopyUtils.copyToByteArray(inputStream);
            // 使用完全基于内存的查询(并发安全)
            searcher = Searcher.newWithBuffer(dbBuff);
            log.info("ip2region 数据库加载成功");
        } catch (Exception e) {
            log.warn("ip2region 数据库加载失败,IP归属地功能不可用: {}", e.getMessage());
        }
    }

    /**
     * 从 HttpServletRequest 获取客户端真实 IP
     * 处理代理情况:X-Forwarded-For、X-Real-IP 等
     */
    public String getClientIp(HttpServletRequest request) {
        if (request == null) {
            return "";
        }

        String ip = request.getHeader("X-Forwarded-For");
        if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (!StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        // X-Forwarded-For 可能包含多个 IP,取第一个(真实客户端 IP)
        if (StringUtils.hasText(ip) && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }

        // 处理本地回环地址
        if (LOCALHOST_IPV6.equals(ip)) {
            ip = LOCALHOST_IPV4;
        }

        return ip;
    }

    /**
     * 获取 IP 归属地
     * @return 格式如:浙江-温州,未知返回空字符串
     */
    public String getIpLocation(String ip) {
        if (!StringUtils.hasText(ip) || searcher == null) {
            return "";
        }
        // 本地 IP 不查询
        if (LOCALHOST_IPV4.equals(ip) || ip.startsWith("192.168.") || ip.startsWith("10.") || ip.startsWith("172.")) {
            return "内网IP";
        }

        try {
            // ip2region 返回格式:国家|区域|省份|城市|ISP
            // 例如:中国|0|浙江省|温州市|电信
            String region = searcher.search(ip);
            return parseLocation(region);
        } catch (Exception e) {
            log.warn("IP归属地查询失败: ip={}, error={}", ip, e.getMessage());
            return "";
        }
    }

    /**
     * 解析 ip2region 返回的地区信息
     * 输入:中国|0|浙江省|温州市|电信
     * 输出:浙江-温州
     */
    private String parseLocation(String region) {
        if (!StringUtils.hasText(region)) {
            return "";
        }
        String[] parts = region.split("\\|");
        if (parts.length < 4) {
            return "";
        }
        String province = parts[2]; // 浙江省
        String city = parts[3];     // 温州市

        // 移除"省"、"市"、"自治区"等后缀
        province = removeSuffix(province, "省", "市", "自治区", "特别行政区");
        city = removeSuffix(city, "市", "县", "区");

        // 如果是直辖市或省和市相同,只返回省份
        if ("0".equals(city) || !StringUtils.hasText(city) || province.equals(city)) {
            return StringUtils.hasText(province) && !"0".equals(province) ? province : "";
        }
        // 如果省份为0(未知),只返回城市
        if ("0".equals(province) || !StringUtils.hasText(province)) {
            return StringUtils.hasText(city) && !"0".equals(city) ? city : "";
        }

        return province + "-" + city;
    }

    /**
     * 移除字符串的指定后缀
     */
    private String removeSuffix(String str, String... suffixes) {
        if (!StringUtils.hasText(str)) {
            return str;
        }
        for (String suffix : suffixes) {
            if (str.endsWith(suffix)) {
                return str.substring(0, str.length() - suffix.length());
            }
        }
        return str;
    }

    /**
     * 获取格式化的 IP 及归属地
     * @return 格式如:61.164.138.58(浙江-温州)
     */
    public String getIpWithLocation(HttpServletRequest request) {
        String ip = getClientIp(request);
        if (!StringUtils.hasText(ip)) {
            return "";
        }
        String location = getIpLocation(ip);
        if (StringUtils.hasText(location)) {
            return ip + "(" + location + ")";
        }
        return ip;
    }

    /**
     * 获取 User-Agent
     */
    public String getUserAgent(HttpServletRequest request) {
        if (request == null) {
            return "";
        }
        String ua = request.getHeader("User-Agent");
        // 限制长度,避免超长 UA
        if (ua != null && ua.length() > 500) {
            ua = ua.substring(0, 500);
        }
        return ua;
    }
}