一、背景
最近项目来了个新需求,要记录用户每次登录项目时所在的省份/城市信息。
网上有了解过,对于这个功能是如何实现的,一般有两种方法:GPS 定位的信息
和用户 IP 地址
。对于这次需求我采用了用户 IP 地址
来完成功能的实现,因为不是每个客户端都会打开 GPS
,而且有时并不太需要太精确的位置(到城市这个级别即可)。
二、Ip2region
2.1 介绍
ip2region
是一个离线IP地址定位库和IP定位数据管理框架,10微秒级别的查询效率,提供了众多主流编程语言的 xdb 数据生成和查询客户端实现。
⭐️Gitee地址:ip2region
🎁GitHub地址:ip2region
2.2 快速上手
第一步,将整个项目down下来,找到data目录,进入
这里有三份ip地址库,我们将ip2region.xdb复制出来,等下我们的java项目中需要使用到。

第二步,创建maven项目,引入依赖
pom.xml依赖如下(我创建的是springboot脚手架项目,都一样):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.lionsoul</groupId> <artifactId>ip2region</artifactId> <version>2.6.4</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
|
加好依赖后,在resources
目录下创建ip2region
文件夹,把上面的ip2region.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 44 45 46 47 48 49 50 51
| package com.apps;
import org.junit.Test; import org.lionsoul.ip2region.xdb.Searcher; import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit;
@SpringBootTest public class Ip2regionApplicationTests {
@Test public void contextLoads() {
String dbPath = Objects.requireNonNull(Ip2regionApplicationTests.class.getResource("/ip2region.xdb")).getPath(); Searcher searcher = null;
try { searcher = Searcher.newWithFileOnly(dbPath); } catch (IOException e) { System.out.printf("无法创建搜索器`%s`: %s\n", dbPath, e); return; }
String ip = "183.14.90.103";
try { long sTime = System.nanoTime(); String region = searcher.search(ip); long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime)); System.out.printf("{区域: %s, ioCount: %d, took: %d}",region, searcher.getIOCount(),cost); } catch (Exception e) { System.out.printf("无法搜索(%s): %s\n", ip, e); }
}
}
|
输出结果如下:

这样就完成了根据用户的IP地址
来获取所在的省份/城市
信息。
三、项目实现
3.1 思路分析
通过上面简单的例子我们已经可以通过ip获取地域了,那么接下来将实现如何监控Controller接口的访问地址。
首先,在一个项目中肯定有很多接口,所以我们不能直接在接口中写代码的方式去实现,这样代码复杂度、耦合度太高。所以我打算在这里使用注解切面的方式实现,只需要在接口方法上加上 @Ip
注解就可以实现。
其次,有些项目中会使用 Nginx
等反向代理软件,则不能通过 request.getRemoteAddr()
获取 IP地址
,如果使用了多级反向代理的话, X-Forwarded-For
的值并不止一个,而是一串 IP地址
, X-Forwarded-For
中第一个非 unknown
的有效 IP字符串
,则为 真实IP地址
。
3.2 配置文件
SpringBoot项目pom.xml文件:
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
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.lionsoul</groupId> <artifactId>ip2region</artifactId> <version>2.6.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
|
3.3 项目代码
项目结构

SpringbootIpApplication.java
1 2 3 4 5 6 7 8 9 10 11
| package com.example.springbootip; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootIpApplication {
public static void main(String[] args) { SpringApplication.run(SpringbootIpApplication.class, args); }
}
|
TestController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.example.springbootip.controller; import com.example.springbootip.ip2region.Ip; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") public class TestController { @GetMapping("/hello") @Ip public String hello() { return "hello"; } }
|
Ip.java
1 2 3 4 5 6 7 8 9
| package com.example.springbootip.ip2region; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Ip { }
|
IpAspect.java
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
| package com.example.springbootip.ip2region; import com.example.springbootip.util.AddressUtil; import com.example.springbootip.util.HttpContextUtil; import com.example.springbootip.util.IPUtil; 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.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.text.MessageFormat; @Aspect @Component public class IpAspect {
@Pointcut("@annotation(com.example.springbootip.ip2region.Ip)") public void pointcut() { } @Around("pointcut()") public Object doAround(ProceedingJoinPoint point) throws Throwable { HttpServletRequest request = HttpContextUtil.getHttpServletRequest(); String ip = IPUtil.getIpAddr(request); System.out.println(MessageFormat.format("当前IP为:[{0}];当前IP地址解析出来的地址为:[{1}]", ip, AddressUtil.getCityInfo(ip))); return point.proceed(); } }
|
AddressUtil.java
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 44 45 46 47 48 49 50 51
| package com.example.springbootip.util; import org.apache.commons.io.FileUtils; import org.lionsoul.ip2region.xdb.Searcher; import java.io.File; import java.text.MessageFormat; import java.util.Objects; public class AddressUtil {
private static final String TEMP_FILE_DIR = "/home/admin/app/";
public static String getCityInfo(String ip) { try { String dbPath = Objects.requireNonNull(AddressUtil.class.getResource("/ip2region/ip2region.xdb")).getPath(); File file = new File(dbPath); if (!file.exists()) { dbPath = TEMP_FILE_DIR + "ip.db"; System.out.println(MessageFormat.format("当前目录为:[{0}]", dbPath)); file = new File(dbPath); FileUtils.copyInputStreamToFile(Objects.requireNonNull(AddressUtil.class.getClassLoader().getResourceAsStream("classpath:ip2region/ip2region.xdb")), file); } Searcher searcher = Searcher.newWithFileOnly(dbPath); return searcher.search(ip); } catch (Exception e) { e.printStackTrace(); } return ""; }
public static void main(String[] args) { System.out.println(getCityInfo("1.2.3.4")); }
}
|
HttpContextUtil.java
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
| package com.example.springbootip.util; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Objects;
public class HttpContextUtil { private HttpContextUtil() { }
public static HttpServletRequest getHttpServletRequest() { return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); }
public static HttpServletResponse getHttpServletResponse() { return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse(); } }
|
IPUtil.java
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
| package com.example.springbootip.util; import javax.servlet.http.HttpServletRequest;
public class IPUtil {
private static final String UNKNOWN = "unknown"; protected IPUtil() { }
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
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; }
}
|
打印结果
由于访问路径是:http://127.0.0.1:8080/test/hello,所以本地解析出来的是内网

好,记录完成!