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