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..7c797a2d7 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 Ip2RegionHelper.getRegion(ip); } } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/Ip2RegionHelper.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/Ip2RegionHelper.java new file mode 100644 index 000000000..d52210a6d --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/Ip2RegionHelper.java @@ -0,0 +1,146 @@ +package org.dromara.common.core.utils.ip; + +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.service.Config; +import org.lionsoul.ip2region.service.Ip2Region; +import org.lionsoul.ip2region.xdb.Util; + +import java.io.File; +import java.time.Duration; + +/** + * IP地址行政区域助手工具类 + * 参考地址:ip2region xdb java 查询客户端实现 + * xdb数据库文件下载:ip2region data + * + * @author 秋辞未寒 + */ +@Slf4j +public class Ip2RegionHelper { + + // 默认IPv4地址库文件路径 + // 下载地址:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v4.xdb + public static final String DEFAULT_IPV4_XDB_PATH = "ip2region_v4.xdb"; + + // 默认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 { + // 创建临时文件用于处理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(FileUtil.createRandomAccessFile(v6TempXdb, FileMode.r)); + + // 初始化Ip2Region实例 + Ip2RegionHelper.ip2Region = Ip2Region.create(v4Config, null); +// Ip2RegionHelper.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 getRegion(byte[] ipBytes) { + try { + String region = ip2Region.search(ipBytes); + if (StringUtils.isBlank(region)) { + region = UNKNOWN_ADDRESS; + } + return region; + } catch (Exception e) { + 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); + } + } + +} 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 deleted file mode 100644 index c9e867899..000000000 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.dromara.common.core.utils.ip; - -import cn.hutool.core.io.resource.NoResourceException; -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; - -/** - * 根据ip地址定位工具类,离线方式 - * 参考地址:集成 ip2region 实现离线IP地址定位库 - * - * @author lishuyan - */ -@Slf4j -public class RegionUtils { - - // IP地址库文件名称 - public static final String IP_XDB_FILENAME = "ip2region.xdb"; - - private static final Searcher SEARCHER; - - 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地址离线获取城市 - */ - public static String getCityInfo(String ip) { - try { - // 3、执行查询 - String region = SEARCHER.search(StringUtils.trim(ip)); - return region.replace("0|", "").replace("|0", ""); - } catch (Exception e) { - log.error("IP地址离线获取城市异常 {}", ip); - return "未知"; - } - } - -}