写在前面

最近有个项目,需要根据当前的地理位置获取天气信息.因为这个是一个web端的项目,没有办法根据移动端的定位去获取地理位置的信息,所以在此记录下如何根据请求的ip地址获取地理位置信息,在google搜索问题的时候发现一个不错的解决方案,用到的是Ip2region这个项目

引入依赖

根据官方文档的描述,我们需要先引入依赖

1
2
3
4
5
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>

下载文件

下载xdb文件到本地 ip2region.xdb,我将这个文件放在了resource目录

根据IP查询地址信息

参考官方文档,有三种方式可以查询所属的地址信息分别为:完全基于文件的查询、缓存 VectorIndex 索引、缓存整个 xdb 数据,在此我选择的是第二种,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static String getRegion(String ip) throws IOException {
InputStream in = IPUtils.class.getClassLoader().getResourceAsStream("ip2region.xdb");

File file = new File("ip2region.xdb");
try (OutputStream outputStream = new FileOutputStream(file)) {
int read;
byte[] bytes = new byte[1024];

while ((read = in.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
}

String dbPath = file.getAbsolutePath();

// 1、从 dbPath 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
byte[] vIndex;
try {
vIndex = Searcher.loadVectorIndexFromFile(dbPath);
} catch (Exception e) {
return null;
}

// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
Searcher searcher;
try {
searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
} catch (Exception e) {
return null;
}

// 3、查询
String region = null;
try {
region = searcher.search(ip);
} catch (Exception e) {
return null;
}

// 4、关闭资源
searcher.close();
return region;
}

开发过程中遇到的坑

使用HttpServletRequest获取用户的IP地址

在使用HttpServletRequest获取IP地址的过程中,如果是通过内网访问,或者用了Nginx、FRP等方式反向代理会导致request.getRemoteAddr()方法拿到的是内网地址,并非真实的用户ip,所以我们需要根据header去判断是否经过了反向代理

1
2
3
4
5
6
7
8
9
10
11
12
13
public static String getRealIP(HttpServletRequest request){
// 获取客户端ip地址
String clientIp = request.getHeader("x-forwarded-for");

if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) {
clientIp = request.getRemoteAddr();
}

String[] clientIps = clientIp.split(",");
if(clientIps.length <= 1) return clientIp.trim();

return clientIps[clientIps.length-1].trim();
}

部署项目最好还是部署在公网服务器上,内网服务器上部署是无法拿到用户的真实ip的