SpringBoot通過(guò)ip獲取歸屬地的幾種方式分享
在日常我們逛網(wǎng)站的時(shí)候會(huì)發(fā)現(xiàn)我們登錄后會(huì)出現(xiàn)歸屬地信息,例如:我在廣州登錄會(huì)顯示廣東廣州,有些更加精確的會(huì)顯示到區(qū)縣。
那么我們來(lái)看看有哪些方式來(lái)獲取歸屬地信息?今天我們來(lái)聊一聊。
公共方法:
獲取用戶ip地址:
/** * @author: jiangjs * @description: 獲取當(dāng)前真實(shí)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)){ //多次反向代理后會(huì)有多個(gè)Ip值,第一個(gè)Ip才是真實(shí)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報(bào)錯(cuò):{}",e.getMessage()); } return ""; } }
1、Ip2Region
Ip2Region:見(jiàn)名知意,就是ip轉(zhuǎn)換成區(qū)域。根據(jù)碼云上的簡(jiǎn)介,Ip2Region是一個(gè)離線IP地址定位庫(kù)和IP定位數(shù)據(jù)管理框架,能夠10微妙級(jí)別的查詢(xún)效率,提供眾多主流編程語(yǔ)言的xdb數(shù)據(jù)生成和查詢(xún)客戶端實(shí)現(xiàn)。
1.1 特性
其主要特性有以下幾點(diǎn):
1.1.1 IP數(shù)據(jù)管理框架
xdb 支持億級(jí)別的 IP 數(shù)據(jù)段行數(shù),默認(rèn)的 region 信息都固定了格式:國(guó)家|區(qū)域|省份|城市|ISP,缺省的地域信息默認(rèn)是0。 region 信息支持完全自定義,我們可以按照自己的需求來(lái)管理IP定位數(shù)據(jù)。
1.1.2 數(shù)據(jù)去重和壓縮
xdb 格式生成程序會(huì)自動(dòng)去重和壓縮部分?jǐn)?shù)據(jù),默認(rèn)的全部 IP 數(shù)據(jù),生成的 ip2region.xdb 數(shù)據(jù)庫(kù)是 11MiB,隨著數(shù)據(jù)的詳細(xì)度增加數(shù)據(jù)庫(kù)的大小也慢慢增大。
1.1.3 極速查詢(xún)響應(yīng)
基于xdb文件查詢(xún)單次響應(yīng)時(shí)間在10微妙級(jí)別,可以通過(guò)以下添加內(nèi)存的方式加速查詢(xún):
- vIndex 索引緩存 :使用固定的 512KiB 的內(nèi)存空間緩存 vector index 數(shù)據(jù),減少一次 IO 磁盤(pán)操作,保持平均查詢(xún)效率穩(wěn)定在10-20微秒之間。
- xdb 整個(gè)文件緩存:將整個(gè) xdb 文件全部加載到內(nèi)存,內(nèi)存占用等同于 xdb 文件大小,無(wú)磁盤(pán) IO 操作,保持微秒級(jí)別的查詢(xún)效率。
1.2 代碼實(shí)現(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工具類(lèi) * @date: 2022/7/28 14:31 **/ @Component public class Ip2RegionUtil { public static final Logger log = LoggerFactory.getLogger(Ip2RegionUtil.class); /** * 將整個(gè)庫(kù)進(jìn)行緩存到內(nèi)存,基于這個(gè)庫(kù)創(chuàng)建查詢(xún)對(duì)象來(lái)實(shí)現(xiàn)基于文件的查詢(xún) * 獲取Searcher */ private Searcher getSearcher() throws IOException { //獲取地址下的庫(kù)數(shù)據(jù) byte[] bytes = Searcher.loadContentFromFile("D:\ipchange\ip2region\ip2region.xdb"); return Searcher.newWithBuffer(bytes); } /** * 根據(jù)ip地址直接返回國(guó)家、省、城市信息 * @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轉(zhuǎn)地址報(bào)錯(cuò):%s",e.getMessage())); e.printStackTrace(); return ""; }finally { try { if (Objects.nonNull(searcher)){ searcher.close(); } }catch (Exception e){ e.printStackTrace(); } } } }
ip2region.xdb
:為ip對(duì)應(yīng)地址的xdb文件,這個(gè)需要不定期的進(jìn)行更新,更新方法可參考ip2region的碼云地址。
1.3 測(cè)試
我們提供兩個(gè)方法,一個(gè)方法直接獲取ip地址,另一個(gè)通過(guò)ip138獲取到的ip地址,傳入到方法進(jìn)行歸屬地的查詢(xún)。
@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)); } }
直接在瀏覽器上進(jìn)行訪問(wèn):
1、直接訪問(wèn)
2、通過(guò)ip138查詢(xún)的IP
通過(guò)測(cè)試的結(jié)果我們可以看到,直接獲取到內(nèi)網(wǎng)的地址并沒(méi)有解析出地址,而是直接提示了內(nèi)網(wǎng)。而通過(guò)ip138查詢(xún)到外網(wǎng)地址,能夠正常的解析出地址。
2、geoip2
geopip2:是國(guó)外的,一個(gè)只有100kb的數(shù)據(jù)庫(kù),小巧實(shí)用,加載時(shí)間極短,查詢(xún)效率高,項(xiàng)目只包含大陸用戶需要的中國(guó)IP地址段。geopip2是免費(fèi)的,里面既包含地理信息,同時(shí)也包含了經(jīng)緯度。GeoIP依賴(lài)MaxMind的IP數(shù)據(jù),需要頻繁更新。
大家有興趣的話可以直接取git上看看介紹。
2.1 代碼實(shí)現(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所在國(guó)家 * @param ip 需查詢(xún)的IP * @return 返回查詢(xún)結(jié)果 * @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 需查詢(xún)的IP * @return 返回查詢(xún)結(jié)果 * @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 需查詢(xún)的IP * @return 返回查詢(xún)結(jié)果 * @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所在經(jīng)緯度 * @param ip 需查詢(xún)的IP * @return 返回查詢(xún)結(jié)果 * @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(); //獲取經(jīng)度信息 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ù)庫(kù)報(bào)錯(cuò):{}",e.getMessage()); }finally { try { if (Objects.nonNull(stream)){ stream.close(); } }catch (Exception e){ e.printStackTrace(); } } return null; } /** * 根據(jù)Ip獲取歸屬信息 * @param ip 用戶Ip * @return 返回查詢(xún)結(jié)果 * @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對(duì)象。后續(xù)使用這個(gè)對(duì)象來(lái)根據(jù)Ip讀取國(guó)家、省級(jí)、市級(jí)、經(jīng)緯度等信息。
2.2 測(cè)試
測(cè)試也跟ip2region一樣,提供兩個(gè)方法,一個(gè)測(cè)試內(nèi)網(wǎng),一個(gè)傳遞外網(wǎng)進(jìn)行測(cè)試。
@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對(duì)應(yīng)地址等信息報(bào)錯(cuò)"); } @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對(duì)應(yīng)地址等信息報(bào)錯(cuò)"); } }
直接在瀏覽器上進(jìn)行訪問(wèn):
1、直接訪問(wèn)
2、通過(guò)外網(wǎng)查詢(xún)的IP
通過(guò)測(cè)試的結(jié)果我們可以看到,在解析內(nèi)網(wǎng)ip時(shí)直接報(bào)錯(cuò),提示數(shù)據(jù)庫(kù)中沒(méi)有這個(gè)ip。而通過(guò)外網(wǎng)地址就能夠正常的解析出地址。
3、頁(yè)面抓取
網(wǎng)絡(luò)上有很多這種根據(jù)ip查詢(xún)歸屬地的網(wǎng)站,因此我們可以通過(guò)調(diào)用其接口,通過(guò)返回頁(yè)面來(lái)抓取歸屬地信息。例如:我們常用的ip138就有可以抓取到。
抓取頁(yè)面就需要解析頁(yè)面,解析頁(yè)面我們可以使用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; } }
工具類(lèi)中通過(guò)訪問(wèn)地址獲取到Document對(duì)象,然后在進(jìn)行解析,從而拿到歸屬地信息。
3.3 測(cè)試
@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("查詢(xún)失敗"); } } }
直接訪問(wèn)地址:
從結(jié)果來(lái)看,其可以精確到區(qū)的。
【總結(jié)】
上述中介紹了三種通過(guò)Ip獲取歸屬地的方法,相比較而言:
ip2region相對(duì)用的比較多,很多博客、技術(shù)文檔都會(huì)進(jìn)行介紹,而且封裝使用也比較簡(jiǎn)單,執(zhí)行速度也比較快;
geoip2:相對(duì)比較細(xì),可以根據(jù)需求直接獲取國(guó)家,省級(jí)等信息,但是伴隨的就是封裝稍微復(fù)雜點(diǎn),而且在解析內(nèi)網(wǎng)時(shí),由于數(shù)據(jù)庫(kù)中沒(méi)有對(duì)應(yīng)ip而報(bào)錯(cuò)。
頁(yè)面抓取:這個(gè)就不推薦了,畢竟依賴(lài)于第三方,如果服務(wù)掛了的話就沒(méi)法使用了。
至于選擇ip2region還是geoip2,這個(gè)就要看具體的也無(wú)需求。
對(duì)了,ip2region和geoip2要去經(jīng)常更新庫(kù)哦。
好了,今天就跟大家叨叨到這,謝謝大家。
碼云地址:https://gitee.com/lovequeena/iptoaddress.git
以上就是SpringBoot通過(guò)ip獲取歸屬地的幾種方式分享的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot獲取歸屬地的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA集成DeepSeek的詳細(xì)教程(保姆級(jí)教程)
DeepSeek作為一款強(qiáng)大的代碼搜索和分析工具,能夠幫助開(kāi)發(fā)者快速定位代碼、理解項(xiàng)目結(jié)構(gòu)以及優(yōu)化代碼質(zhì)量,本文將詳細(xì)介紹如何在IntelliJ?IDEA中集成DeepSeek,并展示如何利用它來(lái)提升開(kāi)發(fā)效率,感興趣的朋友一起看看吧2025-02-02java 一個(gè)類(lèi)實(shí)現(xiàn)兩個(gè)接口的案例
這篇文章主要介紹了java 一個(gè)類(lèi)實(shí)現(xiàn)兩個(gè)接口的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10Retrofit+Rxjava下載文件進(jìn)度的實(shí)現(xiàn)
這篇文章主要介紹了Retrofit+Rxjava下載文件進(jìn)度的實(shí)現(xiàn),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-11-11解決使用@ManyToMany查詢(xún)數(shù)據(jù)時(shí)的死循環(huán)問(wèn)題
這篇文章主要介紹了解決使用@ManyToMany查詢(xún)數(shù)據(jù)時(shí)的死循環(huán)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12@PathVariable為空時(shí)指定默認(rèn)值的操作
這篇文章主要介紹了@PathVariable為空時(shí)指定默認(rèn)值的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02基于spring-mvc.xml和application-context.xml的配置與深入理解
這篇文章主要介紹了spring-mvc.xml和application-context.xml的配置與深入解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08SpringBoot動(dòng)態(tài)表操作服務(wù)的實(shí)現(xiàn)代碼
在現(xiàn)代的應(yīng)用開(kāi)發(fā)中,尤其是在數(shù)據(jù)庫(kù)設(shè)計(jì)不斷變化的情況下,動(dòng)態(tài)操作數(shù)據(jù)庫(kù)表格成為了不可或缺的一部分,在本篇文章中,我們將以一個(gè)典型的動(dòng)態(tài)表操作服務(wù)為例,詳細(xì)介紹如何在 Spring Boot 中使用 JdbcTemplate 實(shí)現(xiàn)動(dòng)態(tài)表管理,需要的朋友可以參考下2025-01-01Java開(kāi)發(fā)人員最常犯的5個(gè)錯(cuò)誤總結(jié)
作為一名java開(kāi)發(fā)程序員,不知道大家有沒(méi)有遇到過(guò)一些匪夷所思的bug。這些錯(cuò)誤通常需要您幾個(gè)小時(shí)才能解決。今天,小編總結(jié)一些常見(jiàn)的編碼錯(cuò)誤,然后給出解決方案。希望大家在日常編碼中能夠避免這樣的問(wèn)題2022-12-12