Spring Boot实现IP地址解析

news2025/1/15 17:37:17

一、本地解析

如果使用本地ip解析的话,我们将会借助ip2region,该项目维护了一份较为详细的本地ip地址对应表,如果为了离线环境的使用,需要导入该项目依赖,并指定版本,不同版本的方法可能存在差异。

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

在使用时需要将xdb文件下载到resources目录下,ip2region使用完全基于xdb文件的查询,单次查询响应时间在十微秒级别:

 

package com.example.demo.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

@NoArgsConstructor
@Slf4j
public class IPUtil {

    private static final String UNKNOWN = "unknown";
    private static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
    private static List<String> internalIpList=new ArrayList<>();
    private static byte[] cBuff;

    {
        internalIpList.add("192.168.1.105");
        internalIpList.add("127.0.0.1");
    }

    /**
     * 功能:获取IP地址
     * 使用 Nginx等反向代理软件, 则不能通过 request.getRemoteAddr()获取 IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,
     * X-Forwarded-For中第一个非 unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIp(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.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        // 本机访问
        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 (UnknownHostException e) {
                e.printStackTrace();
            }
        }

        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (null != ip && ip.length() > 15) {
            if (ip.indexOf(",") > 15) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;
    }

    public static String getIpAddrByLocal(String ip) {
        // 1、创建一个完全基于文件的查询对象
        String xdbPath = "src/main/resources/ip2region.xdb";
        Searcher searcher;
        try {
            searcher = Searcher.newWithFileOnly(xdbPath);
        }catch (Exception e) {
            log.error("无法创建内存的查询对象Searcher");
            return null;
        }

        // 2、查询
        try {
            return searcher.searchByStr(ip);
        } catch (Exception e) {
            log.error("IP地址位置查询失败(%s):%s\n",ip, e);
        }
        return null;
    }

    public static String getIpAddrByByOnline(String ip) {
        String address = UNKNOWN;
        if (internalIp(ip)) {
            // 判断是否是内网,如果是内网,则不进行查询,直接返回
            return "内网IP";
        }
        if (true) {
            try {
                String rspStr = sendGet(IP_URL, "ip=" + ip + "&json=true" ,"GBK");
                if (StrUtil.isBlank(rspStr)) {
                    log.error("获取地理位置异常 {}" , ip);
                    return UNKNOWN;
                }
                JSONObject obj = JSONUtil.parseObj(rspStr);
                String region = obj.getStr("pro");
                String city = obj.getStr("city");
                return String.format("%s %s" , region, city);
            } catch (Exception e) {
                log.error("获取地理位置异常:{}",ip);
            }
        }
        return address;
    }

    public static String sendGet(String url, String param, String contentType) {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            log.info("sendGet - {}" , urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept" , "*/*");
            connection.setRequestProperty("connection" , "Keep-Alive");
            connection.setRequestProperty("user-agent" , "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            log.info("recv - {}" , result);
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception ex) {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    private static boolean internalIp(String ip){
        return internalIpList.contains(ip);
    }
}

特别说明:这里我们将其解析封装成一个工具类,包含获取IP和ip地址解析两个方法,ip 的解析可以在请求中获取。获取到ip后,根据ip在xdb 中查找对应的IP地址的解析,由于是本地数据库可能存在一定的缺失,部分ip 存在无法解析的情况。 

ip2region v2.0 是一个离线 IP 地址定位库和 IP 定位数据管理框架,10 微秒级别的查询效率,准提供了众多主流编程语言的 xdb 数据生成和查询客户端实现。

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

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

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

除了完全基于xdb文件的查询,我们还可以通过如下两种方式开启内存加速查询

第一种方式:缓存 VectorIndex 索引

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

import org.lionsoul.ip2region.xdb.Searcher;

public class Demo {

    public static void main(String[] args) {
        // 1、从dbPath中预先加载VectorIndex索引,并且把这个得到的数据进行缓存作为全局变量,后续反复使用。
        String dbPath = "文件路径";
        byte[] vIndex =new byte[10];
        try {
            vIndex = Searcher.loadVectorIndexFromFile(dbPath);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 2、使用全局的vIndex 创建带 VectorIndex 缓存的查询对象。
        Searcher searcher;
        try {
            searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

第二种方式:缓存整个 xdb 文件数据

将整个xdb文件全部加载到内存,内存占用等同于xdb文件大小,无磁盘IO操作,保持微秒级别的查询效率。 

import org.lionsoul.ip2region.xdb.Searcher;

public class Demo {

    public static void main(String[] args) {
        // 1、根据dbPath直接加载整个xdb文件,并且把这个得到的数据进行缓存作为全局变量(存储到内存中)
        String dbPath = "文件路径";
        byte[] cBuff;
        try {
            cBuff = Searcher.loadContentFromFile(dbPath);
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }

        // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象
        Searcher searcher;
        try {
            searcher = Searcher.newWithBuffer(cBuff);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

二、在线解析

如果想要获取更加全面的ip地址信息,可使用在线数据库,这里提供的是whois.pconline.com的IP解析,该IP解析在我的使用过程中表现非常流畅,而且只有少数的ip存在无法解析的情况。

特别说明:示例代码在上面

三、应用场景

那么在项目的什么流程获取ip地址是比较合适的,这里就要用到我们的拦截器了。拦截进入服务的每个请求,进行前置操作,对请求头的解析,获取ip以及ip属地。

import com.example.demo.utils.IPUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Configuration
public class IpUrlLimitInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) {
        /**
         * 第一种方式:通过本地获取IP的具体地址
         */
         //String ip = IPUtil.getIp(httpServletRequest);
         //String addr = IPUtil.getIpAddrByLocal(ip);
         //String url = httpServletRequest.getRequestURI();

        /**
         * 第二种方式: 通过在线库获取
         */
        String ip = IPUtil.getIp(httpServletRequest);
        String addr = IPUtil.getIpAddrByByOnline(ip);
        String url = httpServletRequest.getRequestURI();

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private IpUrlLimitInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor);
    }
}

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

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

相关文章

管理类联考——逻辑——真题篇——按知识分类——汇总篇——三、综合推理

文章目录 题-综合推理-分类1-排序真题&#xff08;2016-54-55&#xff09;-难度最高*****-综合推理-分类1-排序-画表排除法真题&#xff08;2016-54&#xff09;真题&#xff08;2016-55&#xff09;真题&#xff08;2019-36&#xff09;-综合推理-分类1-排序真题&#xff08;2…

回归预测 | MATLAB实现BES-BP秃鹰搜索算法优化BP神经网络多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现BES-BP秃鹰搜索算法优化BP神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现BES-BP秃鹰搜索算法优化BP神经网络多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本…

如何避免爬虫IP被屏蔽

各位爬友们好&#xff0c;作为一名专业的爬虫代理提供者&#xff0c;我要和大家分享一些避免爬虫IP被屏蔽的实用技巧。你知道吗&#xff0c;当我们爬取数据的时候&#xff0c;很容易被目标网站识别出来并封禁我们的IP地址&#xff0c;导致无法继续爬取数据。这个问题困扰了很多…

Q/GDW 1597-2015《国家电网公司应用软件系统通用安全要求》

电力安全测试报告 电力行业检测标准 随着信息技术的快速发展和广泛应用&#xff0c;应用软件系统已成为企业信息化建设中不可或缺的重要组成部分。然而&#xff0c;应用软件系统的安全问题也随之而来&#xff0c;给企业和用户带来了潜在的风险和威胁。为了提高应用软件系统的…

【第七讲---视觉里程计1】

视觉里程计就是通过对图像进行特征提取与匹配得到两帧之间的位姿&#xff0c;并进行估计相机运动。 经典SLAM中以相机位姿-路标来描述SLAM过程 特征提取与匹配 路标是三维空间中固定不变的点&#xff0c;可以在特定位姿下观测到在视觉SLAM中&#xff0c;可利用图像特征点作为…

opencv光流估计

光流估计 光流是空间运动物体在观测成像平面上的像素运动的“瞬时速度”&#xff0c;根据各个像素点的速度矢量特征&#xff0c;可以对图像进行动态分析&#xff0c;例如目标跟踪。 返回&#xff1a; 亮度恒定&#xff1a;同一点随着时间的变化&#xff0c;其亮度不会发生改变…

STM32 GPIO复习

GPIO General Purpose Input Output&#xff0c;即通用输入输出端口&#xff0c;简称GPIO。 负责采集外部器件的信息或控制外部器件工作&#xff0c;即输入输出。 不同型号&#xff0c;IO口数量可能不一样&#xff0c;可通过选型手册快速查询。 能快速翻转&#xff0c;每次翻…

基于QT4的GPX文件编辑器开发

GPX文件是记录地理点的文件,本质是一种xml文件。GPX文件目前没有很好的编辑器,因此作者决定开发一款无需安装的绿色编辑器。 在QT4开发中,XML可以用DOM来实现,但其逻辑并不是很清晰。使用模型视图反而会更加可读。因此在开发中,使用model-view模式来实现数据读写。 1 需…

高速、稳定、安全:4G工业路由器在户外环境下的组网优势

能够在无人值守的户外环境下实现组网和远程监控功能的4G工业路由器&#xff01;工业级路由器具备防尘、防水、耐高温等特性&#xff0c;适用应用在恶劣的户外及工业场景中&#xff0c;如远程农田监测、驾考科目二/科目三、智能交通系统、环境监控、煤矿数据采集、水利远程管理等…

视频播放实现示例Demo

学习链接 vuespringboot文件分片上传与边放边播实现 同步加载、播放视频的实现 ---- range blob mediaSource 通过调试技术&#xff0c;我理清了 b 站视频播放很快的原理 MSE (Media Source Extensions) 上手指南 浅聊音视频的媒体扩展&#xff08;Media Source Extension…

曲线救国 | 双非渣硕的秋招路

作者 | 带带大兄弟 面试锦囊之面经分享系列&#xff0c;持续更新中 欢迎后台回复"面试"加入讨论组交流噢 一篇旧文&#xff0c;可以参考~ 写在前面 双非渣硕&#xff0c;0实习&#xff0c;3篇水文&#xff0c;三个给老板当打工仔的nlp横向项目&#xff0c;八月份开…

uniapp 微信小程序 绘制海报,长按图片分享,保存海报

uView UI 2.0 dcloud 插件市场地址 弹窗海报源码 <template><!-- 推荐商品弹窗 --><u-popup :show"haibaoShow" mode"center" round26rpx z-index10076 bgColortransparent safeAreaInsetTop close"goodsclose"><image …

第四十九周周报

学习目标&#xff1a; VITGAN实验 学习时间&#xff1a; 2023.8.12-20238.18 学习产出&#xff1a; 一、实验 1、内容&#xff1a;在原模型的基础上加上相对位置编码&#xff0c;结果&#xff1a;比原模型差 2、内容&#xff1a;在原模型的基础上加上可学习位置编码相对位…

常用系统命令

重定向 cat aa.txt > bbb.txt 将输出定向到bbb.txt cat aaa.txt >> bbb.txt 输出并追加查看进程 ps ps -ef 显示所有进程 例⼦&#xff1a;ps -ef | grep mysql |&#xff1a;管道符 kill pid 结束进程&#xff0c; 如 kill 3732&#xff1b;根据进程名结束进程可以先…

终端美化+自动补全和语法高亮 + zsh

终端美化自动补全和语法高亮 zsh 一、前言 1、最终效果 2、所用工具 zsh shell # bash改zsh&#xff0c;选用原因后文提及oh my zsh # 插件管理powerlevel10k # 主题auto-suggestion # 自动补全syntax highlighting # 语法高亮3、bash vs zsh vs fish 经典话题&#xff0c;…

Shell编程之免交户

expect 一、多行重定向1.1 格式1.2 注意事项1.3 使用实例 二、expect免交户2.1 expect概述2.1.1 定义2.1.2 安装2.1.3 命令格式 2.2 相关命令2.2.1 spawn2.2.2 expect.3 send2.2.3 set2.2.4 send_user2.2.5 exp_continue2.2.6 脚本解释器2.2.7 结束符2.2.8 exit 2.3 运用实例.1…

【Django】Task3 外键的使用、Queryset和Instance

【Django】Task3 外键的使用、Queryset和Instance Task3主要理解数据库外键的使用场景&#xff0c;了解Queryset的功能&#xff0c;通过编写代码体验Queryset中对数据库实例的curd操作&#xff0c;同时了解到Instance的定义。 1.外键的使用 1.1什么是外键 数据表外键是数据…

SHELL 基础

echo 打印命令 &#xff1a; 显示字符串 [rootserver ~]# echo this is SHELL language this is SHELL language [rootserver ~]# echo this is SHELL language this is SHELL language [rootserver ~]# echo "this is SHELL language" this is SHELL language…

el-table 实现动态表头 静态内容 根据数据显示动态输入框

直接放代码了 <el-table:data"form.tableDataA"borderstripestyle"width: 100%; margin-top: 20px"><el-table-columnv-for"(category, categoryIndex) in form.tableDataA":key"categoryIndex":label"category.name&qu…

AWS security 培训笔记

云计算的好处 Amazon S3 (Storage) Amazon EC2 (Compute) 上图aws 的几个支柱&#xff1a;安全是其中一个啦 其中安全有几个方面 IAMdetection基础架构保护数据保护应急响应 关于云供应商的责任 data center 原来长这样 &#xff0c;据说非常之隐蔽的 如果有天退役了&#xf…