Ip2region --IP定位功能总结


前言

最近看到微博和抖音上线了IP定位功能,感觉挺有意思的。然后想了解一下是功能实现方式,也是这篇文档的初衷。
IP 定位服务很常见,而且很多公司都提供了类似的付费服务,比如阿里,高德,百度等,当然也有公开的免费服务,像 GeoIP等。这些服务要么通过 HTML 页面解析,要么通过接口请求,但不管怎样都离不开一次 http 请求,更不用说大部分服务都对 QPS 作了限制。下表枚举了一些常见的通过 IP 获取地址的方式。

开放API服务方式限制样例
淘宝IP地址库接口每个用户的QPS要小于1curl -d “ip=218.97.9.25&accessKey=alibaba-inc” http://ip.taobao.com/outGetIpInfo
高德地图接口每个用户每天有10万的访问限制,企业开发者有3000万的访问限制https://restapi.amap.com/v3/ip?ip=218.97.9.25&key=f4cf14aca974dfbb0501c582ce3fce77
GeoIPHTML解析每个用户的QPS要小于1curl -d “ip=218.97.9.25&submit=提交” https://www.geoip.com

在日常工作通常需要将大量用户请求日志中 IP 转换成用户位置信息,用作后续的分析。这其中的关键是数据量大,处理要快。显然每次都通过请求 API 公共服务的方式无法满足我们的日常需求。


一、Ip2region 简介

根据它获取一个具体ip的信息,通过IP解析出国家、具体地址、网络服务商等相关信息。

准确率99.9%的离线IP地址定位库,0.0x毫秒级查询,ip2region.db数据库只有数MB,提供了java、php、c、python、 nodejs、golang、c#等查询绑定和Binary,B树,内存三种查询算法。

Ip2region 为什么查询速度快?
全部的查询客户端单次查询都在0.x毫秒级别,内置了三种查询算法:
memory算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内,C语言的客户端单次查询在0.00x毫秒级别。
binary算法:基于二分查找,基于ip2region.db文件,不需要载入内存,单次查询在0.x毫秒级别。
b-tree算法:基于btree算法,基于ip2region.db文件,不需要载入内存,单词查询在0.x毫秒级别,比binary算法更快。
任何客户端b-tree都比binary算法快,当然memory算法固然是最快的!

官网地址:
https://github.com/lionsoul2014/ip2region

二、Ip2region 实际应用:

在开发中,可以记录登陆的日志信息,关于登陆者的ip和位置信息,可以通过ip2region来实现。

三、项目集成Ip2region

1. pom.xml 引入

代码如下(示例):

        <dependency>
            <groupId>org.lionsoul</groupId>
            <artifactId>ip2region</artifactId>
            <version>1.7.2</version>
        </dependency>

2.下载Ip2region.db

链接:https://pan.baidu.com/s/11SLxYfDnvH-3xVILa_Lutg 
提取码:lxnr 

将下载好的 Ip2region.db拷贝到 resources目录下

在这里插入图片描述


3.写工具类IPUtil

import java.io.File;
import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;
import org.lionsoul.ip2region.Util;

import javax.servlet.http.HttpServletRequest;

public class IPUtil {

    /**
     * 获取城市信息
     *
     * @param ip  ip
     * @return
     */
    public static String getCityInfo(String ip){
        //db
        String dbPath = IPUtil.class.getResource("/city/ip2region.db").getPath();
        File file = new File(dbPath);
        if ( file.exists() == false ) {
            System.out.println("Error: Invalid ip2region.db file");
        }
        //查询算法
        int algorithm = DbSearcher.BTREE_ALGORITHM; //B-tree
        //DbSearcher.BINARY_ALGORITHM //Binary
        //DbSearcher.MEMORY_ALGORITYM //Memory
        try {
            DbConfig config = new DbConfig();
            DbSearcher searcher = new DbSearcher(config, dbPath);
            //define the method
            Method method = null;
            switch ( algorithm )
            {
                case DbSearcher.BTREE_ALGORITHM:
                    method = searcher.getClass().getMethod("btreeSearch", String.class);
                    break;
                case DbSearcher.BINARY_ALGORITHM:
                    method = searcher.getClass().getMethod("binarySearch", String.class);
                    break;
                case DbSearcher.MEMORY_ALGORITYM:
                    method = searcher.getClass().getMethod("memorySearch", String.class);
                    break;
            }
            DataBlock dataBlock = null;
            if ( Util.isIpAddress(ip) == false ) {
                System.out.println("Error: Invalid ip address");
            }
            dataBlock  = (DataBlock) method.invoke(searcher, ip);
            return dataBlock.getRegion();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取客ip所在省份
     *
     * @param request 请求
     * @return
     */
    public static String getIpPossession(String ip) {
        String cityInfo = getCityInfo(ip);
        if (!StringUtils.isEmpty(cityInfo)) {
            cityInfo = cityInfo.replace("|", " ");
            String[] cityList = cityInfo.split(" ");
            if (cityList.length > 0) {
                // 国内的显示到具体的省
                if ("中国".equals(cityList[0])) {
                    if (cityList.length > 1) {
                        return cityList[2];
                    }
                }
                // 国外显示到国家
                return cityList[0];
            }
        }
        return "未知";
    }

    /**
     * 获取客户端ip
     *
     * @param request 请求
     * @return
     */
    public static String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        } else if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        } else if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        } else if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        } else if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    public static void main(String[] args)  throws Exception{
        System.err.println(getCityInfo("1.80.0.0"));
    }
}

执行结果:

    public static void main(String[] args)  throws Exception{
        System.err.println(getCityInfo("1.80.0.0"));
    }

    中国|0|陕西省|西安市|电信


    public static void main(String[] args)  throws Exception{
        System.err.println(getIpPossession("1.80.0.0"));
    }

    陕西省

每个 ip 数据段的 region 信息都固定了格式:国家|区域|省份|城市|ISP,只有中国的数据绝大部分精确到了城市,其他国家部分数据只能定位到国家,后前的选项全部是0。

4.写一个 ipaddress 测试接口

import com.example.ipdemo.utils.IPUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 控制器
 *
 * @author admin
 * @email ${email}
 * @date 2021-11-12 15:15:16
 */
@RestController
@RequestMapping("ipDemo")
@Log4j2
public class IpDemoController {

    private String getRemortIP()throws Exception {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = "";
        if (request.getHeader("x-forwarded-for") == null) {
            ip = request.getRemoteAddr();
        }else{
            ip = request.getHeader("x-forwarded-for");
        }
        return ip;
    }

    @RequestMapping(value = "ipaddress", method = RequestMethod.GET)
    @ResponseBody
    public String testTime() {
        String s = "";
        try {
            s = IPUtil.getCityInfo(getRemortIP());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return s;
    }
}

请求返回:
在这里插入图片描述

总结

ip2region 库解决了一个非常常见的 IP 定位问题,但将这个服务做到了又小又快(当然还提供了多语言的客户端),还是不可多得的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值