当前位置: 首页 > news >正文

Spring Boot实现IP地址解析

一、本地解析

如果使用本地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)){// 根据网卡取本机配置的IPInetAddress 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 {@Overridepublic 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;}@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {}@Overridepublic 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 {@Autowiredprivate IpUrlLimitInterceptor interceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(interceptor);}
}

相关文章:

Spring Boot实现IP地址解析

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

小程序中通过canvas生成并保存图片

1. html <canvas class"canvas" id"photo" type"2d" style"width:200px;height: 300px;"></canvas> <button bindtap"saveImage">保存</button> <!-- 用来展示生成的那张图片 --> <image…...

Error creating bean with name ‘esUtils‘ defined in file

报错异常&#xff1a; 背景&#xff1a; esUtils在common服务中、启动media服务时候、报这个异常、后排查esUtils在启动时候发生异常引起的、在相关bean中加入try{}catch{}即可解决问题 String[] split url.split(","); HttpHost[] httpHosts new HttpHost[split.…...

Java开发面试题 | 2023

Java基础 接口和抽象类的区别&#xff1f;Java动态代理HashMap 底层实现及put元素的具体过程currenthashmap底层实现原理&#xff1f;map可以放null值吗&#xff0c;currenthashmap为什么不能放null值synchronze和reetrantlock区别&#xff1f;怎样停止一个运行中的线程&#…...

Java课题笔记~ 自定义拦截器实现权限验证

实现一个权限验证拦截器。 1、修改web.xml文件中请求路径 2、将所有的页面放入WEB-INF目录下 3、开发登录FirstController 4、开发拦截器 5、配置springmvc.xml文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.spri…...

微信小程序使用npm引入三方包详解

目录 1 前言2 微信小程序npm环境搭建2.1 创建package.json文件2.2 修改 project.config.json2.3 修改project.private.config.json配置2.4 构建 npm 包2.5 typescript 支持2.6 安装组件2.7 引入使用组件 1 前言 从小程序基础库版本 2.2.1 或以上、及开发者工具 1.02.1808300 或…...

pytest自动化框架运行全局配置文件pytest.ini

还记得在之前的篇章中有讲到Pytest是目前主要流行的自动化框架之一&#xff0c;他有基础的脚本编码规则以及两种运行方式。 pytest的基础编码规则是可以进行修改&#xff0c;这就是今日文章重点。 看到这大家心中是否提出了两个问题&#xff1a;pytest的基础编码规则在哪可以…...

视频播放实现示例Demo

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

makefile的自动化变量

一、是什么? 自动化变量:makefile依据执行的规则自动变化生成的变量 $(@) 规则的目标文件名 $(^) 所有依赖 依赖列表 $(<)第一个依赖文件名 $(*)规则中目标中%部分名 $(?)所有比目标文件更新的依赖文件列表,空格分隔 二、使用步骤 1.引入库 代码如下(示例): make …...

使用Kind搭建本地k8s集群环境

目录 1.前提条件 2.安装Kind 3.使用Kind创建一个K8s集群 3.1.创建一个双节点集群&#xff08;一个Master节点&#xff0c;一个Worker节点&#xff09; 3.2.验证一下新创建的集群信息 3.3.删除刚刚新建的集群 4.安装集群客户端 4.1.安装kubectl 4.1.1.验证kubectl 4.2.安…...

【STM32RT-Thread零基础入门】 7. 线程创建应用(多线程运行机制)

硬件&#xff1a;STM32F103ZET6、ST-LINK、usb转串口工具、4个LED灯、1个蜂鸣器、4个1k电阻、2个按键、面包板、杜邦线 文章目录 前言一、RT-Thread相关接口函数1. 获取当前运行的线程2. 设置调度器钩子函数 二、程序设计1. 头文件包含及宏定义2. 线程入口函数定义3. main函数设…...

.net日志系统

.NET 平台提供了强大的日志记录系统,用于在应用程序中记录各种事件、错误和调试信息。最常用的日志记录库是 Microsoft.Extensions.Logging,它是一个通用的日志接口和基础框架,可以与多种日志实现集成。以下是如何使用 .NET 日志系统的基本步骤: 安装 NuGet 包:首先,您需…...

SpringCloud学习笔记(二)_Eureka注册中心

一、Eureka简介 Eureka是一项基于REST&#xff08;代表性状态转移&#xff09;的服务&#xff0c;主要在AWS云中用于定位服务&#xff0c;以实现负载均衡和中间层服务器的故障转移。我们称此服务为Eureka Server。Eureka还带有一个基于Java的客户端组件Eureka Client&#xff…...

spark的eventLog日志分析

查找满足指定条件的app_id查询条件: 表名、时间、节点名时间限定: 最好适当放大, 不知道什么原因有点不准eventLog的存放路径: spark.history.fs.logDirectory 1. spark-sql 先限定时间段;数据是逐行读入的, 但 app_id要按整个文件过滤, 按每个条件打标;按app_id粒度聚合, 查…...

探究Java spring中jdk代理和cglib代理!

面对新鲜事物&#xff0c;我们要先了解在去探索事物的本质-默 目录 一.介绍二者代理模式 1.1.Jdk代理模式 1.2cglib代理模式 1.3二者区别 1.3.1有无接口 1.3.2灵活性 1.4对于两种代理模式的总结 1.4.1jdk代理模式 1.4.2cglib代理模式 二.两种代理模式应用场景 2.1jd…...

反转链表(C++)

1、迭代法的一种写法 ListNode* reverse_linkList(ListNode* head){if(head nullptr || head->next nullptr) return head;ListNode* begin nullptr;ListNode* mid head;ListNode* end head->next;while(true){mid->next begin;if(end nullptr){break;}begin …...

适配器模式:让不兼容的接口协同工作

在面向对象设计中&#xff0c;适配器模式是一种常见的结构型设计模式。它允许将不兼容的接口转换成客户端所期望的另一个接口&#xff0c;从而使不同的类协同工作。适配器模式的主要目的是解决不同接口之间的兼容性问题&#xff0c;同时也提高了代码的可重用性和灵活性。 问题…...

【1day】复现Milesight-VPNserver.js 任意文件读取漏洞

目录 一、漏洞描述 二、影响版本 三、资产测绘 四、漏洞复现 一、漏洞描述 Milesight路由器-VPN是由Milesight Technology Co., Ltd.开发的一种集成了VPN功能的路由器产品。它旨在为用户提供安全、可靠的远程访问和连接解决方案。Milesight-VPNserver.js存在任意文件读取…...

前端代码规范

1 husky husky用于绑定git hooks&#xff0c;在指定时机执行想要的命令 {"husky": {"hooks": {"pre-commit": "lint-staged" }} }需要手动修改.husky文件内容&#xff1a; . "$(dirname -- "$0")/_/husky.sh"n…...

Java接入文心一言

文章目录 文心一言应用创建接口对接接口文档代码示例依赖 常量类实体类 结束语 文心一言应用创建 首先需要先申请文心千帆大模型&#xff0c;申请地址&#xff1a;文心一言 (baidu.com)&#xff0c;点击加入体验&#xff0c;等通过审核之后就可以进入文心千帆大模型后台进行应…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...