package org.dromara.common.core.utils.ip; 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.InputStream; import java.time.Duration; /** * IP地址行政区域工具类 * 参考地址:ip2region xdb java 查询客户端实现 * xdb数据库文件下载:ip2region data * * @author 秋辞未寒 */ @Slf4j public class RegionUtils { // 默认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 = "未知"; // Ip2Region服务实例 private static Ip2Region ip2Region; // 初始化Ip2Region服务实例 static { try { // 注意: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)) { 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); } } }