引言
做后端开发,ip归属地查询绝对是高频刚需场景:用户访问日志埋点、地域权限风控、用户地域画像、站点访问统计,几乎处处都能用得上。之前项目里用过不少第三方在线ip查询接口,踩坑无数——高峰期接口限流、服务不稳定宕机,还存在用户ip隐私合规风险,后来改用ip2region这款纯离线开源查询方案,直接解决了所有痛点。
对比下来,基于ip2region搭建的离线ip查询方案,才更适配生产场景,全程本地运行、零外网依赖,单ip查询速度达微秒级,常规场景精准度完全够用,而且免费开源、无调用限制。这篇文章全程只讲实操、不堆砌理论,代码复制即可运行,从环境配置到生产避坑一站式讲透,直接照搬就能用到正式项目里。
一、项目环境与核心资源
先同步实操环境版本,避开版本兼容坑,本文全程基于ip2region 3.3.6最新稳定版开发,适配springboot3生态:
- jdk 17+
- springboot 3.2.5
- ip2region 3.3.6
核心资源说明:ip2region 官方拆分了ipv4和ipv6两套独立离线数据库,需分别下载对应xdb文件,这是实现双协议查询的关键,旧版单文件无法同时支持两种ip,必须下载官方最新版双库文件,避免数据不匹配、查询失败问题。
ip2region双库文件下载地址(gitee官方): 1. ipv4专用库:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v4.xdb 2. ipv6专用库:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb 下载后不要重命名、不要修改后缀,严格保留原名区分双库,贴合ip2region官方加载规范。
二、maven依赖引入
打开项目 pom.xml,直接引入ip2region核心依赖,springboot3 无需额外适配包,极简引入即可,额外推荐引入hutool工具包,简化后续ip获取和字符串处理,生产项目必备:
<!-- ip2region 离线ip归属地查询核心依赖 最新3.3.6版本 -->
<dependency>
<groupid>org.lionsoul</groupid>
<artifactid>ip2region</artifactid>
<version>3.3.6</version>
</dependency>
<!-- 可选:hutool工具包,简化ip获取和字符串处理,生产常用 -->
<dependency>
<groupid>cn.hutool</groupid>
<artifactid>hutool-all</artifactid>
<version>5.8.25</version>
</dependency>依赖刷新完成后,直接进入下一步数据库文件配置,全程贴合ip2region官方api写法,无需额外编写复杂配置类,上手即用。
三、xdb数据库文件放置
这一步很关键,ip2region双库文件必须规范放置,避免加载冲突,亲测最优路径方案:
- 在项目 resources 目录下,直接新建文件夹 ipdb(无需额外建子目录)
- 将下载好的 ip2region_v4.xdb(ipv4库)、ip2region_v6.xdb(ipv6库),直接放入 resources/ipdb 目录下
- 严格保留文件原名,不重命名、不修改后缀,防止ip2region加载路径匹配失败
文件放置完成后,目录结构参考下图:

重点避坑:打包时如果出现 xdb 文件丢失、损坏,需要在 pom.xml 的 build 配置中,禁止压缩 xdb 后缀文件,后面避坑章节会详细讲,双库文件都需要生效该配置。
四、封装通用ip查询工具类
ip2region 官方针对ipv4、ipv6双库做了统一api封装,不用手动维护两个独立searcher,直接通过config分别配置双库、再由ip2region统一托管即可,使用更简洁、稳定性更强。我采用单例模式,项目启动时一次性加载双库文件,避免重复io占用内存,服务关闭时统一释放资源,完全适配springboot3资源加载规范,全局可直接调用,代码复制即可落地:
import jakarta.annotation.postconstruct;
import jakarta.annotation.predestroy;
import lombok.extern.slf4j.slf4j;
import org.lionsoul.ip2region.service.config;
import org.lionsoul.ip2region.service.ip2region;
import org.springframework.core.io.classpathresource;
import org.springframework.stereotype.component;
import java.io.inputstream;
/**
* 离线ip归属地查询工具类
* 遵循官方双库规范:ipv4+ipv6分开配置,统一托管查询
* 完整覆盖所有内网ip段判断,杜绝公网查询误判
* 单例模式,全局复用,避免重复加载数据库
*/
@slf4j
@component
public class ipregionutil {
/**
* ipv4、ipv6双库文件路径,对应resources/ipdb目录
* 严格对应ip2region官方原文件名,不修改、不重命名
*/
private static final string ipv4_xdb_path = "ipdb/ip2region_v4.xdb";
private static final string ipv6_xdb_path = "ipdb/ip2region_v6.xdb";
/**
* ip2region官方统一查询对象,托管双库,无需手动拆分searcher
*/
private ip2region ip2region;
/**
* 项目启动初始化,加载ip2region双库文件
* 采用buffercache缓存策略,兼顾查询速度与内存占用
*/
@postconstruct
public void init() {
try {
// 加载ipv4数据库配置
classpathresource v4resource = new classpathresource(ipv4_xdb_path);
inputstream v4is = v4resource.getinputstream();
config v4config = config.custom()
.setcachepolicy(config.buffercache)
.setsearchers(10)
.setxdbinputstream(v4is)
.asv4();
// 加载ipv6数据库配置
classpathresource v6resource = new classpathresource(ipv6_xdb_path);
inputstream v6is = v6resource.getinputstream();
config v6config = config.custom()
.setcachepolicy(config.buffercache)
.setsearchers(10)
.setxdbinputstream(v6is)
.asv6();
// 统一创建ip2region查询对象
ip2region = ip2region.create(v4config, v6config);
log.info("ip2region初始化成功,ipv4+ipv6双库加载完成");
} catch (exception e) {
log.error("ip2region初始化失败,请检查双库文件路径和完整性", e);
throw new runtimeexception("ip2region异常,无法提供ip查询服务");
}
}
/**
* 统一ip查询入口,自动识别ipv4/ipv6
* 完整过滤所有内网ip,包含10段、172段、192段、本地回环
*/
public string getipregion(string ip) {
try {
// 空ip直接返回
if (ip == null || ip.isblank()) {
return "内网ip|内网ip";
}
// 判断是否为内网ip,包含ipv4全内网段 + ipv6回环
if (isprivateip(ip)) {
return "内网ip|内网ip";
}
// 调用ip2region执行公网ip查询
return ip2region.search(ip);
} catch (exception e) {
log.error("ip2region ip查询异常,ip:{}", ip, e);
return "查询失败";
}
}
/**
* 完整内网ip判断逻辑
* ipv4内网范围:
* 1. 127.0.0.1 本地回环
* 2. 10.0.0.0 ~ 10.255.255.255
* 3. 172.16.0.0 ~ 172.31.255.255
* 4. 192.168.0.0 ~ 192.168.255.255
* ipv6内网:::1 / 0:0:0:0:0:0:0:1 回环地址
*/
private boolean isprivateip(string ip) {
// ipv4内网判断
if (ip.contains(".")) {
// 本地回环
if ("127.0.0.1".equals(ip)) {
return true;
}
// 192.168网段
if (ip.startswith("192.168.")) {
return true;
}
// 10网段
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 == 4) {
try {
int secondsegment = integer.parseint(parts[1]);
return secondsegment >= 16 && secondsegment <= 31;
} catch (numberformatexception e) {
return false;
}
}
}
return false;
}
// ipv6回环地址判断
return "::1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip);
}
/**
* 服务关闭,释放ip2region资源
*/
@predestroy
public void close() {
try {
if (ip2region != null) {
ip2region.close();
log.info("ip2region资源释放完成");
}
} catch (exception e) {
log.error("ip2region资源释放失败", e);
}
}
}工具类核心说明(贴合ip2region 3.3.6官方规范):
- 严格遵循ip2region官方api规范,采用config+ip2region统一托管双库,摒弃旧版searcher手动维护模式,代码更简洁、无调用冲突,兼容性拉满
- 全内网ip覆盖判断:完整包含 10 段、172.16-31 段、192.168 段、ipv4 回环及 ipv6 回环地址,彻底杜绝内网ip误查公网
- 自动识别ipv4/ipv6地址,一套接口适配双协议,业务调用无需区分ip类型,无感兼容双网络环境
- 选用buffercache缓存策略,平衡查询速度与内存占用,低配服务器可灵活切换为nocache,适配不同部署环境
- spring bean单例生命周期管理,项目启动一次性加载双库,服务关闭自动释放资源,杜绝内存泄漏,长期运行稳定
- 全局异常捕获完善,启动失败直接抛出明确异常,方便运维快速排查,避免后续空指针问题
五、编写测试接口,快速验证双协议功能
写一个极简的controller测试接口,支持两种查询模式:手动传入指定ip查询(兼容ipv4+ipv6)、自动获取当前请求ip查询,适配本地调试、nginx代理、ipv6部署场景,基于ip2region快速验证功能是否正常:
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.requestparam;
import org.springframework.web.bind.annotation.restcontroller;
import javax.servlet.http.httpservletrequest;
@slf4j
@restcontroller
@requestmapping("/ip")
@requiredargsconstructor
public class ipcontroller {
private final ipregionutil ipregionutil;
/**
* 手动传入ip查询归属地
* @param ip 目标ip
* @return 归属地结果
*/
@getmapping("/query")
public string queryip(@requestparam string ip) {
return ipregionutil.getipregion(ip);
}
/**
* 获取当前请求的真实ip并查询归属地
* 适配nginx、反向代理场景,解决内网ip获取问题
*/
@getmapping("/current")
public string getcurrentipregion(httpservletrequest request) {
// 优先获取代理转发的真实客户端ip
string realip = request.getheader("x-real-ip");
if (realip == null || realip.isblank()) {
realip = request.getremoteaddr();
}
log.info("当前请求真实客户端ip:{}", realip);
return ipregionutil.getipregion(realip);
}
}六、启动项目,接口测试
1. 启动springboot项目,观察控制台日志,出现 ip2region初始化成功,ipv4+ipv6双库加载完成 字样,说明双库文件加载正常,核心工具初始化完成

2. 打开postman或者浏览器,调用测试接口:
- 公网ipv4测试:http://localhost:8080/api/ip/query?ip=114.114.114.114
- 内网ipv4测试(172段):http://localhost:8080/api/ip/query?ip=172.16.2.3
- 内网ipv4测试(10段):http://localhost:8080/api/ip/query?ip=10.0.0.5
- 公网ipv6测试:http://localhost:8080/api/ip/query?ip=2408:8888:8888::8888
当前请求ip查询:http://localhost:8080/api/ip/current
ipv4正常返回结果示例:中国|江苏省|南京市|0|cn
ipv6正常返回结果示例:中国|北京|北京市|0|cn

各类内网ip(10段、172段、192段、本地回环)测试结果:内网ip|内网ip,过滤逻辑完全生效

七、常见坑与解决方案
1. 打包后找不到xdb文件,项目启动失败
原因:maven打包时,默认压缩资源文件,导致ip2region依赖的xdb文件损坏;或者双库文件路径写错、少放其中一个库文件。
解决方案:在 pom.xml 的 build 节点中,添加以下配置,禁止压缩 xdb 后缀文件:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>*.xdb</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>*.xdb</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>2. 反向代理后,获取不到真实客户端ip
生产项目基本都用nginx代理,直接用request.getremoteaddr()获取的是服务器内网ip,需要nginx配置转发真实ip,确保ip2region能查询到真实用户ip:
proxy_set_header x-real-ip $remote_addr; proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
java代码中优先读取 x-real-ip 请求头,和我上面controller代码一致。
八、生产进阶用法
- 结果格式化:ip2region默认返回带|分隔符,可封装成vo对象,拆分国家、省份、城市、isp字段,返回前端更友好,ipv4/ipv6查询结果格式完全一致
- 本地缓存:高频查询ip,加caffeine或redis缓存,减少重复调用ip2region,提升性能,ipv4和ipv6地址可共用一套缓存逻辑
- ipv6部署适配:服务器开启ipv6后,无需修改代码,接口可直接接收ipv6地址,工具类自动识别并调用ipv6专用库查询,开箱即用
- 定时更新双库:ip2region官方会单独更新ipv4、ipv6数据库,定时下载对应最新xdb文件,替换对应目录文件,重启服务即可,保证双协议数据精准度
九、总结
springboot3集成ip2region实现离线ip查询,全程不到15分钟就能落地,纯本地运行、零外网依赖、严格遵循官方双库规范支持ipv4+ipv6、查询性能拉满,完全能满足中小项目到生产级别的ip查询需求,不管是传统ipv4网络,还是新部署的ipv6环境,都能稳定适配。整个过程没有复杂配置,工具类和接口代码直接复用即可,文中的避坑方案都是实战踩坑总结,照着操作基本不会出现异常。
对比第三方在线接口,ip2region离线方案更稳定、更安全,还没有调用次数限制,也不用担心隐私合规风险,强烈建议大家把项目里的在线ip接口替换成这套方案,上手成本极低,实用性拉满!
以上就是springboot3集成ip2region实现离线ip查询方案的详细内容,更多关于springboot3 ip2region离线ip查询的资料请关注代码网其它相关文章!
发表评论