支持亿级别的 IP 数据段行数,默认的 region 信息都固定了格式:
国家|区域|省份|城市|ISP
,缺省的地域信息默认是0。region
信息支持完全自定义,例如:你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编
等。也就是你完全可以使用ip2region
来管理你自己的IP
定位数据。
基于
xdb
文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:
1.vIndex
索引缓存: 使用固定的512KiB
的内存空间缓存vector index
数据,减少一次IO
磁盘操作,保持平均查询效率稳定在10-20
微秒之间。2.
xdb
整个文件缓存: 将整个xdb
文件全部加载到内存,内存占用等同于xdb
文件大小,无磁盘IO
操作,保持微秒级别的查询效率。
本文主要介绍基于SpringBoot
整合ip2region
,利用注解
和AOP
实现获取接口请求的IP
所在地。
1、目录结构
1、Ip
: 作用在方法上的注解,用来标注需要获取ip所在城市
的资源接口。
2、IpAop
: 切面类,扫描方法上的Ip
注解,进行逻辑处理。
3、TestResource
:资源接口,也就是要获取请求IP所在城市的接口。
4、IP2RegionUtil
:ip2region
的工具类,加载iPhoneregion.xdb
文件,并提供方法,入参为IP
地址,返回的参数为IP所在城市
,格式为国家|区域|省份|城市|ISP
。
5、Ip2RegionApplication
: 启动类。
6、ip2region.xdb
: ip2region数据包。
2、maven
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/>
</parent>
<groupId>com.caibw</groupId>
<artifactId>ip2region</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3、编码实现
3.1、Ip
注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @auth: caibw
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ip {
}
3.2、IpAop
切面类
package com.caibw.ip2region.aop;
import com.caibw.ip2region.utils.IP2RegionUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @auth: caibw
*/
@Aspect
@Component
public class IpAop {
private static final Logger log = LoggerFactory.getLogger(IpAop.class);
private static final String UNKNOWN = "unknown";
private HttpServletRequest request;
private IP2RegionUtils ip2RegionUtils;
public IpAop(HttpServletRequest request, IP2RegionUtils ip2RegionUtils) {
this.request = request;
this.ip2RegionUtils = ip2RegionUtils;
}
@Pointcut("@annotation(com.caibw.ip2region.annotations.Ip)")
public void pointcut() {
}
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
String ipAddress = getIpAddress(request);
String region = ip2RegionUtils.getRegion(ipAddress);
// region格式:国家|区域|省份|城市|ISP
log.info("ip:{},region:{}", ipAddress, region);
return point.proceed();
}
// 获取 IP 地址
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
ip = "183.14.90.103";
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.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
}
3.3、TestResource
资源接口
import com.caibw.ip2region.annotations.Ip;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
/**
* @auth: caibw
*/
@RestController
public class TestResource {
@Ip
@GetMapping("test")
public String test() {
return "test:" + LocalDateTime.now();
}
}
3.4、IP2RegionUtils
工具类
import org.lionsoul.ip2region.xdb.Searcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
/**
* @auth: caibw
*/
@Component
public class IP2RegionUtils implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(IP2RegionUtils.class);
private static Searcher searcher = null;
@Override
public void run(ApplicationArguments args) throws Exception {
try {
log.info("开始加载 ip2region 数据文件");
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("ip2region.xdb");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
inputStream.close();
searcher = Searcher.newWithBuffer(bytes);
log.info("成功加载 ip2region 数据文件。");
} catch (IOException e) {
log.error("加载 ip2region 失败。{}",e.getMessage());
}
}
public static String getRegion(String ip) {
if (Objects.isNull(searcher)) {
log.error("IP2RegionUtils 没有成功加载数据文件");
return null;
}
try {
return searcher.search(ip);
} catch (Exception e) {
log.error("IP 格式错误:{}",e.getMessage());
return null;
}
}
}
3.5、ip2region.xdb
数据文件和源代码
在绑定资源中,免费下载。