抖音、微博、小红书等各平台相继上线”网络用户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;
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 { ip = request.getHeader("X-Original-Forwarded-For"); if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("x-forwarded-for"); } if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); if ("127.0.0.1".equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equalsIgnoreCase(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); } if (!StringUtils.isEmpty(ip) && ip.indexOf(",") > 0) { ip = ip.substring(0, ip.indexOf(",")); } return ip; } }
|
输入流转化
package cn.goitman.utils;
import java.io.*;
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;
@Service public class SearcherService { public String getRegion(String ip) { InputStream in = this.getClass().getClassLoader().getResourceAsStream("ip2region.xdb"); byte[] bytes = InputStreamUtil.inputStreamToByteArray(in);
try { Searcher searcher = Searcher.newWithBuffer(bytes);
long sTime = System.nanoTime(); 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