文章目录
- Ip2region 简介
- 是什么
- 特性
- 支持的编程语言
- 案例实操
- 依赖
- 获取IP
- 输入流转化
- 解析IP
- 测试
抖音、微博、小红书等各平台相继上线"
网络用户IP地址显示功能
",
境外
显示
国家
,
境内
显示到
省市
,且该功能无法关闭,IP地址为强制显示。无疑更加有效的约束键盘侠的言行举止,还原一个干净的网络环境!
作为技术人来说其实这个功能so easy
,下面借助Ip2region
来实现
Ip2region 简介
是什么
ip2region
v2.0 是一个离线IP地址定位库和IP定位数据管理框架
,10微秒级别的查询效率,提供了众多主流编程语言的xdb
数据生成和查询客户端实现。
特性
- 标准化的数据格式
每个ip
数据段的region
信息都固定了格式:国家|区域|省份|城市|ISP
,只有中国的数据绝大部分精确到了城市,其他国家部分数据只能定位到国家,后前的选项全部是0。
- 数据去重和压缩
xdb
格式生成程序会自动去重和压缩部分数据,默认的全部IP
数据,生成的 ip2region.xdb
数据库是11MiB
,随着数据的详细度增加数据库的大小也慢慢增大。
- 极速查询响应
即使是完全基于xdb
文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:
vIndex 索引缓存
:使用固定的512KiB
的内存空间缓存vector index
数据,减少一次IO
磁盘操作,保持平均查询效率稳定在10-20微秒之间。xdb 整个文件缓存
:将整个xdb
文件全部加载到内存,内存占用等同于xdb 文件大小
,无磁盘 IO 操作,保持微秒级别的查询效率。
注:下文实操以缓存 xdb 整个文件为例
支持的编程语言
binding | 描述 | 开发状态 | binary查询耗时 | b-tree查询耗时 | memory查询耗时 |
---|---|---|---|---|---|
c | ANSC c binding | 已完成 | 0.0x毫秒 | 0.0x毫秒 | 0.00x毫秒 |
c# | c# binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.1x毫秒 |
golang | golang binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.1x毫秒 |
java | java binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.1x毫秒 |
lua | lua实现的binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.x毫秒 |
lua_c | lua的c扩展 | 已完成 | 0.0x毫秒 | 0.0x毫秒 | 0.00x毫秒 |
nginx | nginx的c扩展 | 已完成 | 0.0x毫秒 | 0.0x毫秒 | 0.00x毫秒 |
nodejs | nodejs | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.1x毫秒 |
php | php实现的binding | 已完成 | 0.x毫秒 | 0.1x毫秒 | 0.1x毫秒 |
php5_ext | php5的c扩展 | 已完成 | 0.0x毫秒 | 0.0x毫秒 | 0.00x毫秒 |
php7_ext | php7的c扩展 | 已完成 | 0.0毫秒 | 0.0x毫秒 | 0.00x毫秒 |
python | python bindng | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.x毫秒 |
rust | rust binding | 已完成 | 0.x毫秒 | 0.x毫秒 | 0.x毫秒 |
案例实操
依赖
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
获取IP
根据Request
请求,从请求头
中获取IP
地址
package cn.goitman.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author Nicky
* @version 1.0
* @className IpUtil
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 解析IP地址工具
* @date 2023/3/23 16:45
*/
public class IpUtil {
private static Logger log = LoggerFactory.getLogger(IpUtil.class);
private static final String UNKNOWN = "unknown";
public static String getIpAddress(HttpServletRequest request) {
String ip = null;
try {
// k8s将真实的客户端IP,放到了x-Original-Forwarded-For。而将WAF的回源地址放到了 x-Forwarded-For了。
ip = request.getHeader("X-Original-Forwarded-For");
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
// 通过nginx获取ip
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("x-forwarded-for");
}
// 通过Apache代理获取ip
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
// 通过WebLogic代理获取ip
if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
// 通过负载均衡获取IP地址(HTTP_CLIENT_IP、HTTP_X_FORWARDED_FOR)
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
// 通过Nginx获取ip(Nginx中的另一个变量,内容就是请求中X-Forwarded-For的信息)
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
//兼容集群获取ip
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
// 客户端和服务器为同一台机器时,获取的地址为IPV6格式:"0:0:0:0:0:0:0:1"
if ("127.0.0.1".equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)) {
//根据网卡取本机配置的IP
InetAddress iNet = null;
try {
iNet = InetAddress.getLocalHost();
ip = iNet.getHostAddress();
} catch (UnknownHostException e) {
log.error("根据网卡获取IP地址异常: ", e);
}
}
}
} catch (Exception e) {
log.error("获取IP地址异常 ", e);
}
//使用代理,则获取第一个IP地址
if (!StringUtils.isEmpty(ip) && ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
return ip;
}
}
输入流转化
package cn.goitman.utils;
import java.io.*;
/**
* @author Nicky
* @version 1.0
* @className FileUtil
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 输入流工具
* @date 2023/3/23 17:22
*/
public class InputStreamUtil {
/**
* 将输入流转化为字节数组
*/
public static byte[] inputStreamToByteArray(InputStream inputStream) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int num;
while ((num = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, num);
}
byteArrayOutputStream.flush();
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
解析IP
下载ip2region
仓库中的ip2region.xdb
文件,然后放到resource
目录下
ip2region.xdb
文件路径:https://github.com/lionsoul2014/ip2region/tree/master/data
package cn.goitman.service;
import cn.goitman.utils.InputStreamUtil;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
/**
* @author Nicky
* @version 1.0
* @className SearcherFile
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 解析ip地址属性
* @date 2023/3/23 17:21
*/
@Service
public class SearcherService {
public String getRegion(String ip) {
// jar包也能获取ip2region.xdb文件
InputStream in = this.getClass().getClassLoader().getResourceAsStream("ip2region.xdb");
byte[] bytes = InputStreamUtil.inputStreamToByteArray(in);
try {
Searcher searcher = Searcher.newWithBuffer(bytes);
long sTime = System.nanoTime();
// 中国|0|上海|上海市|联通;美国|0|犹他|盐湖城|0
String regionInfo = searcher.search(ip);
String region = getCityInfo(regionInfo);
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
System.out.printf("{IP属地 : %s, ip: %s, 耗时: %d 纳秒}\n", region, ip, cost);
return region;
} catch (Exception e) {
System.out.printf("IP地址异常 (%s) : %s\n", ip, e);
return null;
}
}
/**
* 解析城市信息,国内显示城市名,国外显示国家名
*/
private String getCityInfo(String regionInfo) {
if (!StringUtils.isEmpty(regionInfo)) {
String[] cityArr = regionInfo.replace("|0", "").replace("0|", "").split("\\|");
if (cityArr.length > 0) {
if ("内网ip".equalsIgnoreCase(cityArr[0])) {
return "内网IP";
}
if ("中国".equals(cityArr[0])) {
return cityArr[1];
}
return cityArr[0];
}
}
return "未知IP";
}
}
测试
没什么蹊跷,就是这么简单,下班…
源码:https://github.com/wangdaicong/spring-boot-project/tree/master/ip2region-demo