Java 中是如何获取 IP 属地的

细心的小伙伴可能会发现,抖音新上线了 IP 属地的功能,小伙伴在发表动态、发表评论以及聊天的时候,都会显示自己的 IP 属地信息

下面,我就来讲讲,Java 中是如何获取 IP 属地的,主要分为以下几步

  • 通过 HttpServletRequest 对象,获取用户的 IP 地址
  • 通过 IP 地址,获取对应的省份、城市

首先需要写一个 IP 获取的工具类,因为每一次用户的 Request 请求,都会携带上请求的 IP 地址放到请求头中。

public class IpUtil {    public static String getIpAddr(ServerHttpRequest request) {        HttpHeaders headers = request.getHeaders();        String ipAddress = headers.getFirst("X-Forwarded-For");        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {            ipAddress = headers.getFirst("Proxy-Client-IP");        }        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {            ipAddress = headers.getFirst("WL-Proxy-Client-IP");        }        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {            ipAddress = request.getRemoteAddress().getAddress().getHostAddress();            if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {                // 根据网卡取本机配置的IP                try {                    InetAddress inet = InetAddress.getLocalHost();                    ipAddress = inet.getHostAddress();                } catch (UnknownHostException e) {                    log.error("根据网卡获取本机配置的IP异常", e);                }             }        }         // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割        if (ipAddress != null && ipAddress.indexOf(",") > 0) {            ipAddress = ipAddress.split(",")[0];        }         return ipAddress;    }}

这里有三个名词,分别是

  • X-Forwarded-For:一个 HTTP 扩展头部,主要是为了让 Web 服务器获取访问用户的真实 IP 地址。每个 IP 地址,每个值通过逗号+空格分开,最左边是最原始客户端的 IP 地址,中间如果有多层代理,每⼀层代理会将连接它的客户端 IP 追加在 X-Forwarded-For 右边。
  • X-Real-IP:一般只记录真实发出请求的客户端IP
  • Proxy-Client-IP:这个一般是经过 Apache http 服务器的请求才会有,用 Apache http 做代理时一般会加上 Proxy-Client-IP 请求头
  • WL-Proxy-Client-IP:也是通过 Apache http 服务器,在 weblogic 插件加上的头。

在我们获取到用户的 IP 地址后,那么就可以获取对应的 ip 信息了

我在 Github 冲浪的时候,发现了 Ip2region 项目。

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

数据聚合了一些知名 ip 到地名查询提供商的数据,这些是他们官方的的准确率,经测试着实比经典的纯真 IP 定位准确一些。ip2region 的数据聚合自以下服务商的开放 API 或者数据。

  • 80%, 淘宝IP地址库, http://ip.taobao.com/
  • ≈10%, GeoIP, https://geoip.com/
  • ≈2%, 纯真IP库, http://www.cz88.net/

备注:如果上述开放API或者数据都不给开放数据时ip2region将停止数据的更新服务。

每条 ip 数据段都固定了格式:

_城市Id|国家|区域|省份|城市|ISP_

只有中国的数据精确到了城市,其他国家有部分数据只能定位到国家,后前的选项全部是 0,已经包含了全部你能查到的大大小小的国家

生成的数据库文件 ip2region.db 只有几 MB,最小的版本只有 1.5MB,随着数据的详细度增加数据库的大小也慢慢增大,目前还没超过 8MB

内置的三种查询算法

全部的查询客户端单次查询都在 0.x 毫秒级别,内置了三种查询算法

  • memory 算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内,C语言的客户端单次查询在0.00x毫秒级别。
  • binary 算法:基于二分查找,基于ip2region.db文件,不需要载入内存,单次查询在0.x毫秒级别。
  • b-tree 算法:基于btree算法,基于ip2region.db文件,不需要载入内存,单词查询在0.x毫秒级别,比binary算法更快。

ip2region安装

下面,就让我们给项目引入 ip2region,进行 ip 信息转换吧

首先引入 maven 依赖

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

然后编写一个工具类 IpUtils ,首先需要加载 ip2region.db 文件

static {    dbPath = createFtlFileByFtlArray() + "ip2region.db";    try {        config = new DbConfig();    } catch (DbMakerConfigException e) {        e.printStackTrace();    }    try {        searcher = new DbSearcher(config, dbPath);    } catch (FileNotFoundException e) {        e.printStackTrace();    }}

在加载的时候,需要下载仓库中的 ip2region.db 文件,然后放到 resource 目录下

然后,通过内置的三种算法,分别转换用户 ip 地址

    public static String getCityInfo(String ip) {         if (StringUtils.isEmpty(dbPath)) {            log.error("Error: Invalid ip2region.db file");            return null;        }        if(config == null || searcher == null){            log.error("Error: DbSearcher or DbConfig is null");            return null;        }         //查询算法        //B-tree, B树搜索(更快)        int algorithm = DbSearcher.BTREE_ALGORITHM;         //Binary,使用二分搜索        //DbSearcher.BINARY_ALGORITHM         //Memory,加载内存(最快)        //DbSearcher.MEMORY_ALGORITYM        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;                default:            }             DataBlock dataBlock = null;            if (Util.isIpAddress(ip) == false) {                System.out.println("Error: Invalid ip address");            }             dataBlock = (DataBlock) method.invoke(searcher, ip);            String ipInfo = dataBlock.getRegion();            if (!StringUtils.isEmpty(ipInfo)) {                ipInfo = ipInfo.replace("|0", "");                ipInfo = ipInfo.replace("0|", "");            }            return ipInfo;         } catch (Exception e) {            e.printStackTrace();        }         return null;    }

下面,我们编写 main 函数进行测试,发现可以正常的解析出 ip 信息

由于 ip 属地在国内的话,只会展示省份,而国外的话,只会展示国家。所以我们还需要对这个方法进行一下封装,得到获取 IP 属地的信息。

/** * 获取IP属地 * @param ip * @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[1];                }            }            // 国外显示到国家            return cityList[0];        }    }    return "未知";}

下面,我们在找一个 国外的 IP 测试一下效果。可以看到已经能够正常的显示 IP 属地信息了~

到这里如果获取用户的 IP 属地已经完成啦,如果想要了解关于更多 ip2region 的功能,欢迎访问其 Github 地址进行学习。

                                             项目地址

https://github.com/lionsoul2014/ip2region

码文不易,学到的小伙伴点一点关注哦,一键三连加收藏哦!!!

*************************~~~

全部评论

相关推荐

牛客257271999号:之前在这个组实习过 氛围还是不错的 学的东西也不错 唯一的就是午休很短 一点就上班了
点赞 评论 收藏
分享
10-16 11:44
已编辑
门头沟学院 Java
先叠个甲:避雷客户端!客户端校招都是资本家的减税工具!招聘软件被要简历就给了,面着体验下,还是客户端友好啊反问交流基本都20min起,面试官都会积极交流或给建议岗位是iOS,一面1h,当天下午约二面;二面1h,第二天约hr;三面hr&nbsp;20min多,当场约主管面【一面】1.&nbsp;进程、线程、协程2.&nbsp;进程间通信方式3.&nbsp;讲讲实习和项目中的多线程场景,后端、android、IOS都说说4.&nbsp;网络请求的响应码知道哪些5.&nbsp;各版本的http的特点6.&nbsp;数组、链表、map的区别及应用7.&nbsp;实习客户端埋点都有哪些内容?8.&nbsp;讲到卡顿,客户端、服务端卡顿一般有什么原因?9.&nbsp;多线程并发场景题,一个线程写、多个线程读,假设写要10s,同时进行不阻塞用户操作,服务端和客户端你会怎么设计?(异步写,无强一致性要求就读副本,有就轮询/事件订阅,写好再读)10.&nbsp;实习画UI?那讲讲IOS的复用及原理(不会,原理还没看,大概讲了如何/为什么要reusable)11.&nbsp;点击button或者其他UIview到响应的原理12.&nbsp;讲讲实习中印象最深的需求后面聊了点redis运维监控相关,感觉面试官也懂不少后端的东西手撕:LRU反问:1.&nbsp;OC需要深入学习吗?(重点了解runtime特性)2.&nbsp;那边用UIKit还是swiftUI?(都有,但swiftUI坑优点多)3.&nbsp;经常用cocoapod,那ruby需要深入了解下吗(脚本语言用gpt就好了)4.&nbsp;流程(技术+技术/项目+总监+HR)【二面】1.&nbsp;redis缓存与分布式锁,原理及潜在问题2.&nbsp;auth2.0授权码模式3.&nbsp;飞书登陆接口怎么做的4.&nbsp;授权码模式下,客户端如何防范CRSF攻击5.&nbsp;最近做的IOS需求有啥?碰到困难怎么解决的6.&nbsp;IOS如何获取当前top的vc7.&nbsp;客户端埋点做了那些内容,有碰到什么坑吗8.&nbsp;读取图片的过程,如果图片读取时间很长可能是什么原因9.&nbsp;包体积怎么优化10.&nbsp;IOS不太会,面试官:那聊聊你擅长的,对ai在端侧的应用场景有什么看法手撕:螺旋矩阵反问:1.&nbsp;大前端学习建议:flutter没什么必要,大厂不太用,react/vue可以学一下2.&nbsp;对于网上客户端言论的看法:ai&nbsp;native商业模式能成功的话可能是个新的需求点。具体个人竞争力还是看个人,后端摆烂也都是crud,建议现在做的端智能方向之后可以深入点。3.&nbsp;流程:3轮技术+1轮hr4.&nbsp;讨论了下native&nbsp;ai盈利的可能商业模式,面试官说他们海外业务已经开始盈利了“但怎么盈利的肯定不能告诉你”(跟现在实习单位算是竞对)【hr面】唠家常,反问时候问流程,hr直接当场查主管日程发邮件🤣【四面】主管https://www.nowcoder.com/share/jump/6401346151729002178897 #24届软开秋招面试经验大赏#&nbsp;&nbsp;#你都收到了哪些公司的感谢信?#
点赞 评论 收藏
分享
5 4 评论
分享
牛客网
牛客企业服务