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

news2024/11/15 17:38:18

目录

前言

一、获取访问的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)) {
                // 根据网卡取本机配置的 IP
                InetAddress 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)) {
            // 根据网卡取本机配置的IP
            InetAddress 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)) {
                // 根据网卡取本机配置的IP
                try {
                    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地址拼装成String
            StringBuilder 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、创建 URLConnction
            URL 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转map
                Gson 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才是真实ip
                if (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地址拼装成String
            StringBuilder 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、创建 URLConnction
            URL 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转map
                Gson 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";// 国内IP
        String abroadIp = "48.119.248.100"; // 国外IP

        System.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.coloradmin.cn/o/1378041.html

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

相关文章

从Scroll怒喷社区用户事件,看L2龙头ZKFair的做事格局

这两天&#xff0c;随着美国SEC正式批准所有11只比特币现货ETF的消息公布&#xff0c;吸引了传统主流增量资金的入场&#xff0c;比特币多头一举将比特币干到了48000刀的位置&#xff0c;并随时向着前高发起了冲击。比特币的强势带动了其他加密资产的保障&#xff0c;整个加密市…

Android开发基础(四)

Android开发基础&#xff08;四&#xff09; 本篇将从Android数据存储方式去理解Android开发。 Android数据存储方式 Android提供了多种数据存储方式。 一、SharedPreferences存储 主要用于存储一些简单的配置信息&#xff0c;如登录账号密码等&#xff1b; 这种存储方式采…

类和对象---C++

类和对象目录 类和对象1.封装1.1 封装的意义1.2 struct和class区别1.3 成员属性设置为私有1.3.1 联系---判断圆和点的位置关系 2.对象的初始化和清理2.1 构造函数和析构函数2.2 构造函数的分类及调用2.2.1无参构造函数调用2.2.2有参构造函数调用2.2.2.1括号法2.2.2.2显式法2.2.…

C++学习笔记(二十八):c++ 静态库及动态库的使用

静态库的使用 库的使用会很大程度减少我们的工作&#xff0c;本节对c中静态库和动态库的使用进行简单的介绍。静态链接库意味着这个库会被放到可执行文件中&#xff0c;在生成的exe中。动态链接库是在程序运行时链接的&#xff0c;可以在程序运行时调用加载库函数的方法来实现&…

蚂蚁爱购--靠谱的SpringBoot项目

简介 这是一个靠谱的SpringBoot项目实战&#xff0c;名字叫蚂蚁爱购。从零开发项目&#xff0c;视频加文档&#xff0c;十天就能学会开发JavaWeb项目。 教程路线是&#xff1a;搭建环境> 安装软件> 创建项目> 添加依赖和配置> 通过表生成代码> 编写Java代码&g…

测试八年|对业务测试人员的一些思考

自从事测试工作八年多以来&#xff0c;经历过三个部门多条业务线&#xff0c;也经历过测试转型再回到测试&#xff0c;在此过程中对测试工作和角色的认知也逐步有些思考&#xff0c;想把这些思考分享给大家&#xff0c;希望为业务测试同学提供一些有价值的思路。 一、质量保障…

【Leetcode】2085. 统计出现过一次的公共字符串

文章目录 题目思路代码 题目 2085. 统计出现过一次的公共字符串 思路 使用两个哈希表 words1Count 和 words2Count 分别统计两个数组中每个单词的出现次数。然后遍历 words1Count 中的每个单词&#xff0c;如果该单词在 words1 中出现了一次&#xff0c;且在 words2 中也出…

如何运用自养号测评策略在Lazada、Shopee上轻松提升销售和排名

卖家们常常会为Lazada、Shopee店铺销量不佳而感到困惑。然而&#xff0c;仅仅感叹并不能解决问题。作为卖家&#xff0c;我们需要深入分析问题&#xff0c;并采取有效的措施来解决它们。基本功是提升销量的基石&#xff0c;但仅仅依靠基本功是不够的。我们需要将运营和测评结合…

Java面试题(java高级面试题)

线程池的核心线程数设置为多大比较合理&#xff1f; Worker线程在执行的过程中&#xff0c;有一部计算时间需要占用CPU&#xff0c;另一部分等待时间不需要占用CPU&#xff0c;通过量化分析&#xff0c;例如打日志进行统计&#xff0c;可以统计出整个Worker线程执行过程中这两…

【排序算法】插入排序与希尔排序,你不想知道为什么希尔比插入更快吗?

文章目录 &#x1f680;前言&#x1f680;插入排序&#xff08;insertsort&#xff09;✈️原理✈️代码实现&#xff08;coding&#xff09; &#x1f680;总结&#x1f680;希尔排序&#xff08;shellsort&#xff09;✈️代码实现&#xff08;coding&#xff09;✈️为啥希尔…

Android基于Matrix绘制PaintDrawable设置BitmapShader,以手指触点为中心显示原图像圆图,Kotlin(2)

Android基于Matrix绘制PaintDrawable设置BitmapShader&#xff0c;以手指触点为中心显示原图像圆图&#xff0c;Kotlin&#xff08;2&#xff09; 在 https://zhangphil.blog.csdn.net/article/details/135374279 基础上&#xff0c;增加一个功能&#xff0c;当手指在上面的图片…

以报时机器人为例详细介绍tracker_store和event_broker

报时机器人源码参考[1][2]&#xff0c;本文重点介绍当 tracker_store 类型为 SQL 时&#xff0c;events 表的表结构以及数据是如何生成的。以及当 event_broker 类型为 SQL 时&#xff0c;events 表的表结构以及数据是如何生成的。 一.报时机器人启动 [3] Rasa 对话系统启动方…

基于springboot+vue心理测试管理系统

摘要 基于Spring Boot 和 Vue 的心理测试管理系统是一个综合利用现代Web开发技术的应用程序。系统采用了Spring Boot作为后端框架&#xff0c;通过其简化的配置和强大的功能提供了稳健的服务器端支持。前端则使用Vue.js&#xff0c;一个灵活、高效的JavaScript框架&#xff0c;…

通义千问AI挑战赛赛后反思

个人理解&#xff1a; 初赛阶段主要聚焦在如何通过 SFT 提升基础模型的代码能力&#xff0c;需要选手基于最新开源的 Qwen 1.8 模型作为基础模型&#xff0c;上分的关键主要通过收集高质量的代码数据提升模型的在Python, JavaScript, Java, Go, C, Rust六种编程语言的代码生成…

档案数字化怎样快速整理资料

对于机构和组织来说&#xff0c;档案数字化是一个重要的信息管理和保护措施。要快速整理资料进行档案数字化&#xff0c;可以遵循以下步骤&#xff1a; 1. 准备工具和设备&#xff1a;确保有一台计算机、扫描仪和相关软件。 2. 分类和组织资料&#xff1a;先将资料分类&#xf…

c++最值查找

目录 min和max函数 min_element和max_element 例 nth_element函数 例 例题 题目描述 输入描述 输出描述 解 min和max函数 只能传入两个值或一个列表 时间复杂度为O(1),数组O(n)&#xff0c;n为元素个数 min_element和max_element min_element(st,ed)返回地址[st,…

数据结构学习之顺序栈应用的案例(有效的括号)

实例要求&#xff1a; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效&#xff1b; 有效字符串需满足的条件&#xff1a; 1、左括号必须用相同类型的右括号闭合&#xff1b; 2、左括号必须…

服务端性能测试——性能测试工具JMeter-L1

第一遍没学懂&#xff0c;后续文章会更新~ 目录&#xff1a; 1.JMeter介绍与安装Meter简介JMeter安装2.JMeter的运行JMeter运行、界面功能简介3.使用代理服务器录制请求录制压测脚本&#xff08;一&#xff09;Web端脚本录制方法4.测试计划5.线程组6.控制器7.JMeter采样器/取…

Postman接口测试工具最全实用教程

一、postman简介 1、postman的特点 postman只做http协议的接口的测试&#xff0c;是一种最广泛REST接口测试客户端软件。postman支持http协议的所有请求方式&#xff0c;包括get、post、head、put、delete等。postman支持各种额外的头部字段的添加。postman除了可以模拟普通表…