通过IP计算分析归属地

小程序数据库算法

在产品中可能存在不同客户端,请求同一个服务端接口的场景。

例如小程序和App或者浏览器中,如果需要对请求的归属地进行分析,前提是需要先获取请求所在的国家或城市,这种定位通常需要主动授权,而用户一般是不愿意提供的,就需要通过请求的IP来进行归属地计算。

IP地址一般分为两种,IPV4和IPV6,相应的计算方式也有差异,以国家维度来参考,每个国家都有对应的网段范围,计算网段中的最小和最大IP地址的对应数值,然后对比请求的IP地址,来判断属于哪个国家的网段范围。

  
import cn.hutool.core.net.Ipv4Util;  
import cn.hutool.core.util.StrUtil;  
import java.math.BigInteger;  
import java.net.InetAddress;  
  
publicclass IpCalculate {  
    public static void main(String[] args) throws Exception {  
        // IPv4 网段  
        String ipv4Network = "IPv4 网段";  
        String[] ipv4Param = StrUtil.splitToArray(ipv4Network, "/");  
  
        // IPv4 起始和结束IP  
        String ipv4StartIp = Ipv4Util.getBeginIpStr(ipv4Param[0],Integer.parseInt(ipv4Param[1]));  
        String ipv4OverIp = Ipv4Util.getEndIpStr(ipv4Param[0],Integer.parseInt(ipv4Param[1]));  
        System.out.println(ipv4StartIp);  
        System.out.println(ipv4OverIp);  
  
        // IPv4 起始和结束IP对应的Long值  
        System.out.println(Ipv4Util.ipv4ToLong(ipv4StartIp));  
        System.out.println(Ipv4Util.ipv4ToLong(ipv4OverIp));  
  
        // IPv6 网段  
        String ipv6Network = "IPv6 网段";  
        String[] ipv6Param =ipv6Network.split("/");  
        int prefixLength = Integer.parseInt(ipv6Param[1]);  
  
        // IPv6 起始和结束IP  
        InetAddress baseAddress = InetAddress.getByName(ipv6Param[0]);  
        BigInteger baseValue = new BigInteger(1, baseAddress.getAddress());  
        BigInteger mask = BigInteger.ONE.shiftLeft(128).subtract(BigInteger.ONE)  
                .shiftRight(128 - prefixLength).shiftLeft(128 - prefixLength);  
        BigInteger minIp = baseValue.and(mask);  
        BigInteger maxIp = minIp.add(BigInteger.ONE.shiftLeft(128 - prefixLength).subtract(BigInteger.ONE));  
        System.out.println(toIPv6String(minIp));  
        System.out.println(toIPv6String(maxIp));  
  
        // IPv6 起始和结束IP对应的Long值  
        System.out.println(minIp);  
        System.out.println(maxIp);  
    }  
  
    private static String toIPv6String(BigInteger value) throws Exception {  
        byte[] bytes = value.toByteArray();  
        byte[] ipv6Bytes = newbyte[16];  
        int start = bytes.length > 16 ? bytes.length - 16 : 0;  
        int length = Math.min(bytes.length, 16);  
        System.arraycopy(bytes, start, ipv6Bytes, 16 - length, length);  
        return InetAddress.getByAddress(ipv6Bytes).getHostAddress();  
    }  
}  

不过网段地址和国家的对应关系需要进行维护,如果归属地分析不需要非常精准,可以直接使用开源的字典库,比如使用比较多的就是GeoIP2组件。

  
<dependency>  
  <groupId>com.maxmind.geoip2groupId>  
  <artifactId>geoip2</artifactId>  
</dependency>  

通过组件中提供的API加载相应的文件字典,然后传入IP地址进行归属地判断,这里要注意争议和敏感地区的处理,如果出错产品可不止是上热搜的问题了。

  
import com.maxmind.geoip2.DatabaseReader;  
import java.io.File;  
import java.net.Inet4Address;  
import java.net.Inet6Address;  
import java.net.InetAddress;  
  
publicclass GeoIpTool {  
    public static void main(String[] args) throws Exception {  
  
        // 读取IP库文件  
        File ipFile = new File("IP文件库");  
        DatabaseReader reader = new DatabaseReader.Builder(ipFile).build();  
  
        // IPV4地址  
        InetAddress ipV4 = InetAddress.getByName("IPV4");  
        if (ipV4 instanceof Inet4Address){  
            System.out.println(reader.country(ipV4));  
            System.out.println(reader.country(ipV4).getCountry());  
            // 默认英文名  
            System.out.println(reader.country(ipV4).getCountry().getName());  
            // 查询中文名  
            System.out.println(reader.country(ipV4).getCountry().getNames().get("zh-CN"));  
        }  
  
        // IPV6地址  
        InetAddress ipV6 = InetAddress.getByName("IPV6");  
        if (ipV6 instanceof Inet6Address){  
            System.out.println(reader.country(ipV6));  
            System.out.println(reader.country(ipV6).getCountry());  
            // 默认英文名  
            System.out.println(reader.country(ipV6).getCountry().getName());  
            // 查询中文名  
            System.out.println(reader.country(ipV6).getCountry().getNames().get("zh-CN"));  
        }  
    }  
}  
  

如果需要非常精确的实时归属地分析,可以购买专业的IP网段数据,实时更新到本地的数据库中,作为IP字典使用,获取请求的IP后,直接范围匹配即可。

  
CREATE TABLE`ip\_place` (  
`id`bigint(20) NOTNULL AUTO\_INCREMENT,  
`network`varchar(100) DEFAULTNULLCOMMENT'网段区间',  
`min\_ip`bigint(20) DEFAULTNULLCOMMENT'最小IP',  
`max\_ip`bigint(20) DEFAULTNULLCOMMENT'最大IP',  
`min\_ip\_number`bigint(20) DEFAULTNULLCOMMENT'最小IP数值',  
`max\_ip\_number`bigint(20) DEFAULTNULLCOMMENT'最大IP数值',  
`ip\_place`varchar(100) DEFAULTNULLCOMMENT'归属地',  
  PRIMARY KEY (`id`)  
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='IP归属地';  

最后需要补充说一句,对于很多标准的数据,尽可能在项目最初就设计好字典枚举或者数据表,避免后续规范时面临数据清洗的问题。

#Java #IP技术

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
云原生数据库 veDB 核心技术剖析与展望
veDB 是一款分布式数据库,采用了云原生计算存储分离架构。本次演讲将为大家介绍火山引擎这款云原生数据库的核心技术原理,并对未来进行展望。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论