某些业务需要获取请求IP以及将IP解析成省份之类的,于是我写了一个工具类,可以直接COPY
/**
* IP工具类
* @author xxl
* @since 2023/11/9
*/
@Slf4j
public class IPUtils {
/**
* 过滤本地地址
*/
public static final String LOCAL_ADDRESS = "127.0.0.1";
public static final String LOOP_BACK_ADDRESS = "0:0:0:0:0:0:0:1";
/**
* 离线查询IP地址的数据文件,这个文件去ip2region GitHub官方仓库获取
*/
private static String IP_ADDRESS_FILE_PATH ;
/**
* 前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,
* 每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,
* 从而加速查询,减少 IO 压力。
*/
private static byte[] vIndex= null;
private static Searcher searcher = null;
static {
try {
//这个ip2region.xdb我是放在/resources/data/ip2region.xdb目录下的
String fileName = "/data/ip2region.xdb";
File existFile = FileUtil.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
if(!FileUtil.exist(existFile)) {
InputStream resourceAsStream = IPUtils.class.getResourceAsStream(fileName);
FileUtil.writeFromStream(resourceAsStream, existFile);
}
IP_ADDRESS_FILE_PATH = existFile.getPath();
// 从 db 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
vIndex = Searcher.loadVectorIndexFromFile(IP_ADDRESS_FILE_PATH);
// 使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
searcher = Searcher.newWithVectorIndex(IP_ADDRESS_FILE_PATH, vIndex);
} catch (Exception e) {
throw new RuntimeException("IPUtils class load error", e);
}
}
/**
* 每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局的制度 vIndex 缓存。
* @param ip IP
* @return IP地址
*/
public static String getCity(String ip) {
String search = null;
try {
search = searcher.search(ip);
} catch (Exception e) {
throw new RuntimeException("getCity fail",e);
}
return search;
}
/**
* 获取 IP
*
* @param request 请求
* @return 字符串
*/
public static String getIp(HttpServletRequest request) {
String ip = null;
try {
//解析IP
ip = new ChainUtils<>(request.getHeader("X-Forwarded-For"))
//多次反向代理后会有多个ip值,第一个ip才是真实ip
.chain(re -> StrUtil.isNotBlank(re) ? (re.contains(DOT) ? re.substring(0, re.indexOf(DOT)) : EMPTY) : re)
//依次查找IP
.chain(re -> StrUtil.isNotBlank(re) ? re : request.getHeader("X-Real-IP"))
.chain(re -> StrUtil.isNotBlank(re) ? re : request.getHeader("Proxy-Client-IP"))
.chain(re -> StrUtil.isNotBlank(re) ? re : request.getHeader("WL-Proxy-Client-IP"))
.chain(re -> StrUtil.isNotBlank(re) ? re : request.getHeader("HTTP_CLIENT_IP"))
.chain(re -> StrUtil.isNotBlank(re) ? re : request.getHeader("HTTP_X_FORWARDED_FOR"))
.chain(re -> StrUtil.isNotBlank(re) ? re : request.getRemoteAddr())
//过滤本地地址
.chain(re -> StrUtil.isNotBlank(re) ? (LOOP_BACK_ADDRESS.equals(re) ? LOCAL_ADDRESS : re) : re)
.getValue(true);
} catch (Exception e) {
log.error("getIp fail", e);
}
return ip;
}
}
使用以上工具类需要以下依赖和一个自定义工具类
<!-- 解析IP -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
ChainUtil:为什么写这个工具类在解析请求中的IP参考了https://blog.csdn.net/chwshuang/article/details/71940858
此博客中部分代码如下图可以发现有很多if判断,很难看不好维护。于是就写了以下的工具类
/**
* @author: xxl
* @since: 2023/11/9
* @description: 解决if,else地狱
*/
@AllArgsConstructor
public class ChainUtil<T> {
/**
* 存储的值
*/
private T value;
public <E> ChainUtil<E> chain(Function<T,E> function) {
return new ChainUtil<>(function.apply(value));
}
/**
* 获取存储的值
*
* @param isNullForException 如果存储的值为null是否抛出异常
* @return T
*/
public T getValue(boolean isNullForException) {
if (isNullForException) {
Assert.notNull(value, () -> new RuntimeException("chain value is null"));
}
return value;
}
}
if地狱