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); } }