From 1165c8dc0652bc766b99d4a33ae4fbd9aa97a89c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E8=BE=9E=E6=9C=AA=E5=AF=92?= <545073804@qq.com> Date: Thu, 11 Dec 2025 05:23:09 +0000 Subject: [PATCH] =?UTF-8?q?!803=20feat=20IP=E5=9C=B0=E5=9D=80=E8=A1=8C?= =?UTF-8?q?=E6=94=BF=E5=8C=BA=E5=9F=9F=E7=A6=BB=E7=BA=BF=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=94=AF=E6=8C=81IPv6=EF=BC=88=E5=B7=B2=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E8=80=85=E8=87=AA=E8=A1=8C=E5=86=B3=E5=AE=9A=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=EF=BC=89=20*=20update=20IP=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E8=A1=8C=E6=94=BF=E5=8C=BA=E5=9F=9F=E5=8A=A9=E6=89=8B=E7=B1=BB?= =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=E4=BB=A5=E5=8C=B9=E9=85=8D=E5=85=B6?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=E7=9A=84=E5=8A=9F=E8=83=BD=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D=20*=20feat=20IP=E5=9C=B0=E5=9D=80=E8=A1=8C=E6=94=BF?= =?UTF-8?q?=E5=8C=BA=E5=9F=9F=E7=A6=BB=E7=BA=BF=E6=9F=A5=E8=AF=A2=E6=94=AF?= =?UTF-8?q?=E6=8C=81IPv6=EF=BC=88=E5=B7=B2=E6=8F=90=E4=BE=9B=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=BC=80=E5=8F=91=E8=80=85?= =?UTF-8?q?=E8=87=AA=E8=A1=8C=E5=86=B3=E5=AE=9A=E6=98=AF=E5=90=A6=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../{ip2region.xdb => ip2region_v4.xdb} | Bin .../common/core/utils/ip/AddressUtils.java | 43 +----- .../common/core/utils/ip/RegionUtils.java | 139 +++++++++++++++--- 4 files changed, 126 insertions(+), 58 deletions(-) rename ruoyi-admin/src/main/resources/{ip2region.xdb => ip2region_v4.xdb} (100%) diff --git a/pom.xml b/pom.xml index 8b603c29e..ee9aef2df 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.80 1.16.7 - 2.7.0 + 3.3.0 2.28.22 diff --git a/ruoyi-admin/src/main/resources/ip2region.xdb b/ruoyi-admin/src/main/resources/ip2region_v4.xdb similarity index 100% rename from ruoyi-admin/src/main/resources/ip2region.xdb rename to ruoyi-admin/src/main/resources/ip2region_v4.xdb diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java index 2fe3bd7b6..fe36d9cc0 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java @@ -20,51 +20,24 @@ public class AddressUtils { public static final String UNKNOWN_IP = "XX XX"; // 内网地址 public static final String LOCAL_ADDRESS = "内网IP"; - // 未知地址 - public static final String UNKNOWN_ADDRESS = "未知"; public static String getRealAddressByIP(String ip) { // 处理空串并过滤HTML标签 ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,"")); // 判断是否为IPv4 - if (NetUtils.isIPv4(ip)) { - return resolverIPv4Region(ip); - } + boolean isIPv4 = NetUtils.isIPv4(ip); // 判断是否为IPv6 - if (NetUtils.isIPv6(ip)) { - return resolverIPv6Region(ip); - } + boolean isIPv6 = NetUtils.isIPv6(ip); // 如果不是IPv4或IPv6,则返回未知IP - return UNKNOWN_IP; - } - - /** - * 根据IPv4地址查询IP归属行政区域 - * @param ip ipv4地址 - * @return 归属行政区域 - */ - private static String resolverIPv4Region(String ip){ + if (!isIPv4 && !isIPv6) { + return UNKNOWN_IP; + } // 内网不查询 - if (NetUtils.isInnerIP(ip)) { + if ((isIPv4 && NetUtils.isInnerIP(ip)) || (isIPv6 && NetUtils.isInnerIPv6(ip))) { return LOCAL_ADDRESS; } - return RegionUtils.getCityInfo(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; + // Tips:Ip2Region 提供了精简的IPv6地址库,精简的IPv6地址库并不能完全支持IPv6地址的查询,且准确度上可能会存在问题,如需要准确的IPv6地址查询,建议自行实现 + return RegionUtils.getRegion(ip); } } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java index c9e867899..a2d210c10 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java @@ -1,50 +1,145 @@ 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 lombok.extern.slf4j.Slf4j; import org.dromara.common.core.exception.ServiceException; 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地址定位工具类,离线方式 - * 参考地址:集成 ip2region 实现离线IP地址定位库 + * IP地址行政区域工具类 + * 参考地址:ip2region xdb java 查询客户端实现 + * xdb数据库文件下载:ip2region data * - * @author lishuyan + * @author 秋辞未寒 */ @Slf4j public class RegionUtils { - // IP地址库文件名称 - public static final String IP_XDB_FILENAME = "ip2region.xdb"; + // 默认IPv4地址库文件路径 + // 下载地址: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 { try { - // 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。 - // 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。 - SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME)); - log.info("RegionUtils初始化成功,加载IP地址库数据成功!"); - } catch (NoResourceException e) { - throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!"); - } catch (Exception e) { - throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage()); + // 创建临时文件用于处理IP离线数据库xdb文件 + File v4TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile()); +// File v6TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV6_XDB_PATH), FileUtil.createTempFile()); + + // IPv4配置 + Config v4Config = Config.custom() + .setCachePolicy(Config.BufferCache) + .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 为 BufferCache,BufferCache是加载整个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地址离线获取城市 + * + * @param ipBytes ip地址字节数组 */ - public static String getCityInfo(String ip) { + public static String getRegion(byte[] ipBytes) { try { - // 3、执行查询 - String region = SEARCHER.search(StringUtils.trim(ip)); - return region.replace("0|", "").replace("|0", ""); + String region = ip2Region.search(ipBytes); + if (StringUtils.isBlank(region)) { + region = UNKNOWN_ADDRESS; + } + return region; } catch (Exception e) { - log.error("IP地址离线获取城市异常 {}", ip); - return "未知"; + log.error("IP地址离线获取城市异常 {}", Util.ipToString(ipBytes)); + 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); } }