抖音、微博、小红书等各平台相继上线”网络用户IP地址显示功能“,境外显示国家境内显示到省市,且该功能无法关闭,IP地址为强制显示。无疑更加有效的约束键盘侠的言行举止,还原一个干净的网络环境!

作为技术人来说其实这个功能so easy,下面借助Ip2region来实现

Ip2region 简介

是什么

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

特性

  • 标准化的数据格式

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

  • 数据去重和压缩

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

  • 极速查询响应

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

  1. vIndex 索引缓存:使用固定的512KiB的内存空间缓存vector index 数据,减少一次IO磁盘操作,保持平均查询效率稳定在10-20微秒之间。
  2. 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