Java获取IP地址及对应的归属地

目录

前言

一、获取访问的IP地址

二、通过IP地址获取对应的归属地

2.1 Ip2region

2.1.1 高达 99.9 % 的查询准确率

2.1.2 Ip2region V2.0 特性

2.1.3 多语言以及查询客户端的支持

2.2 Ip2region xdb Java 查询客户端实现

2.2.1 引入 Maven 仓库

2.2.2 ip2region.xdb 文件,放到工程resources目录下

2.2.3 实现方式

2.2.3.1 基于文件查询

2.2.3.2 缓存VectorIndex索引

2.2.3.3 缓存整个 xdb 数据

2.2.3.4 通过第三方API查询(在线查询)

2.2.3.5 最优方案


前言

        细心的朋友们可能已经发现了,先在抖音、知乎、快手、小红书等这些平台已经上线了“网络用户显示 IP 的功能”,境外用户显示的是国家,国内的用户显示的省份,而且此项显示无法关闭,归属地强制显示。

作为一个努力搬砖的码农,我们肯定要来看一下这个功能是如何实现的,今天这篇文章,就来讲述一下这个功能是怎么实现的。


一、获取访问的IP地址

HttpServletRequest 获取 IP

首先我们来看一下,在 Java 中,是如何获取到 IP 属地的,主要有以下两步:

  • 通过 HttpServletRequest 对象,获取用户的 【IP】 地址

  • 通过 IP 地址,获取对应的【省份、城市】

我这里写一个工具类用于获取 IP 地址,因为用户的每次 Request 请求都会携带请求的 IP 地址放到请求头中,所以我们可以通过截取请求中的 IP 来获取 IP 地址,代码如下:

package com.test.java.util;import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Objects;/*** IP地址Util*/
@Slf4j
public class IpAddressUtil {/*** 获取请求的 IP 地址*/public static String getIpAddress(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();if ("127.0.0.1".equals(ip)) {// 根据网卡取本机配置的 IPInetAddress inet = null;try {inet = InetAddress.getLocalHost();} catch (Exception e) {e.printStackTrace();log.error("获取IP地址异常,{}", e.getMessage());}if (inet != null) {ip = inet.getHostAddress();}}}// 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割if (ip != null && ip.length() > 15) {if (ip.indexOf(",") > 0) {ip = ip.substring(0, ip.indexOf(","));}}// 本机访问if ("localhost".equalsIgnoreCase(ip) || "127.0.0.1".equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)) {// 根据网卡取本机配置的IPInetAddress inet;try {inet = InetAddress.getLocalHost();ip = inet.getHostAddress();} catch (Exception e) {e.printStackTrace();log.error("获取本机IP地址异常,{}", e.getMessage());}}// 如果查找不到 IP,可以返回 127.0.0.1,可以做一定的处理,但是这里不考虑// if (ip == null) {//     return "127.0.0.1";// }return ip;}/*** 获取IP地址*/public static String getIpAddress(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 = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {// 根据网卡取本机配置的IPtry {InetAddress inet = InetAddress.getLocalHost();ipAddress = inet.getHostAddress();} catch (Exception e) {log.error("获取IP地址异常,{}", e.getMessage());}}}// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割if (ipAddress != null && ipAddress.indexOf(",") > 0) {ipAddress = ipAddress.split(",")[0];}return ipAddress;}/*** 获取mac地址*/public static String getMacIpAddress() {try {InetAddress inetAddress = InetAddress.getLocalHost();byte[] macAddressBytes = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress();// 将mac地址拼装成StringStringBuilder sb = new StringBuilder();for (int i = 0; i < macAddressBytes.length; i++) {if (i != 0) {sb.append("-");}// mac[i] & 0xFF 是为了把byte转化为正整数String s = Integer.toHexString(macAddressBytes[i] & 0xFF);sb.append(s.length() == 1 ? 0 + s : s);}return sb.toString().trim().toUpperCase();} catch (Exception e) {log.error("Mac获取IP地址异常,{}", e.getMessage());}return "";}}

这里出现了三个名词:

  • 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地址获取对应的归属地

通过第三方地址库 Ip2region,获取IP归属地。

2.1 Ip2region

Ip2region 是一个 Gthub 的开源项目,即 Ip2region 开源项目。

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

这个开源库目前已经更新到了 V2 的版本,现在的它是一个强大的离线IP地址定位库和IP定位数据管理框架,其达到了微秒级别的查询效率,还提供了众多主流编程语言的 xdb 数据生成和查询客户端实现,可以说是非常得好用👍👍👍👍

2.1.1 高达 99.9 % 的查询准确率

数据聚合了一些知名 ip 到地名查询提供商的数据,这些是他们官方的准确率,经测试着实比经典的纯真 IP 定位准确一些。

ip2region 的数据聚合自以下服务商的开放 API 或者数据(升级程序每秒请求次数 2 到 4 次),比例如下:

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

2.1.2 Ip2region V2.0 特性

  • IP 数据管理框架

xdb 支持亿级别的 IP 数据段行数,默认的 region 信息都固定了格式:国家|区域|省份|城市|ISP,缺省的地域信息默认是0。

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

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

region 信息支持完全自定义,例如:你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编等。也就是你完全可以使用 ip2region 来管理你自己的 IP 定位数据。

  • 数据去重和压缩

xdb 格式生成程序会自动去重和压缩部分数据,默认的全部 IP 数据,生成的 ip2region.xdb 数据库是 11MiB,随着数据的详细度增加数据库的大小也慢慢增大。

  • 极速查询响应

即使是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:

  1. vIndex 索引缓存:使用固定的 512KiB 的内存空间缓存 vector index 数据,减少一次 IO 磁盘操作,保持平均查询效率稳定在10-20微秒之间
  2. xdb 整个文件缓存:将整个 xdb 文件全部加载到内存,内存占用等同于 xdb 文件大小,无磁盘 IO 操作,保持微秒级别的查询效率。

  • 内置的三种查询算法

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

  1. memory 算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内,C语言的客户端单次查询在0.00x毫秒级别。

  2. binary 算法:基于二分查找,基于ip2region.db文件,不需要载入内存,单次查询在0.x毫秒级别。

  3. b-tree 算法:基于btree算法,基于ip2region.db文件,不需要载入内存,单词查询在0.x毫秒级别,比binary算法更快。

2.1.3 多语言以及查询客户端的支持

已经有的客户端:Java、C#、php、C、Python、Node.js、PHP 拓展(PHP 5 和 PHP 7)等,主要如下:

binding描述开发状态binary查询耗时b-tree查询祥时memory查询耗时
CANSC C binding已完成0.0x毫秒0.0x毫秒0.00x毫秒
C#C# binding已完成0.x毫秒0.x毫秒0.1x毫秒
Golanggolang binding已完成0.x毫秒0.x毫秒0.1x毫秒
Javajava binding已完成0.x毫秒0.x毫秒0.1x毫秒
Lualua实现 binding已完成0.x毫秒0.x毫秒0.x毫秒
Lua_clua的c扩展已完成0.0x毫秒0.0x毫秒0.00x毫秒
nginxnginx的c扩展已完成0.0x毫秒0.0x毫秒0.00x毫秒
nodejsnodejs已完成0.x毫秒0.x毫秒0.1x毫秒
phpphp实现 binding已完成0.x毫秒0.1x毫秒0.1x毫秒
php5_ extphp5的c扩展已完成0.0x毫秒0.0x毫秒0.00x毫秒
php7_ extphp7的c扩展已完成0.0x毫秒0.0x毫秒0.00x毫秒
pythonpython bindng已完成0.x毫秒0.x毫秒0.x毫秒
rustrust binding已完成0.x毫秒0.x毫秒0.x毫秒

2.2 Ip2region xdb Java 查询客户端实现

这里简单展示一下 Java 的实现,这里使用开发中常用的 Maven 实现的方式:

2.2.1 引入 Maven 仓库

<!-- IP地址转归属地 -->
<dependency><groupId>org.lionsoul</groupId><artifactId>ip2region</artifactId><version>2.6.4</version>
</dependency>

2.2.2 ip2region.xdb 文件,放到工程resources目录下

这个xdb文件我放到网盘里,大伙可以直接下载,或者直接去GitHub上访问下载。

百度网盘地址:ip2region.xdb

提取码:4399

2.2.3 实现方式

2.2.3.1 基于文件查询
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;import java.util.concurrent.TimeUnit;/*** IP地址Util*/
@Slf4j
public class IpAddressUtil {// ip2region.xdb 文件地址常量(本地xdb文件路径)public static String XDB_PATH = "D:\\IDEA2022.2.3\\workspace\\java\\src\\main\\resources\\ip\\ip2region.xdb";/*** 完全基于ip2region.xdb文件,对用户ip地址进行转换* 注:并发调用时,每个线程需创建一个独立的searcher对象 单独使用。*/public static String getIpPossessionByFile(String ip) {if (StringUtils.isNotEmpty(ip)) {try {// 1、创建 searcher 对象Searcher searcher = Searcher.newWithFileOnly(XDB_PATH);// 2、查询long sTime = System.nanoTime();String region = searcher.search(ip);long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);region = region.replace("|0", "");log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);return region;} catch (Exception e) {log.error("获取IP地址异常:{} ", e.getMessage());throw new RuntimeException("获取IP地址异常");}}return "未知";}}
2.2.3.2 缓存VectorIndex索引

我们可以提前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;import java.util.concurrent.TimeUnit;/*** IP地址Util*/
@Slf4j
public class IpAddressUtil {// ip2region.xdb 文件地址常量(本地xdb文件路径)public static String XDB_PATH = "D:\\IDEA2022.2.3\\workspace\\java\\src\\main\\resources\\ip\\ip2region.xdb";/*** 缓存 VectorIndex 索引,对用户ip地址进行转换* 注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局变量 vIndex 缓存。*/public static String getCityInfoByVectorIndex(String ip) {if (StringUtils.isNotEmpty(ip)) {try {// 1、从 XDB_PATH 中预先加载 VectorIndex 缓存,并且作为全局变量,后续反复使用。byte[] vIndex = Searcher.loadVectorIndexFromFile(XDB_PATH);// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。Searcher searcher = Searcher.newWithVectorIndex(XDB_PATH, vIndex);// 3、查询long sTime = System.nanoTime();String region = searcher.search(ip);long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);region = region.replace("|0", "");log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);return region;} catch (Exception e) {log.error("获取IP地址异常:{} ", e.getMessage());throw new RuntimeException("获取IP地址异常");}}return "未知";}}
2.2.3.3 缓存整个 xdb 数据

我们也可以预先加载整个 ip2region.xdb 的数据到内存,然后基于这个数据创建查询对象来实现完全基于文件的查询,类似之前的 memory search。

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;import java.util.concurrent.TimeUnit;/*** IP地址Util*/
@Slf4j
public class IpAddressUtil {// ip2region.xdb 文件地址常量public static String XDB_PATH = "D:\\java\\src\\main\\resources\\ip\\ip2region.xdb";/*** 缓存整个 xdb 数据,对用户ip地址进行转换* 注:并发使用时,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。*/public static String getCityInfoByMemorySearch(String ip) {if (StringUtils.isNotEmpty(ip)) {try {// 1、从 XDB_PATH 加载整个 xdb 到内存。byte[] cBuff = Searcher.loadContentFromFile(XDB_PATH);// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。Searcher searcher = Searcher.newWithBuffer(cBuff);// 3、查询long sTime = System.nanoTime();String region = searcher.search(ip);long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);region = region.replace("|0", "");log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);return region;} catch (Exception e) {log.error("获取IP地址异常:{} ", e.getMessage());throw new RuntimeException("获取IP地址异常");}}return "未知";}}
2.2.3.4 通过第三方API查询(在线查询)

前面介绍的3种方法都是离线查询,该方法主要通过第三方提供的官网或API接口去实现在线查询的功能,但有个弊端就是特别依赖对方的服务器,一旦对方的服务器宕机就无法访问了。具体实现效果跟之前介绍的离线查询方法是一样的。

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;/*** IP地址Util*/
@Slf4j
public class IpAddressUtil {/*** 在线查询IP归属地*/public static String getIpAddressByOnline(String ip) {try {//1、创建 URLConnctionURL url = new URL("http://ip-api.com/json/" + ip + "?lang=zh-CN");//2、设置connection的属性HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("GET");connection.setConnectTimeout(20000);connection.setReadTimeout(20000);connection.setRequestProperty("content-type", "application/json; charset=utf-8");//3.连接connection.connect();//4.获取内容InputStream inputStream = connection.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));String line;StringBuilder sb = new StringBuilder();while ((line = br.readLine()) != null) {sb.append(line);}br.close();//System.out.println(sb);String str = sb.toString();if (StringUtils.isNotEmpty(str)) {// string转mapGson gson = new Gson();Map<String, Object> map = new HashMap<>();map = gson.fromJson(str, map.getClass());String country = (String) map.get("country");String city = (String) map.get("city");String regionName = (String) map.get("regionName");System.out.println("国家:" + country);System.out.println("城市:" + city);System.out.println("地区:" + regionName);return country + "|" + city + "|" + regionName;}} catch (Exception e) {log.error("在线查询IP地址异常,{}", e.getMessage());throw new RuntimeException(e.getMessage());}return null;}}
2.2.3.5 最优方案

其实我推荐可以将方法结合使用。先采用离线查询,如果发现地址为null的话,则调用在线查询方法。这样在一定的程度上能够保证数据的完整性。完整的工具类如下:

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;/*** IP地址Util*/
@Slf4j
public class IpAddressUtil {// ip2region.xdb 文件地址常量(本地xdb文件路径)public static String XDB_PATH = "D:\\IDEA2022.2.3\\workspace\\java\\src\\main\\resources\\ip\\ip2region.xdb";/*** 获取IP地址:*/public static String getIpAddress(HttpServletRequest request) {String ipAddress = null;try {ipAddress = request.getHeader("X-Forwarded-For");if (ipAddress != null && ipAddress.length() != 0 && !"unknown".equalsIgnoreCase(ipAddress)) {// 多次反向代理后会有多个ip值,第一个ip才是真实ipif (ipAddress.contains(",")) {ipAddress = ipAddress.split(",")[0];}}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("WL-Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("HTTP_CLIENT_IP");}if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getRemoteAddr();}} catch (Exception e) {log.error("获取IP地址异常,{}", e.getMessage());}return ipAddress;}/*** 获取mac地址*/public static String getMacIpAddress() {try {InetAddress inetAddress = InetAddress.getLocalHost();byte[] macAddressBytes = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress();// 将mac地址拼装成StringStringBuilder sb = new StringBuilder();for (int i = 0; i < macAddressBytes.length; i++) {if (i != 0) {sb.append("-");}// mac[i] & 0xFF 是为了把byte转化为正整数String s = Integer.toHexString(macAddressBytes[i] & 0xFF);sb.append(s.length() == 1 ? 0 + s : s);}return sb.toString().trim().toUpperCase();} catch (Exception e) {log.error("Mac获取IP地址异常,{}", e.getMessage());}return "";}/*** 方法一:完全基于ip2region.xdb文件,对用户ip地址进行转换* 注:并发调用时,每个线程需创建一个独立的searcher对象 单独使用。*/public static String getIpPossessionByFile(String ip) {if (StringUtils.isNotEmpty(ip)) {try {// 1、创建 searcher 对象Searcher searcher = Searcher.newWithFileOnly(XDB_PATH);// 2、查询long sTime = System.nanoTime();String region = searcher.search(ip);long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);region = region.replace("|0", "");//log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);return region;} catch (Exception e) {log.error("获取IP地址异常:{} ", e.getMessage());throw new RuntimeException("获取IP地址异常");}}return "未知";}/*** 方法二:缓存 VectorIndex 索引,对用户ip地址进行转换* 注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局变量 vIndex 缓存。*/public static String getCityInfoByVectorIndex(String ip) {if (StringUtils.isNotEmpty(ip)) {try {// 1、从 XDB_PATH 中预先加载 VectorIndex 缓存,并且作为全局变量,后续反复使用。byte[] vIndex = Searcher.loadVectorIndexFromFile(XDB_PATH);// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。Searcher searcher = Searcher.newWithVectorIndex(XDB_PATH, vIndex);// 3、查询long sTime = System.nanoTime();String region = searcher.search(ip);long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);region = region.replace("|0", "");//log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);return region;} catch (Exception e) {log.error("获取IP地址异常:{} ", e.getMessage());throw new RuntimeException("获取IP地址异常");}}return "未知";}/*** 方法三:缓存整个 xdb 数据,对用户ip地址进行转换* 注:并发使用时,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。*/public static String getCityInfoByMemorySearch(String ip) {if (StringUtils.isNotEmpty(ip)) {try {// 1、从 XDB_PATH 加载整个 xdb 到内存。byte[] cBuff = Searcher.loadContentFromFile(XDB_PATH);// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。Searcher searcher = Searcher.newWithBuffer(cBuff);// 3、查询long sTime = System.nanoTime();String region = searcher.search(ip);long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);region = region.replace("|0", "");//log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost);return region;} catch (Exception e) {log.error("获取IP地址异常:{} ", e.getMessage());throw new RuntimeException("获取IP地址异常");}}return "未知";}/*** 方法四:在线获取IP地址* 注:通过别人或者官网提供的API接口去实现查询的功能,弊端就是特别依赖别人的服务器,一旦服务器宕机就无法访问了。*/public static String getIpAddressByOnline(String ip) {try {//1、创建 URLConnctionURL url = new URL("http://ip-api.com/json/" + ip + "?lang=zh-CN");//2、设置connection的属性HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("GET");connection.setConnectTimeout(20000);connection.setReadTimeout(20000);connection.setRequestProperty("content-type", "application/json; charset=utf-8");//3.连接connection.connect();//4.获取内容InputStream inputStream = connection.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));String line;StringBuilder sb = new StringBuilder();while ((line = br.readLine()) != null) {sb.append(line);}br.close();//System.out.println(sb);String str = sb.toString();if (StringUtils.isNotEmpty(str)) {// string转mapGson gson = new Gson();Map<String, Object> map = new HashMap<>();map = gson.fromJson(str, map.getClass());String country = (String) map.get("country");String city = (String) map.get("city");String regionName = (String) map.get("regionName");//log.info("【国家】{},【城市】{},【地区】{}", country, city, regionName);return country + "|" + city + "|" + regionName;}} catch (Exception e) {log.error("在线查询IP地址异常,{}", e.getMessage());throw new RuntimeException("在线查询IP地址异常");}return null;}/*** 根据IP地址 获取归属地*/public static String getIpPossession(String ipAddress) {if (StringUtils.isNotEmpty(ipAddress)) {ipAddress = ipAddress.replace("|", " ");String[] cityList = ipAddress.split(" ");if (cityList.length > 0) {// 国内的显示到具体的省if ("中国".equals(cityList[0])) {if (cityList.length > 1) {return cityList[1];}}// 国外显示到国家return cityList[0];}}return "未知";}public static void main(String[] args) {String ip = "183.162.252.0";// 国内IPString abroadIp = "48.119.248.100"; // 国外IPSystem.out.println("方法一(国内):" + getIpPossessionByFile(ip));System.out.println("方法二(国内):" + getCityInfoByVectorIndex(ip));System.out.println("方法三(国内):" + getCityInfoByMemorySearch(ip));System.out.println("方法四(国内):" + getIpAddressByOnline(ip));System.out.println("方法一(国外):" + getIpPossessionByFile(abroadIp));System.out.println("方法二(国外):" + getCityInfoByVectorIndex(abroadIp));System.out.println("方法三(国外):" + getCityInfoByMemorySearch(abroadIp));System.out.println("方法四(国外):" + getIpAddressByOnline(abroadIp));//System.out.println("归属地(国内):" + getIpPossession(getCityInfoByVectorIndex(ip)));//System.out.println("归属地(国外):" + getIpPossession(getCityInfoByVectorIndex(abroadIp)));}
}

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/613873.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【.NET Core】可为null类型详解

【.NET Core】可为null类型详解 文章目录 【.NET Core】可为null类型详解一、概述二、可为空的值类型2.1 声明和赋值2.2 检查可为空值类型2.3 基础类型与可为空的值类型互换2.4 可为空的值类型装箱和取消装箱2.5 如何确定可为空的值类型 三、可为 null 的引用类型 一、概述 nu…

用通俗易懂的方式讲解:在 Langchain 中建立一个多模态的 RAG 管道

写在前面 语言模型的出现彻底改变了我们从文件中提取信息的方式。然而&#xff0c;我们知道图片&#xff0c;通常是图表和表格&#xff0c;经常包含关键信息&#xff0c;但基于文本的语言模型无法处理媒体文件。 例如&#xff0c;我们以前只能使用 PDF 文件中的文本来查找答案…

C#编程-实现线程声明周期

实现线程声明周期 当System.Threading.Thread类的对象被创建的时候,线程的生命周期开始。线程的生命周期在完成任务时结束。在线程的生命周期中有各种状态。这些状态是: 未启动状态可运行状态不可运行状态死亡状态下图显示了线程的各种状态和引起线程从一个状态变为另一个状…

欢乐的周末 - 华为OD统一考试

OD统一考试 题解: Java / Python / C++ 题目描述 小华和小为是很要好的朋友,他们约定周末一起吃饭。 通过手机交流,他们在地图上选择了多个聚餐地点(由于自然地形等原因,部分聚餐地点不可达)。求小华和小为都能到达的聚餐地点有多少个? 输入描述 第一行输入m和n,m代表…

C练习——递归求第n个人年龄

题目&#xff1a; 有n个人坐在一起&#xff0c;第n个人比第n-1个人大2岁&#xff0c;第n-1个人比第n-2个人大2岁&#xff0c;以此类推&#xff0c;……&#xff0c;第1个人是10岁。请问第n个人年龄多大&#xff1f; 解析&#xff1a; 简单循环也能求解 但按题意要求递归求解…

Spark SQL进阶

DataFrame详解 清洗相关API 去重API 删除空缺值的API 替换缺失值的API from pyspark import SparkConf, SparkContext import os from pyspark.sql import SparkSession# 绑定指定的Python解释器 os.environ[SPARK_HOME] /export/server/spark os.environ[PYSPARK_PYTHON]…

渗透线应用-取料呼叫FC(SCL源代码)

渗透线应用相关文章可以参考下面文章链接: https://rxxw-control.blog.csdn.net/article/details/135526725https://rxxw-control.blog.csdn.net/article/details/135526725渗透线小车控制 https://rxxw-control.blog.csdn.net/article/details/133611151

【算法】基础算法001之双指针

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.数组分块&#xf…

【JaveWeb教程】(20) MySQL数据库开发之 基本查询、条件查询、聚合函数、分组查询、排序查询、分页查询 详细代码示例讲解

目录 1. 数据库操作-DQL1.1 介绍1.2 语法1.3 基本查询1.4 条件查询1.5 聚合函数1.6 分组查询1.7 排序查询1.8 分页查询1.9 案例1.9.1 案例一1.9.2 案例二 在上次学习的内容中&#xff0c;我们讲解了&#xff1a; 使用DDL语句来操作数据库以及表结构&#xff08;数据库设计&…

C++学习笔记(三十二):c++ 堆内存与栈内存比较

本节对堆和栈内存进行描述。 应用程序启动后&#xff0c;操作系统将整个程序加载到内存&#xff0c;分配相应的物理ram&#xff0c;确保程序可以正常运行。堆和栈是ram中存在的两个区域。栈通常是一个预定义大小的内存区域&#xff0c;一般是2M字节左右。堆也是预定了默认值的…

12、JVM高频面试题

1、JVM的主要组成部分有哪些 JVM主要分为下面几部分 类加载器&#xff1a;负责将字节码文件加载到内存中 运行时数据区&#xff1a;用于保存java程序运行过程中需要用到的数据和相关信息 执行引擎&#xff1a;字节码文件并不能直接交给底层操作系统去执行&#xff0c;因此需要…

NumPy 数据操作实用指南:从基础到高效(下)

文章接上篇&#xff1a; In [53]: from PIL import Image In [60]: dog Image.open(./dog.jpg) dog . . . In [61]: dog_datanp.array(dog) # 图片数据是ndarray # 彩色照片三维&#xff1a;高度&#xff0c;宽度&#xff0c;像素&#xff08;表示不同颜色&#xff09;&…

C语言操作符与表达式详解

目录 操作符的分类&#xff1a; &#xff08;1&#xff09;算数操作符 &#xff08;2&#xff09;移位操作符 &#xff08;3&#xff09;位操作符 &#xff08;4&#xff09;赋值操作符 &#xff08;5&#xff09;单目操作符 &#xff08;6&#xff09;关系操作符 &…

CSS 选择器全攻略:从入门到精通(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

pytorch11:模型加载与保存、finetune迁移训练

目录 一、模型加载与保存1.1 序列化与反序列化概念1.2 pytorch中的序列化与反序列化1.3 模型保存的两种方法1.4 模型加载两种方法 二、断点训练2.1 断点保存代码2.2 断点恢复代码 三、finetune3.1 迁移学习3.2 模型的迁移学习3.2 模型微调步骤3.2.1 模型微调步骤3.2.2 模型微调…

Asp .Net Core 系列: 集成 CORS跨域配置

文章目录 什么是CORS?Asp .Net Core 种如何配置CORS?CorsPolicyBuilder类详解注册以及使用策略三种方式EnableCors 和 DisableCors 特性关于带证书与不带证书代码的实现跨源&#xff08;cross-origin&#xff09;不带请求证书(Credentials)跨源&#xff08;cross-origin&…

c++析构函数

析构函数的简述 1. 析构函数和构造函数类似&#xff0c;是c规定当对象的生命周期结束时&#xff0c;默认你会调用析构函数。 2. 同理&#xff0c;当我们不写析构函数的时候&#xff0c;编译器会自动生成一个空实现的析构函数。 3. 析构函数只能编译器自己调用&#xff0c;我们…

CSS 选择器全攻略:从入门到精通(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

JavaScript从入门到精通系列第三十一篇:详解JavaScript中的字符串和正则表达式相关的方法

文章目录 知识回顾 1&#xff1a;概念回顾 2&#xff1a;正则表达式字面量 一&#xff1a;字符串中正则表达式方法 1&#xff1a;split 2&#xff1a;search 3&#xff1a;match 4&#xff1a;replace 知识回顾 1&#xff1a;概念回顾 正则表达式用于定义一些字符串的…

代码随想录算法训练营第二天|977 有序数组的平方、209长度最小的子数组、59 螺旋矩阵||

977 有序数组的平方 题目链接&#xff1a;有序数组的平方 思路 暴力解法 很容易想到的就是按照题目的说明&#xff0c;先给非递减数组中的每个元素做平方&#xff0c;然后使用一个排序函数对齐进行排序即可。 class Solution { public:vector<int> sortedSquares(ve…