GitHub项目地址:https://github.com/lionsoul2014/ip2region
我们首先需要下载一个 ip2region.xdb 的文件
下载地址:https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.xdb
打开后点击如图的Download图标即可下载。
下载完成后,需要将该文件放到我们的项目中。
ps:我是直接放到服务器的,因为放在项目的资源文件夹下,当我们调试的时候使用Java Spring自带的工具去获取该文件的绝对路径时,没有任何问题,能够正常获取到。但是当我们打成jar包部署到测试或者开发环境,就会拿不到该文件,具体情况可以看 spring boot集成,db放在resource下,打jar包后可以加载资源,但search不生效。本地debug生效 该Issues,如果不想麻烦的话直接放在服务器上吧,这样我们就能直接配好绝对路径了,少走弯路。
首先需要导包:
<dependency>
<groupId>org.lionsoulgroupId>
<artifactId>ip2regionartifactId>
<version>2.6.5version>
dependency>
/**
* 通过ip地址获取所属国家-离线方式
*
* @param ip IP地址
* @param dbPath ip2region.xdb路径
* @return 国家信息
*/
public static String getCountryByIpOffline(String ip, String dbPath) throws Exception{
// 1、从 dbPath 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
byte[] vIndex = Searcher.loadVectorIndexFromFile(dbPath);
// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
Searcher searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
// 3、查询
return searcher.search(ip);
}
其中dbPath我是走的nacos配置,这个路径就是我们服务器存放ip2region.xdb的路径。
ip2region xdb 数据结构和查询过程的详解:ip2region xdb 数据结构和查询过程的详解
这篇文章写得很清楚我就不再叙述了。
ps:需要特别提一点的是,该方式查询国内的IP还是比较准确的,基本上能精确到省市。但是国外的一些IP,大体的国家信息没啥问题,再具体的信息可能就不全了。但是一般项目都够用。
查询结果统一采用的是 国家|区域|省份|城市|ISP 的方式返回。
在线方式网页测试:在线方式网页测试
只需要通过GET请求该链接 http://ip-api.com/json/ 并后面拼接你的ip,如 http://ip-api.com/json/24.48.0.1 即可。
也能通过添加lang参数让该接口返回中文。如 http://ip-api.com/json/24.48.0.1?lang=zh-CN,就可以返回中文数据了。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
@Slf4j
@Component
public class IpUtil {
private static RestTemplate restTemplate;
private final RestTemplate template;
public static final String IP_API_URL = "http://ip-api.com/json/";
public IpUtil(RestTemplate restTemplate) {
this.template = restTemplate;
}
/**
* 初始化 RestTemplate
*/
@PostConstruct
public void init() {
setRestTemplate(this.template);
}
/**
* 初始化 RestTemplate
*/
private static void setRestTemplate(RestTemplate template) {
restTemplate = template;
}
/**
* 通过ip地址获取所属国家-在线方式
*
* @param ip ip地址
* @return 国家信息
*/
public static CountryInfo getCountryByIpOnline(String ip) {
ResponseEntity<CountryInfo> entity = restTemplate.getForEntity(
IP_API_URL + ip + "?lang=zh-CN",
CountryInfo.class
);
return entity.getBody();
}
/**
* 在线方式接收类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class CountryInfo {
private String query;
private String status;
private String country;
private String countryCode;
private String region;
private String regionName;
private String city;
private String zip;
private String lat;
private String lon;
private String timezone;
private String isp;
private String org;
private String as;
}
}
代码也非常简单。
这里我推荐我们项目中都统一采用先离线后在线方式兜底的写法来处理。因为谁也不能保证离线的就一定能查询到具体的归属地,如果我们使用离线方式查询不到具体的归属地(获取结果为null),那么我建议再使用在线查询的方式来做一个兜底处理,如果都拿不到,再返回空,或者根据自己的业务具体处理。
以上仅作为建议。
这里附带一下查询请求的ip地址方法,有需要的可以直接拿走。
/**
* 获取Ip地址
*
* @author dxc
* @date 2022/7/12 18:18
*/
public final class IpUtil {
private static final String UNKNOWN = "unknown";
private IpUtil() {}
/**
* 获取客户端ip 地址
*
* @param request 请求
* @return {@link String}
*/
public static String getClientIpAddr(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_CLUSTER_CLIENT_IP");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_FORWARDED_FOR");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_FORWARDED");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_VIA");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("REMOTE_ADDR");
}
if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
}