Selaa lähdekoodia

Update IP地址行政区域离线查询支持IPv6

Yue 1 viikko sitten
vanhempi
sitoutus
495af718d4

+ 4 - 4
SERVER/VberAdminPlusV3/pom.xml

@@ -22,15 +22,15 @@
         <java.version>17</java.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-        <spring-boot.version>3.5.6</spring-boot.version>
+        <spring-boot.version>3.5.8</spring-boot.version>
         <spring-boot-admin.version>3.5.5</spring-boot-admin.version>
-        <springdoc.version>2.8.13</springdoc.version>
+        <springdoc.version>2.8.14</springdoc.version>
         <mybatis.version>3.5.16</mybatis.version>
         <mybatis-plus.version>3.5.14</mybatis-plus.version>
         <dynamic-ds.version>4.3.1</dynamic-ds.version>
         <shardingsphere.version>5.5.0</shardingsphere.version>
         <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
-        <redisson.version>3.51.0</redisson.version>
+        <redisson.version>3.52.0</redisson.version>
         <xxl-job.version>2.4.1</xxl-job.version>
         <poi.version>5.2.3</poi.version>
         <fastexcel.version>1.3.0</fastexcel.version>
@@ -45,7 +45,7 @@
         <bouncycastle.version>1.80</bouncycastle.version>
         <justauth.version>1.16.7</justauth.version>
         <!-- 离线IP地址定位库 -->
-        <ip2region.version>2.7.0</ip2region.version>
+        <ip2region.version>3.3.4</ip2region.version>
 
         <!-- OSS 配置 -->
         <aws.sdk.version>2.28.22</aws.sdk.version>

BIN
SERVER/VberAdminPlusV3/vber-admin/src/main/resources/ip2region.xdb → SERVER/VberAdminPlusV3/vber-admin/src/main/resources/ip2region_v4.xdb


+ 9 - 37
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ip/AddressUtils.java

@@ -20,53 +20,25 @@ 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 (NetUtils.isInnerIP(ip)) {
-            return LOCAL_ADDRESS;
+        if (!isIPv4 && !isIPv6) {
+            return UNKNOWN_IP;
         }
-        return RegionUtils.getCityInfo(ip);
-    }
-
-    /**
-     * 根据IPv6地址查询IP归属行政区域
-     *
-     * @param ip ipv6地址
-     * @return 归属行政区域
-     */
-    private static String resolverIPv6Region(String ip) {
         // 内网不查询
-        if (NetUtils.isInnerIPv6(ip)) {
+        if ((isIPv4 && NetUtils.isInnerIP(ip)) || (isIPv6 && 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);
     }
 
-}
+}

+ 135 - 21
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ip/RegionUtils.java

@@ -1,50 +1,164 @@
 package com.vber.common.core.utils.ip;
 
-import cn.hutool.core.io.resource.NoResourceException;
 import cn.hutool.core.io.resource.ResourceUtil;
 import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.utils.StringUtils;
 import lombok.extern.slf4j.Slf4j;
-import org.lionsoul.ip2region.xdb.Searcher;
+
+import java.io.InputStream;
+import java.time.Duration;
+
+import org.lionsoul.ip2region.service.Config;
+import org.lionsoul.ip2region.service.Ip2Region;
+import org.lionsoul.ip2region.xdb.Util;
 
 /**
- * 根据ip地址定位工具类,离线方式
- * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
+ * IP地址行政区域工具类
+ * 参考地址:<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 Iwb
+ * @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";
+
+    // 默认IPv6地址库文件路径
+    // 下载地址:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
+    public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
+
+    // 默认缓存切片大小为15MB(仅针对BufferCache全量读取有效,如果你的xdb数据库很大,合理设置该值可以有效提升BufferCache模式下的查询效率,具体可以查看Ip2Region的README)
+    // 注意:设置过大的值可能会申请内存时,因内存不足而导致OOM,请合理设置该值。
+    // README:https://gitee.com/lionsoul/ip2region/tree/master/binding/java
+    public static final int DEFAULT_CACHE_SLICE_BYTES = 1024 * 1024 * 15;
+
+    // 未知地址
+    public static final String UNKNOWN_ADDRESS = "未知";
 
-    private static final Searcher SEARCHER;
+    // 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地址库数据不存在!");
+            // 注意:Ip2Region 的xdb文件加载策略 CachePolicy
+            // 有三种,分别是:BufferCache(全量读取xdb到内存中)、VIndexCache(默认策略,按需读取并缓存)、NoCache(实时读取)
+            // 本项目工具使用的 CachePolicy 为
+            // BufferCache,BufferCache会加载整个xdb文件到内存中,setXdbInputStream 仅支持 BufferCache 策略。
+            // 因为加载整个xdb文件会耗费非常大的内存,如果你不希望加载整个xdb到内存中,更推荐使用 VIndexCache 或
+            // NoCache(即实时读取文件)策略和 setXdbPath/setXdbFile 加载方法(需要注意的一点,setXdbPath 和
+            // setXdbFile 不支持读取ClassPath(即源码和resource目录)中的文件)。
+            // 一般而言,更建议把xdb数据库放到一个指定的文件目录中(即不打包进jar包中),然后使用 VIndexCache +
+            // 配合SearcherPool的并发池读取数据,更方便随时更新xdb数据库
+
+            InputStream v4InputStream = ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH);
+
+            // IPv4配置
+            Config v4Config = Config.custom()
+                    .setCachePolicy(Config.BufferCache)
+                    // .setXdbFile(v4TempXdb)
+                    .setXdbInputStream(v4InputStream)
+                    //
+                    .setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
+                    .asV4();
+
+            // IPv6配置
+            Config v6Config = null;
+            InputStream v6XdbInputStream = ResourceUtil.getStreamSafe(DEFAULT_IPV6_XDB_PATH);
+            if (v6XdbInputStream == null) {
+                log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6,请将 ip2region_v6.xdb 放置到 resources 目录",
+                        DEFAULT_IPV6_XDB_PATH);
+            } else {
+                v6Config = Config.custom()
+                        .setCachePolicy(Config.BufferCache)
+                        // .setXdbFile(v6TempXdb)
+                        .setXdbInputStream(v6XdbInputStream)
+                        .setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
+                        .asV6();
+            }
+
+            // 初始化Ip2Region实例
+            RegionUtils.ip2Region = Ip2Region.create(v4Config, v6Config);
+            log.debug("IP工具初始化成功,加载IP地址库数据成功!");
+        } catch (Exception e) {
+            throw new ServiceException("RegionUtils初始化失败,原因:{}", e.getMessage());
+        }
+    }
+
+    /**
+     * 根据IP地址离线获取城市
+     *
+     * @param ipString ip地址字符串
+     */
+    public static String getRegion(String ipString) {
+        try {
+            String region = ip2Region.search(ipString);
+            if (StringUtils.isBlank(region)) {
+                return UNKNOWN_ADDRESS;
+            }
+            return StringUtils.replace(region, "0", UNKNOWN_ADDRESS);
         } catch (Exception e) {
-            throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
+            log.error("IP地址离线获取城市异常 {}", ipString);
+            return UNKNOWN_ADDRESS;
         }
+
     }
 
     /**
      * 根据IP地址离线获取城市
+     *
+     * @param ipBytes ip地址字节数组
      */
-    public static String getCityInfo(String ip) {
+    public static String getRegion(byte[] ipBytes) {
+        try {
+            String region = ip2Region.search(ipBytes);
+            if (StringUtils.isBlank(region)) {
+                return UNKNOWN_ADDRESS;
+            }
+            return StringUtils.replace(region, "0", UNKNOWN_ADDRESS);
+        } 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 {
-            // 3、执行查询
-            String region = SEARCHER.search(StringUtils.trim(ip));
-            return region.replace("0|", "").replace("|0", "");
+            ip2Region.close(timeout.toMillis());
         } catch (Exception e) {
-            log.error("IP地址离线获取城市异常 {}", ip);
-            return "未知";
+            log.error("Ip2Region服务关闭异常", e);
         }
     }