java 获取xml 指定节点-java xml指定属性节点
每个 IP 地址,每个值通过逗号+空格分开,最左边是最原始客户端的 IP 地址,中间如果有多层代理,每⼀层代理会将连接它的客户端 IP 追加在 X-Forwarded-For 右边。
X-Real-IP
一般只记录真实发出请求的客户端IP。
Proxy-Client-IP
这个一般是经过 Apache http 服务器的请求才会有,用 Apache http 做代理时一般会加上 Proxy-Client-IP 请求头。
WL-Proxy-Client-IP
也是通过 Apache http 服务器,在 weblogic 插件加上的头。
分析方法request.getRemoteAddr()
这种方法在大部分情况下都是有效的。
但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实IP地址了。
如果使用了反向代理软件,用request.getRemoteAddr()方法获取的IP地址是:127.0.0.1或192.168.1.110或公网IP,而并不是客户端的真实IP。
获取ip属地
我们通过以上方法获取了ip完整地址后,该如何使用ip完整地址获取ip属地呢?
在浏览Gitee时,发现了Ip2region 这个库,我们可以使用Ip2region 来获取ip属地。
Ip2region是什么
ip2region – 是一个离线IP地址定位库和IP定位数据管理框架,10微秒级别的查询效率,提供了众多主流编程语言的 xdb 数据生成和查询客户端实现。
Ip2region的特性标准化的数据格式
每个 ip 数据段的 region 信息都固定了格式:国家|区域|省份|城市|ISP。
只有中国的数据绝大部分精确到了城市,其他国家部分数据只能定位到国家,后前的选项全部是0。
数据去重和压缩
xdb 格式生成程序会自动去重和压缩部分数据,默认的全部 IP 数据,生成的 ip2region.xdb 数据库是 11MiB,随着数据的详细度增加数据库的大小也慢慢增大。
极速查询响应
即使是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:
vIndex 索引缓存 :使用固定的 512KiB 的内存空间缓存 vector index 数据,减少一次 IO 磁盘操作,保持平均查询效率稳定在10-20微秒之间。
xdb 整个文件缓存:将整个 xdb 文件全部加载到内存,内存占用等同于 xdb 文件大小,无磁盘 IO 操作,保持微秒级别的查询效率。
IP 数据管理框架
v2.0 格式的 xdb 支持亿级别的 IP 数据段行数,region 信息也可以完全自定义,例如:你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编等。也就是你完全可以使用 ip2region 来管理你自己的 IP 定位数据。
内置的三种查询算法
之所以全部的查询客户端单次查询都在 0.x 毫秒级别,是因为内置了三种查询算法:
memory 算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内,C语言的客户端单次查询在0.00x毫秒级别。
binary 算法:基于二分查找,基于ip2region.db文件,不需要载入内存,单次查询在0.x毫秒级别。
b-tree 算法:基于btree算法,基于ip2region.db文件,不需要载入内存,单词查询在0.x毫秒级别,比binary算法更快。
使用ip2region安装依赖
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.6.5</version>
</dependency>
下载 ip2region.xdb文件
我们可以登录ip2region的源代码,找到目录 ip2region/ data / ip2region.xdbjava 获取xml 指定节点,点击下载源文件,如下图所示:
配置ip2region.xdb文件
我们把下载好的ip2region.xdb文件,放置在resources目录下,如下图所示:
实现代码完全基于文件查询
/**
* @author zs
* @datetime 2022/8/1:17:05
* @param ipAddress ip地址,比如127.0.0.1
* @desc 获取ip属地
*/
public static String getIpBelong(String ipAddress) {
// 1、 获取文件地址
String dbPath = "./src/main/resources/ip2region.xdb";
// 2、 创建 searcher 对象
Searcher searcher = null;
try {
searcher = Searcher.newWithFileOnly(dbPath);
} catch (IOException e) {
System.out.printf("failed to create searcher with `%s`: %s\n", dbPath, e);
return null;
}
// 3、查询地址
String region = null;
try {
long sTime = System.nanoTime();
region = searcher.search(ipAddress);
long cost = TimeUnit.NANOSECONDS.toMicros((System.nanoTime() - sTime));
System.out.printf(
"输出日志 = {ip属地: %s, io次数: %d, 耗时: %d μs}\n", region, searcher.getIOCount(), cost);
} catch (Exception e) {
System.out.printf("failed to search(%s): %s\n", ipAddress, e);
}
// 4、关闭资源
try {
searcher.close();
} catch (IOException e) {
e.printStackTrace();
}
return region;
}
重要提醒
并发使用,每个线程需要创建一个独立的 searcher 对象单独使用。
使用缓存 VectorIndex 索引查询
我们可以提前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
/**
* @author zs
* @datetime 2022/8/1:17:15
* @param ipAddress ip地址,比如127.0.0.1
* @desc 使用缓存 VectorIndex 索引
*/
public static String vectorIndexCache(String ipAddress) {
// 1、获取文件地址
String dbPath = "./src/main/resources/ip2region.xdb";
// 2、从 dbPath 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
byte[] vIndex;
try {
vIndex = Searcher.loadVectorIndexFromFile(dbPath);
} catch (Exception e) {
System.out.printf("failed to load vector index from `%s`: %s\n", dbPath, e);
return null;
}
// 3、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
Searcher searcher;
try {
searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
} catch (Exception e) {
System.out.printf("failed to create vectorIndex cached searcher with `%s`: %s\n", dbPath, e);
return null;
}
// 4、查询
String region = null;
try {
long sTime = System.nanoTime();
region = searcher.search(ipAddress);
long cost = TimeUnit.NANOSECONDS.toMicros((System.nanoTime() - sTime));
System.out.printf(
"输出日志 = {ip属地: %s, io次数: %d, 耗时: %d μs}\n", region, searcher.getIOCount(), cost);
} catch (Exception e) {
System.out.printf("failed to search(%s): %s\n", ipAddress, e);
}
// 5、关闭资源
try {
searcher.close();
} catch (IOException e) {
e.printStackTrace();
}
return region;
}
重要提醒使用缓存整个 xdb 数据
/**
* @author zs
* @datetime 2022/8/1:17:15
* @param ipAddress ip地址,比如127.0.0.1
* @desc 使用缓存整个 xdb 数据
*/
public static String cacheXdb(String ipAddress) {
// 1、获取文件地址
String dbPath = "./src/main/resources/ip2region.xdb";
// 2、从 dbPath 加载整个 xdb 到内存。
byte[] cBuff;
try {
cBuff = Searcher.loadContentFromFile(dbPath);
} catch (Exception e) {
System.out.printf("failed to load content from `%s`: %s\n", dbPath, e);
return;
}
// 3、使用上述的 cBuff 创建一个完全基于内存的查询对象。
Searcher searcher;
try {
searcher = Searcher.newWithBuffer(cBuff);
} catch (Exception e) {
System.out.printf("failed to create content cached searcher: %s\n", e);
return null;
}
// 4、查询
String region = null;
try {
long sTime = System.nanoTime();
region = searcher.search(ipAddress);
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
System.out.printf(
"输出日志 = {ip属地: %s, io次数: %d, 耗时: %d μs}\n", region, searcher.getIOCount(), cost);
} catch (Exception e) {
System.out.printf("failed to search(%s): %s\n", ipAddress, e);
}
// 5、关闭资源
// searcher.close();
return region;
}
重要提醒
关闭资源 – 该 searcher 对象可以安全用于并发,等整个服务关闭的时候再关闭 searcher。
并发使用,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。
获取具体属地
因为上述代码获取到的属地是原始属地,比如中国|0|上海|上海市|联通,因而,我们需要将其拆分,获取真正的属地,如下代码所示:
/**
* @author zs
* @datetime 2022/8/1:17:31
* @param cityInfo ip原始属地,比如:中国|0|上海|上海市|联通
* @desc 将ip原始属地拆分,比如中国|0|上海|上海市|联通拆分成上海
*/
public static String getIpPossession(String cityInfo) {
if (null == cityInfo || cityInfo.equals("")) {
return "未知";
}
cityInfo = cityInfo.replace("|", " ");
String[] cityList = cityInfo.split(" ");
if (cityList.length <= 0) {
return "未知";
}
// 国内的显示到具体的省
if ("中国".equals(cityList[0]) && cityList.length > 1) {
return cityList[1];
}
// 国外显示到国家
return cityList[0];
}
测试代码测试国内ip
@Test
public void testGetIpBelong() {
String ipBelong = getIpBelong("220.248.12.158");
System.out.println("\n输出ip原始属地:" + ipBelong);
System.out.println("输出ip属地:" + getIpPossession(ipBelong));
}
输出结果如下图:
测试国外ip
@Test
public void testGetIpBelong() {
String ipBelong = getIpBelong("67.220.12.158");
System.out.println("\n输出ip原始属地:" + ipBelong);
System.out.println("输出ip属地:" + getIpPossession(ipBelong));
}
输出结果如下图:
总结源代码地址
因为代码时不断更新的,如果想要获取最新的maven,可以打开其源代码地址:
failed to create searcher: java.io.FileNotFoundException复现问题
在获取文件地址时java 获取xml 指定节点,报出了如下错误:
failed to create searcher with `./resources/ip2region.xdb`:
java.io.FileNotFoundException: .\resources\ip2region.xdb (
系统找不到指定的路径。
)
分析问题
因为我使用文件的相对位置,即./resources/ip2region.xdb,但ip2region代码是从项目的根路径,去查找ip2region.xdb的文件的相对路径。
ip2region.xdb的完整路径是D:/project/demo/src/main/resources/ip2region.xdb,而我只写了./resources/ip2region.xdb,肯定找不到这个文件,就报出找不到文件异常。
如果我们不知道项目的位置,可以使用如下代码来查找,即:
File file=new File("");
try {
System.out.println(file.getCanonicalPath());
} catch (IOException e) {
e.printStackTrace();
}
输出结果是:D:\project\demo,便可以知道项目的根路径。
解决问题
既然知道了错误原因,便将文件的相对路径修改如下即可:
// 1. 获取文件地址
String dbPath = "./src/main/resources/ip2region.xdb";