SpringBoot通過ip獲取歸屬地的幾種方式分享
在日常我們逛網站的時候會發(fā)現(xiàn)我們登錄后會出現(xiàn)歸屬地信息,例如:我在廣州登錄會顯示廣東廣州,有些更加精確的會顯示到區(qū)縣。

那么我們來看看有哪些方式來獲取歸屬地信息?今天我們來聊一聊。
公共方法:
獲取用戶ip地址:
/**
* @author: jiangjs
* @description: 獲取當前真實IP
* @date: 2022/5/23 16:42
**/
public class GetUserIP {
public static final Logger log = LoggerFactory.getLogger(GetUserIP.class);
public static String getUserIpAddress(HttpServletRequest request){
try {
String realIp = request.getHeader("X-Real-IP");
String forwardedIp = request.getHeader("X-Forwarded-For");
String clientIp = request.getHeader("Proxy-Client-IP");
String proxyIp = request.getHeader("WL-Proxy-Client-IP");
String httpClientIp = request.getHeader("HTTP_CLIENT_IP");
String xForwardedIp = request.getHeader("HTTP_X_FORWARDED_FOR");
String remoteAddress = InetAddress.getLocalHost().getHostAddress();
if(StringUtils.isNotEmpty(forwardedIp) && !"unKnown".equalsIgnoreCase(forwardedIp)){
//多次反向代理后會有多個Ip值,第一個Ip才是真實Ip
int index = forwardedIp.indexOf(",");
if(index != -1){
return forwardedIp.substring(0,index);
}else{
return forwardedIp;
}
}
return StringUtils.isNoneBlank(realIp) && !"unKnown".equalsIgnoreCase(realIp) ? realIp :
StringUtils.isNoneBlank(clientIp) && !"unKnown".equalsIgnoreCase(clientIp) ? clientIp :
StringUtils.isNoneBlank(proxyIp) && !"unKnown".equalsIgnoreCase(proxyIp) ? proxyIp :
StringUtils.isNoneBlank(httpClientIp) && !"unKnown".equalsIgnoreCase(httpClientIp) ? httpClientIp :
StringUtils.isNoneBlank(xForwardedIp) && !"unKnown".equalsIgnoreCase(xForwardedIp) ? xForwardedIp : remoteAddress;
}catch (Exception e){
e.printStackTrace();
log.error("獲取用戶Ip報錯:{}",e.getMessage());
}
return "";
}
}1、Ip2Region
Ip2Region:見名知意,就是ip轉換成區(qū)域。根據(jù)碼云上的簡介,Ip2Region是一個離線IP地址定位庫和IP定位數(shù)據(jù)管理框架,能夠10微妙級別的查詢效率,提供眾多主流編程語言的xdb數(shù)據(jù)生成和查詢客戶端實現(xiàn)。
1.1 特性
其主要特性有以下幾點:
1.1.1 IP數(shù)據(jù)管理框架
xdb 支持億級別的 IP 數(shù)據(jù)段行數(shù),默認的 region 信息都固定了格式:國家|區(qū)域|省份|城市|ISP,缺省的地域信息默認是0。 region 信息支持完全自定義,我們可以按照自己的需求來管理IP定位數(shù)據(jù)。
1.1.2 數(shù)據(jù)去重和壓縮
xdb 格式生成程序會自動去重和壓縮部分數(shù)據(jù),默認的全部 IP 數(shù)據(jù),生成的 ip2region.xdb 數(shù)據(jù)庫是 11MiB,隨著數(shù)據(jù)的詳細度增加數(shù)據(jù)庫的大小也慢慢增大。
1.1.3 極速查詢響應
基于xdb文件查詢單次響應時間在10微妙級別,可以通過以下添加內存的方式加速查詢:
- vIndex 索引緩存 :使用固定的 512KiB 的內存空間緩存 vector index 數(shù)據(jù),減少一次 IO 磁盤操作,保持平均查詢效率穩(wěn)定在10-20微秒之間。
- xdb 整個文件緩存:將整個 xdb 文件全部加載到內存,內存占用等同于 xdb 文件大小,無磁盤 IO 操作,保持微秒級別的查詢效率。
1.2 代碼實現(xiàn)
1.2.1 引入jar包
<dependency> <groupId>org.lionsoul</groupId> <artifactId>ip2region</artifactId> <version>2.6.5</version> </dependency>
1.2.2 封裝工具
/**
* @author: jiangjs
* @description: 創(chuàng)建Ip2Region工具類
* @date: 2022/7/28 14:31
**/
@Component
public class Ip2RegionUtil {
public static final Logger log = LoggerFactory.getLogger(Ip2RegionUtil.class);
/**
* 將整個庫進行緩存到內存,基于這個庫創(chuàng)建查詢對象來實現(xiàn)基于文件的查詢
* 獲取Searcher
*/
private Searcher getSearcher() throws IOException {
//獲取地址下的庫數(shù)據(jù)
byte[] bytes = Searcher.loadContentFromFile("D:\ipchange\ip2region\ip2region.xdb");
return Searcher.newWithBuffer(bytes);
}
/**
* 根據(jù)ip地址直接返回國家、省、城市信息
* @param ip ip
* @return 返回地址
*/
public String changeIpToAddress(String ip){
//獲取searcher
Searcher searcher = null;
try {
searcher = getSearcher();
return searcher.search(ip);
}catch (Exception e){
log.error(String.format("ip轉地址報錯:%s",e.getMessage()));
e.printStackTrace();
return "";
}finally {
try {
if (Objects.nonNull(searcher)){
searcher.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}ip2region.xdb:為ip對應地址的xdb文件,這個需要不定期的進行更新,更新方法可參考ip2region的碼云地址。
1.3 測試
我們提供兩個方法,一個方法直接獲取ip地址,另一個通過ip138獲取到的ip地址,傳入到方法進行歸屬地的查詢。
@RestController
@RequestMapping("/ip2region")
public class Ip2RegionChangeController {
@Autowired
private Ip2RegionUtil regionUtil;
@GetMapping("/getIpToAddress.do")
public ResultUtil<String> getIpToAddressNoIp(HttpServletRequest request){
String userIp = GetUserIP.getUserIpAddress(request);
return ResultUtil.success(regionUtil.changeIpToAddress(userIp));
}
@GetMapping("/getIpToAddress.do/{ip}")
public ResultUtil<String> getIpToAddress(@PathVariable("ip") String ip){
return ResultUtil.success(regionUtil.changeIpToAddress(ip));
}
}直接在瀏覽器上進行訪問:
1、直接訪問

2、通過ip138查詢的IP

通過測試的結果我們可以看到,直接獲取到內網的地址并沒有解析出地址,而是直接提示了內網。而通過ip138查詢到外網地址,能夠正常的解析出地址。
2、geoip2
geopip2:是國外的,一個只有100kb的數(shù)據(jù)庫,小巧實用,加載時間極短,查詢效率高,項目只包含大陸用戶需要的中國IP地址段。geopip2是免費的,里面既包含地理信息,同時也包含了經緯度。GeoIP依賴MaxMind的IP數(shù)據(jù),需要頻繁更新。
大家有興趣的話可以直接取git上看看介紹。
2.1 代碼實現(xiàn)
2.1.1 引入jar包
<dependency> <groupId>com.maxmind.geoip2</groupId> <artifactId>geoip2</artifactId> <version>2.8.1</version> </dependency>
2.1.2 封裝工具
/**
* @author: jiangjs
* @description:
* @date: 2022/5/23 15:12
**/
public class GeoLiteUtil {
public static final Logger log = LoggerFactory.getLogger(GeoLiteUtil.class);
/**
* 獲取ip所在國家
* @param ip 需查詢的IP
* @return 返回查詢結果
* @throws UnknownHostException 異常
*/
private static String getCountry(String ip) throws Exception {
DatabaseReader reader = getDatabaseReader();
return Objects.nonNull(reader) ? reader.city(InetAddress.getByName(ip)).getCountry().getNames().get("zh-CN") : "";
}
/**
* 獲取ip所在省份
* @param ip 需查詢的IP
* @return 返回查詢結果
* @throws UnknownHostException 異常
*/
private static String getProvince(String ip) throws Exception {
DatabaseReader reader = getDatabaseReader();
return Objects.nonNull(reader) ? reader.city(InetAddress.getByName(ip)).getMostSpecificSubdivision().getNames().get("zh-CN") : "";
}
/**
* 獲取ip所在市
* @param ip 需查詢的IP
* @return 返回查詢結果
* @throws UnknownHostException 異常
*/
private static String getCity(String ip) throws Exception {
DatabaseReader reader = getDatabaseReader();
return Objects.nonNull(reader) ? reader.city(InetAddress.getByName(ip)).getCity().getNames().get("zh-CN") : "";
}
/**
* 獲取ip所在經緯度
* @param ip 需查詢的IP
* @return 返回查詢結果
* @throws UnknownHostException 異常
*/
private static JSONObject getLongitudeAndLatitude (String ip) throws Exception {
DatabaseReader reader = getDatabaseReader();
JSONObject resJson = new JSONObject();
resJson.put("latitude",0d);
resJson.put("longitude",0d);
if (Objects.nonNull(reader)){
//獲取緯度信息
Double latitude = reader.city(InetAddress.getByName(ip)).getLocation().getLatitude();
//獲取經度信息
Double longitude = reader.city(InetAddress.getByName(ip)).getLocation().getLongitude();
resJson.put("latitude",latitude);
resJson.put("longitude",longitude);
}
return resJson;
}
private static DatabaseReader getDatabaseReader(){
InputStream stream = null;
try {
stream = GeoLiteUtil.class.getClassLoader().getResourceAsStream("static/geo/GeoLite2-City.mmdb");
return new DatabaseReader.Builder(stream).build();
}catch (Exception e){
e.printStackTrace();
log.error("獲取geolite2數(shù)據(jù)庫報錯:{}",e.getMessage());
}finally {
try {
if (Objects.nonNull(stream)){
stream.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}
/**
* 根據(jù)Ip獲取歸屬信息
* @param ip 用戶Ip
* @return 返回查詢結果
* @throws Exception 異常
*/
public static JSONObject getIpToAddress(String ip) throws Exception {
StringJoiner address = new StringJoiner(",");
address.add(getCountry(ip));
address.add(getProvince(ip));
address.add(getCity(ip));
JSONObject resJson = GeoLiteUtil.getLongitudeAndLatitude(ip);
resJson.put("address",String.valueOf(address));
return resJson;
}
}getDatabaseReader() :讀取本地的GeoLite2-City.mmdb并創(chuàng)建DatabaseReader對象。后續(xù)使用這個對象來根據(jù)Ip讀取國家、省級、市級、經緯度等信息。
2.2 測試
測試也跟ip2region一樣,提供兩個方法,一個測試內網,一個傳遞外網進行測試。
@RestController
@RequestMapping("/change/ip")
public class GeoIpChangeController {
@Resource
private GeoIpChangeService geoIpChangeService;
@GetMapping("/getIpAddress.do/{ip}")
public ResultUtil<JSONObject> getIpAddress(@PathVariable("ip") String ip, HttpServletRequest request){
try {
return ResultUtil.success(GeoLiteUtil.getIpToAddress(ip));
}catch (Exception e){
e.printStackTrace();
}
return ResultUtil.error("獲取ip對應地址等信息報錯");
}
@GetMapping("/getLocationIpAddress.do")
public ResultUtil<JSONObject> getLocationIpAddress( HttpServletRequest request){
String userIp = GetUserIP.getUserIpAddress();
try {
return ResultUtil.success(GeoLiteUtil.getIpToAddress(userIp));
}catch (Exception e){
e.printStackTrace();
}
return ResultUtil.error("獲取ip對應地址等信息報錯");
}
}直接在瀏覽器上進行訪問:
1、直接訪問


2、通過外網查詢的IP

通過測試的結果我們可以看到,在解析內網ip時直接報錯,提示數(shù)據(jù)庫中沒有這個ip。而通過外網地址就能夠正常的解析出地址。
3、頁面抓取
網絡上有很多這種根據(jù)ip查詢歸屬地的網站,因此我們可以通過調用其接口,通過返回頁面來抓取歸屬地信息。例如:我們常用的ip138就有可以抓取到。
抓取頁面就需要解析頁面,解析頁面我們可以使用jsoup。
3.1 引入jar包
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.15.2</version> </dependency>
3.2 封裝方法
/**
* @author: jiangjs
* @description:
* @date: 2023/9/20 16:12
**/
public class Ip138ChangeUtil {
private static final String URL = "http://www.ip138.com/iplookup.asp?ip=";
private static final Logger log = LoggerFactory.getLogger(Ip138ChangeUtil.class);
public static JSONObject getIpToAddress(String ip) throws IOException {
JSONObject resJson = new JSONObject();
Document document = Jsoup.connect(URL + ip + "&action=2").get();
Elements es = document.select("script").eq(1);
for (Element e : es) {
String[] data = e.data().split("var");
for (String variable : data) {
if (variable.contains("=") && variable.contains("ip_result")){
String[] va = StringUtils.split(variable, "=");
String value = va[1].trim().substring(0,va[1].trim().length() - 1);
JSONObject object = JSONObject.parseObject(value);
log.info(String.format("獲取到的數(shù)據(jù)值:%s",value));
resJson.put("address", object.getString("ASN歸屬地"));
}
}
}
return resJson;
}
}工具類中通過訪問地址獲取到Document對象,然后在進行解析,從而拿到歸屬地信息。
3.3 測試
@RestController
@RequestMapping("/ip138")
public class Ip138ChangeController {
@GetMapping("/getIpToAddress.do/{ip}")
public ResultUtil<JSONObject> getIpToAddress(@PathVariable("ip") String ip){
try {
return ResultUtil.success(Ip138ChangeUtil.getIpToAddress(ip));
} catch (IOException e) {
e.printStackTrace();
return ResultUtil.error("查詢失敗");
}
}
}直接訪問地址:

從結果來看,其可以精確到區(qū)的。
【總結】
上述中介紹了三種通過Ip獲取歸屬地的方法,相比較而言:
ip2region相對用的比較多,很多博客、技術文檔都會進行介紹,而且封裝使用也比較簡單,執(zhí)行速度也比較快;
geoip2:相對比較細,可以根據(jù)需求直接獲取國家,省級等信息,但是伴隨的就是封裝稍微復雜點,而且在解析內網時,由于數(shù)據(jù)庫中沒有對應ip而報錯。
頁面抓?。哼@個就不推薦了,畢竟依賴于第三方,如果服務掛了的話就沒法使用了。
至于選擇ip2region還是geoip2,這個就要看具體的也無需求。
對了,ip2region和geoip2要去經常更新庫哦。
好了,今天就跟大家叨叨到這,謝謝大家。
碼云地址:https://gitee.com/lovequeena/iptoaddress.git
以上就是SpringBoot通過ip獲取歸屬地的幾種方式分享的詳細內容,更多關于SpringBoot獲取歸屬地的資料請關注腳本之家其它相關文章!
相關文章
Retrofit+Rxjava下載文件進度的實現(xiàn)
這篇文章主要介紹了Retrofit+Rxjava下載文件進度的實現(xiàn),非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-11-11
解決使用@ManyToMany查詢數(shù)據(jù)時的死循環(huán)問題
這篇文章主要介紹了解決使用@ManyToMany查詢數(shù)據(jù)時的死循環(huán)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
基于spring-mvc.xml和application-context.xml的配置與深入理解
這篇文章主要介紹了spring-mvc.xml和application-context.xml的配置與深入解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot動態(tài)表操作服務的實現(xiàn)代碼
在現(xiàn)代的應用開發(fā)中,尤其是在數(shù)據(jù)庫設計不斷變化的情況下,動態(tài)操作數(shù)據(jù)庫表格成為了不可或缺的一部分,在本篇文章中,我們將以一個典型的動態(tài)表操作服務為例,詳細介紹如何在 Spring Boot 中使用 JdbcTemplate 實現(xiàn)動態(tài)表管理,需要的朋友可以參考下2025-01-01

