!803 feat IP地址行政区域离线查询支持IPv6(已提供相关代码,开发者自行决定是否使用)

* update IP地址行政区域助手类重命名以匹配其工具类的功能定位
* feat IP地址行政区域离线查询支持IPv6(已提供相关代码,开发者自行决定是否使用)
This commit is contained in:
秋辞未寒
2025-12-11 05:23:09 +00:00
committed by 疯狂的狮子Li
parent ee09377997
commit 1165c8dc06
4 changed files with 126 additions and 58 deletions

View File

@@ -38,7 +38,7 @@
<bouncycastle.version>1.80</bouncycastle.version> <bouncycastle.version>1.80</bouncycastle.version>
<justauth.version>1.16.7</justauth.version> <justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 --> <!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version> <ip2region.version>3.3.0</ip2region.version>
<!-- OSS 配置 --> <!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version> <aws.sdk.version>2.28.22</aws.sdk.version>
<!-- SMS 配置 --> <!-- SMS 配置 -->

View File

@@ -20,51 +20,24 @@ public class AddressUtils {
public static final String UNKNOWN_IP = "XX XX"; public static final String UNKNOWN_IP = "XX XX";
// 内网地址 // 内网地址
public static final String LOCAL_ADDRESS = "内网IP"; public static final String LOCAL_ADDRESS = "内网IP";
// 未知地址
public static final String UNKNOWN_ADDRESS = "未知";
public static String getRealAddressByIP(String ip) { public static String getRealAddressByIP(String ip) {
// 处理空串并过滤HTML标签 // 处理空串并过滤HTML标签
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,"")); ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
// 判断是否为IPv4 // 判断是否为IPv4
if (NetUtils.isIPv4(ip)) { boolean isIPv4 = NetUtils.isIPv4(ip);
return resolverIPv4Region(ip);
}
// 判断是否为IPv6 // 判断是否为IPv6
if (NetUtils.isIPv6(ip)) { boolean isIPv6 = NetUtils.isIPv6(ip);
return resolverIPv6Region(ip);
}
// 如果不是IPv4或IPv6则返回未知IP // 如果不是IPv4或IPv6则返回未知IP
return UNKNOWN_IP; if (!isIPv4 && !isIPv6) {
} return UNKNOWN_IP;
}
/**
* 根据IPv4地址查询IP归属行政区域
* @param ip ipv4地址
* @return 归属行政区域
*/
private static String resolverIPv4Region(String ip){
// 内网不查询 // 内网不查询
if (NetUtils.isInnerIP(ip)) { if ((isIPv4 && NetUtils.isInnerIP(ip)) || (isIPv6 && NetUtils.isInnerIPv6(ip))) {
return LOCAL_ADDRESS; return LOCAL_ADDRESS;
} }
return RegionUtils.getCityInfo(ip); // TipsIp2Region 提供了精简的IPv6地址库精简的IPv6地址库并不能完全支持IPv6地址的查询且准确度上可能会存在问题如需要准确的IPv6地址查询建议自行实现
} return RegionUtils.getRegion(ip);
/**
* 根据IPv6地址查询IP归属行政区域
* @param ip ipv6地址
* @return 归属行政区域
*/
private static String resolverIPv6Region(String ip){
// 内网不查询
if (NetUtils.isInnerIPv6(ip)) {
return LOCAL_ADDRESS;
}
log.warn("ip2region不支持IPV6地址解析{}", ip);
// 不支持IPv6不再进行没有必要的IP地址信息的解析直接返回
// 如有需要可自行实现IPv6地址信息解析逻辑并在这里返回
return UNKNOWN_ADDRESS;
} }
} }

View File

@@ -1,50 +1,145 @@
package org.dromara.common.core.utils.ip; package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.resource.NoResourceException; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.io.resource.ResourceUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher; import org.lionsoul.ip2region.service.Config;
import org.lionsoul.ip2region.service.Ip2Region;
import org.lionsoul.ip2region.xdb.Util;
import java.io.File;
import java.time.Duration;
/** /**
* 根据ip地址定位工具类离线方式 * IP地址行政区域工具类
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a> * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">ip2region xdb java 查询客户端实现</a>
* xdb数据库文件下载<a href="https://gitee.com/lionsoul/ip2region/tree/master/data">ip2region data</a>
* *
* @author lishuyan * @author 秋辞未寒
*/ */
@Slf4j @Slf4j
public class RegionUtils { public class RegionUtils {
// IP地址库文件名称 // 默认IPv4地址库文件路径
public static final String IP_XDB_FILENAME = "ip2region.xdb"; // 下载地址https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v4.xdb
public static final String DEFAULT_IPV4_XDB_PATH = "ip2region_v4.xdb";
private static final Searcher SEARCHER; // 默认IPv6地址库文件路径
// 下载地址https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
// 未知地址
public static final String UNKNOWN_ADDRESS = "未知";
// Ip2Region服务实例
private static Ip2Region ip2Region;
// 初始化Ip2Region服务实例
static { static {
try { try {
// 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。 // 创建临时文件用于处理IP离线数据库xdb文件
// 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。 File v4TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME)); // File v6TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV6_XDB_PATH), FileUtil.createTempFile());
log.info("RegionUtils初始化成功加载IP地址库数据成功");
} catch (NoResourceException e) { // IPv4配置
throw new ServiceException("RegionUtils初始化失败原因IP地址库数据不存在"); Config v4Config = Config.custom()
} catch (Exception e) { .setCachePolicy(Config.BufferCache)
throw new ServiceException("RegionUtils初始化失败原因" + e.getMessage()); .setXdbPath(v4TempXdb.getPath())
.asV4();
// IPv6配置
// Config v6Config = Config.custom()
// .setCachePolicy(Config.BufferCache)
// .setXdbPath(v6TempXdb.getPath())
// .asV6();
// 初始化Ip2Region实例
RegionUtils.ip2Region = Ip2Region.create(v4Config, null);
// RegionUtils.ip2Region = Ip2Region.create(v4Config, v6Config);
// 删除临时文件
// 注意:因为使用的 CachePolicy 为 BufferCacheBufferCache是加载整个xdb文件到内存中所以临时文件的删除不会影响到正常的使用。如果使用的是 VIndexCache 或 NoCache即实时读取文件删除临时文件会导致xdb数据库读取不到而无法使用。
// CachePolicy的三种策略BufferCache全量读取xdb到内存中、VIndexCache按需读取并缓存、NoCache实时读取
// 一般而言更建议把xdb数据库放到一个指定的文件目录中即不打包进jar包中然后使用 NoCache + 配合SearcherPool的并发池读取数据更方便随时更新xdb数据库
v4TempXdb.delete();
// v6TempXdb.delete();
log.info("Ip2RegionHelper初始化成功加载IP地址库数据成功");
} catch (Exception e) {
throw new ServiceException("Ip2RegionHelper初始化失败原因{}", e.getMessage());
} }
} }
/**
* 根据IP地址离线获取城市
*
* @param ipString ip地址字符串
*/
public static String getRegion(String ipString) {
try {
String region = ip2Region.search(ipString);
if (StringUtils.isBlank(region)) {
region = UNKNOWN_ADDRESS;
}
return region;
} catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", ipString);
return UNKNOWN_ADDRESS;
}
}
/** /**
* 根据IP地址离线获取城市 * 根据IP地址离线获取城市
*
* @param ipBytes ip地址字节数组
*/ */
public static String getCityInfo(String ip) { public static String getRegion(byte[] ipBytes) {
try { try {
// 3、执行查询 String region = ip2Region.search(ipBytes);
String region = SEARCHER.search(StringUtils.trim(ip)); if (StringUtils.isBlank(region)) {
return region.replace("0|", "").replace("|0", ""); region = UNKNOWN_ADDRESS;
}
return region;
} catch (Exception e) { } catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", ip); log.error("IP地址离线获取城市异常 {}", Util.ipToString(ipBytes));
return "未知"; return UNKNOWN_ADDRESS;
}
}
/**
* 关闭Ip2Region服务
*/
public static void close() {
if (ip2Region == null) {
return;
}
try {
ip2Region.close(10000);
} catch (Exception e) {
log.error("Ip2Region服务关闭异常", e);
}
}
/**
* 关闭Ip2Region服务
*
* @param timeout 关闭超时时间
*/
public static void close(final Duration timeout) {
if (ip2Region == null) {
return;
}
if (timeout == null) {
close();
return;
}
try {
ip2Region.close(timeout.toMillis());
} catch (Exception e) {
log.error("Ip2Region服务关闭异常", e);
} }
} }