草庐IT

Java IP归属地查询(离线方式+在线方式,内附查询IP方法)

唯有代码不会骗人 2025-07-09 原文

一、离线方式

1.1. 下载 ip2region.xdb

GitHub项目地址:https://github.com/lionsoul2014/ip2region
我们首先需要下载一个 ip2region.xdb 的文件
下载地址:https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.xdb
打开后点击如图的Download图标即可下载。

下载完成后,需要将该文件放到我们的项目中。
ps:我是直接放到服务器的,因为放在项目的资源文件夹下,当我们调试的时候使用Java Spring自带的工具去获取该文件的绝对路径时,没有任何问题,能够正常获取到。但是当我们打成jar包部署到测试或者开发环境,就会拿不到该文件,具体情况可以看 spring boot集成,db放在resource下,打jar包后可以加载资源,但search不生效。本地debug生效 该Issues,如果不想麻烦的话直接放在服务器上吧,这样我们就能直接配好绝对路径了,少走弯路。

1.2. 代码实现

首先需要导包:

	<dependency>
	    <groupId>org.lionsoul</groupId>
	    <artifactId>ip2region</artifactId>
	    <version>2.6.5</version>
	</dependency>
	/**
     * 通过ip地址获取所属国家-离线方式
     *
     * @param ip        IP地址
     * @param dbPath    ip2region.xdb路径
     * @return      国家信息
     */
	public static String getCountryByIpOffline(String ip, String dbPath) throws Exception{
        // 1、从 dbPath 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
        byte[] vIndex = Searcher.loadVectorIndexFromFile(dbPath);
        // 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
        Searcher searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
        // 3、查询
        return searcher.search(ip);
    }

其中dbPath我是走的nacos配置,这个路径就是我们服务器存放ip2region.xdb的路径。

1.3. 查询结果解析

ip2region xdb 数据结构和查询过程的详解:ip2region xdb 数据结构和查询过程的详解
这篇文章写得很清楚我就不再叙述了。
ps:需要特别提一点的是,该方式查询国内的IP还是比较准确的,基本上能精确到省市。但是国外的一些IP,大体的国家信息没啥问题,再具体的信息可能就不全了。但是一般项目都够用。

查询结果展示:

查询结果统一采用的是 国家|区域|省份|城市|ISP 的方式返回。

二、在线方式

2.1 接口地址

在线方式网页测试:在线方式网页测试


只需要通过GET请求该链接 http://ip-api.com/json/ 并后面拼接你的ip,如 http://ip-api.com/json/24.48.0.1 即可。
也能通过添加lang参数让该接口返回中文。如 http://ip-api.com/json/24.48.0.1?lang=zh-CN,就可以返回中文数据了。

2.2 代码实现

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;

@Slf4j
@Component
public class IpUtil {

    private static RestTemplate restTemplate;

    private final RestTemplate template;

    public static final String IP_API_URL = "http://ip-api.com/json/";

    public IpUtil(RestTemplate restTemplate) {
        this.template = restTemplate;
    }

    /**
     * 初始化 RestTemplate
     */
    @PostConstruct
    public void init() {
        setRestTemplate(this.template);
    }

    /**
     * 初始化 RestTemplate
     */
    private static void setRestTemplate(RestTemplate template) {
        restTemplate = template;
    }

    /**
     * 通过ip地址获取所属国家-在线方式
     *
     * @param ip    ip地址
     * @return      国家信息
     */
    public static CountryInfo getCountryByIpOnline(String ip) {
        ResponseEntity<CountryInfo> entity = restTemplate.getForEntity(
                IP_API_URL + ip + "?lang=zh-CN",
                CountryInfo.class
        );
        return entity.getBody();
    }

    /**
     * 在线方式接收类
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class CountryInfo {
        private String query;
        private String status;
        private String country;
        private String countryCode;
        private String region;
        private String regionName;
        private String city;
        private String zip;
        private String lat;
        private String lon;
        private String timezone;
        private String isp;
        private String org;
        private String as;
    }

}

代码也非常简单。

三、使用建议

这里我推荐我们项目中都统一采用先离线后在线方式兜底的写法来处理。因为谁也不能保证离线的就一定能查询到具体的归属地,如果我们使用离线方式查询不到具体的归属地(获取结果为null),那么我建议再使用在线查询的方式来做一个兜底处理,如果都拿不到,再返回空,或者根据自己的业务具体处理。
以上仅作为建议。

四、获取请求IP地址

这里附带一下查询请求的ip地址方法,有需要的可以直接拿走。

/**
 * 获取Ip地址
 *
 * @author dxc
 * @date 2022/7/12 18:18
 */
public final class IpUtil {

    private static final String UNKNOWN = "unknown";

    private IpUtil() {}

    /**
     * 获取客户端ip 地址
     *
     * @param request 请求
     * @return {@link String}
     */
    public static String getClientIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");

        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_CLUSTER_CLIENT_IP");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_FORWARDED_FOR");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_FORWARDED");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_VIA");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("REMOTE_ADDR");
        }
        if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        // 多次反向代理后会有多个ip值,第一个ip才是真实ip
        int index = ip.indexOf(",");
        if (index != -1) {
            return ip.substring(0, index);
        } else {
            return ip;
        }
    }
}

有关Java IP归属地查询(离线方式+在线方式,内附查询IP方法)的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  5. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  6. Ruby 方法() 方法 - 2

    我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

  7. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  8. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  9. ruby - 完全离线安装RVM - 2

    我打算为ruby​​脚本创建一个安装程序,但我希望能够确保机器安装了RVM。有没有一种方法可以完全离线安装RVM并且不引人注目(通过不引人注目,就像创建一个可以做所有事情的脚本而不是要求用户向他们的bash_profile或bashrc添加一些东西)我不是要脚本本身,只是一个关于如何走这条路的快速指针(如果可能的话)。我们还研究了这个很有帮助的问题:RVM-isthereawayforsimpleofflineinstall?但有点误导,因为答案只向我们展示了如何离线在RVM中安装ruby。我们需要能够离线安装RVM本身,并查看脚本https://raw.github.com/wayn

  10. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

随机推荐