全网的 IP 归属地显示,带你5分钟加上,就是这么简单
最近,繼新浪微博之后,今日頭條、騰訊、抖音、知乎、快手、小紅書、百家號等各大平臺陸陸續續都上線了"網絡用戶IP地址顯示功能",境外用戶顯示的是國家,國內的用戶顯示的省份,而且此項顯示無法關閉,歸屬地強制顯示;
作為技術人,那!這個功能要怎么實現呢?
其實要想實現這個功能還是非常的容易,基于現成 GeoLite2離線庫+免費的在線解析資源,5分鐘就能整合了;
在整合之前,我們先簡單了解一下,要想拿到用戶的位置信息,有那些方式:
終端定位
我們的手機等電子設備都是帶有GPS定位功能的,APP可以申請權限獲取用戶所處的經緯度坐標,根據坐標,就可以知道到用戶所處的位置;比如百度、高德等地圖廠商,就提供了完善的SDK,能非常方便的集成到應用,快速根據經緯度獲取詳細的位置詳細;
優點
- 快捷;
- 準確;
- 誤差小。
缺點
- 依賴硬件支持;
- 依賴用戶授權,如果用戶不授權,APP將拿不到經緯度信息,導致失敗;
IP地址解析
用戶向服務端發起的請求都會帶上IP地址,服務端拿到IP地址后,就能基于IP解析出用戶的所處的位置;
優點
- 無需授權,只要用戶跟服務端交互,服務端就能拿到對應的IP信息
缺點
- 準確性不高,位置可能存在偏差;
- IP庫更新不及時,導致部分IP歸屬地解析失敗。
三方終端上報
比如,我們騎共享單車的時候,我們的位置信息就是通過單車的設備上報到服務器;
優點
- 由三方終端基于GPS定位上報,不會獲取個人設備的信息;
- 準確快捷;
- 專業設備,誤差小;
缺點
- 用戶無法干預,信息會被迫強制上傳至服務端,用戶無法取消上傳;
下面就來試著將 GeoLite2 免費 IP 庫整合值SpringBoot項目,來獲取用戶的歸屬地信息;
1什么是GeoLite2?
GeoLite2數據庫是免費的IP地理定位數據庫;
優點:
- 離線庫,不需要網絡
- 數據庫豐富
- 速度快
- 免費
缺點:
- 準確度不高,存在偏差
- 數據更新慢
2下載 GeoLite2 離線庫
官網地址:https://www.maxmind.com/en/home
下載過程稍微有點點麻煩,這里下載了一份最新的,放在網盤,需要測試的可以直接通過這個鏈接下載:https://www.123pan.com/s/xPY9-J37vH
3SpringBoot 獲取用戶的IP
- 工具類public?class?IpUtils?{
/**
* 獲取用戶IP*?@param?request
*?@return
*/
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("X-Real-IP");
}if?(ip ==?null?|| ip.length() ==?0?||?"unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("http_client_ip");
}if?(ip ==?null?|| ip.length() ==?0?||?"unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();}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.getHeader("HTTP_X_FORWARDED_FOR");
}// 如果是多級代理,那么取第一個ip為客戶ip
if?(ip !=?null?&& ip.indexOf(",") != -1) {
ip = ip.substring(ip.lastIndexOf(",") +?1).trim();
}return?ip;
}} - Controller獲取HttpServletRequest通過上面的工具類,即可獲取用戶請求的真實IP;為了避免重復工作,這里也可以使用AOP解析出用戶的IP信息,放到用戶的請求對象中@RestController
public?class?IpController?{
@GetMapping("/user/ip")
public?String?userIp(HttpServletRequest request)?{
// 這里就能拿到用戶的真實IP
return?IpUtils.getIpAddr(request);
}}
4SpringBoot 整合 GeoLite2
- 添加依賴<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.maxmind.db</groupId>
<artifactId>maxmind-db</artifactId>
<version>1.0.0</version>
</dependency> - 工具類public?class?GeoIpUtils?{
private?static?DatabaseReader reader;
private?static?void?init()?{
try?{
// 創建 GeoLite2 數據庫 Reader
// 這里可以放在本地磁盤,也可以隨項目放在resource目錄下
File database =?new?File("F:\\web\\GeoLite2-City.mmdb");
// 讀取數據庫內容
reader =?new?DatabaseReader.Builder(database).build();
}?catch?(Exception ex) {
}}public?static?void?getCityByIP(String ip)?throws?Exception {
if?(null?== reader) {
init();}InetAddress ipAddress = InetAddress.getByName(ip);// 獲取查詢結果
CityResponse response = reader.city(ipAddress);// 獲取國家信息
Country country = response.getCountry();System.out.println("國家信息:"?+ JSON.toJSONString(country));
// 獲取省份
Subdivision subdivision = response.getMostSpecificSubdivision();System.out.println("省份信息:"?+ JSON.toJSONString(subdivision));
//城市
City city = response.getCity();System.out.println("城市信息:"?+ JSON.toJSONString(city));
// 獲取城市
Location location = response.getLocation();System.out.println("經緯度信息:"?+ JSON.toJSONString(location));
}} - 測試public?static?void?main(String[] args)?throws?Exception {
String ip =?"183.19.xxx.138";
GeoIpUtils.getCityByIP(ip);}輸出結果:國家信息:{"geoNameId":1814991,"isoCode":"CN","name":"China","names":{"de":"China","ru":"Китай","pt-BR":"China","ja":"中國","en":"China","fr":"Chine","zh-CN":"中國","es":"China"}}
省份信息:{"geoNameId":1809935,"isoCode":"GD","name":"Guangdong","names":{"en":"Guangdong","fr":"Province de Guangdong","zh-CN":"廣東"}}
城市信息:{"geoNameId":1998011,"name":"Yanqianlaocun","names":{"en":"Yanqianlaocun","zh-CN":"巖前老村"}}
經緯度信息:{"accuracyRadius":500,"latitude":23.3255,"longitude":116.5007,"timeZone":"Asia/Shanghai"}
就這么簡單,輕輕松松就能拿到用戶IP所處的國家、省份、城市、經緯度等詳細信息,可以根據自己的業務需要,對這些數據再做進一步的封裝。
5GeoLite2的其他用法
上面介紹的時SpringBoot整合GeoLite2,同樣在其他的一些場景下,也是可以利用GeoLite2獲取歸屬地信息;
- 整合至Nignx,獲取用戶歸屬地信息Nginx 整合 GeoLite2 來解析用戶的歸屬地信息,在代理層就直接整理好對應的數據;
- ELK中整合GeoLite2ELK 日志整理的時候,可以通過GeoLite2 獲取用戶的IP歸屬地信息;然后通過Kibana,就能非常直觀的展示用戶的地域分布情況;ELK搭建,這才是看日志的正確姿勢
6在線方案
上面一開始介紹GeoLite2時就列舉了其離線庫更新收錄不及時的問題,可能導致一些IP在離線庫中并不存在,查找的時候,就會報AddressNotFoundException的錯誤,如下示例:
遇到這種請求,我們要怎么辦呢?
下面就來介紹幾種在線IP歸屬地獲取的方式,當本地離線庫無法獲取的時候,就可以利用三方的在線庫,來補充完善;
在線獲取的優點:
- IP更新及時
- 準確度高
缺點
- 三方依賴性強
- 需要付費,免費版本一般都有各種限制
以下示例中的xxx.xxx.xxx.xxx均代表ip地址;
百度
地址:https://opendata.baidu.com/api.php?query=xxx.xxx.xxx.xxx&resource_id=6006&co=&oe=utf8
響應數據:
{"status": "0","t": "","set_cache_time": "","data": [{"ExtendedLocation": "","OriginQuery": "183.19.xxx.138","appinfo": "","disp_type": 0,"fetchkey": "183.19.xxx.138","location": "廣東省肇慶市 電信","origip": "183.19.xxx.138","origipquery": "183.19.xxx.138","resourceid": "6006","role_id": 0,"shareImage": 1,"showLikeShare": 1,"showlamp": "1","titlecont": "IP地址查詢","tplt": "ip"}] }status等于0表示成功,1表示失敗;可能存在status等于0,但是data中沒有數據的情況。
ip-api接口
- 本機的IP信息http://ip-api.com/json/
- 指定國際化http://ip-api.com/json/?lang=zh-CN
- 指定IP查詢http://ip-api.com/json/xxx.xxx.xxx.xxx?lang=zh-CN返回數據:{
"status":?"success",
"country":?"中國",
"countryCode":?"CN",
"region":?"GD",
"regionName":?"廣東",
"city":?"巖前老村",
"zip":?"",
"lat":?23.3255,
"lon":?116.5007,
"timezone":?"Asia/Shanghai",
"isp":?"Chinanet",
"org":?"Chinanet GD",
"as":?"AS4134 CHINANET-BACKBONE",
"query":?"183.19.xxx.138"
}
搜狐IP查詢
地址:http://pv.sohu.com/cityjson?ie=utf-8
返回數據比較的簡單:
var returnCitySN = {"cip": "xxx.xxx.xxx.xxx", "cid": "440300", "cname": "廣東省深圳市"};太平洋IP地址查詢
地址:http://whois.pconline.com.cn/ipJson.jsp?ip=xxx.xxx.xxx.xxx&json=true
返回數據:
{"ip": "183.17.xxx.138","pro": "廣東省","proCode": "440000","city": "深圳市","cityCode": "440300","region": "","regionCode": "0","addr": "廣東省深圳市 電信","regionNames": "","err": "" }淘寶API接口
http://ip.taobao.com/service/getIpInfo.php?ip=xxx.xxx.xxx.xxx
{"code": 0,"data": {"ip": "183.17.xxx.138","country": "中國","area": "","region": "廣東","city": "深圳","county": "XX","isp": "電信"} }code等于0表示成功,1表示失敗
126
地址:https://ip.ws.126.net/ipquery?ip=xxx.xxx.xxx.xxx
響應數據:
var lo="廣東省", lc="肇慶市"; var localAddress={city:"肇慶市", province:"廣東省"}響應的數據比較的簡單
IP信息
地址:https://ip.useragentinfo.com/json?ip=xxx.xxx.xxx.xxx
響應數據:
{"country": "中國","short_name": "CN","province": "廣東省","city": "肇慶市","area": "德慶縣","isp": "電信","net": "","ip": "183.19.xxx.138","code": 200,"desc": "success" }這么多的姿勢,實現起來是不是就非常的容易了;如果你對IP解析的需求比較依賴,也完全可以通過離線加這么多在線的方式,開發一個單獨的IP解析模塊,作為公司的基礎服務,提供給內部其他模塊使用。
示例目錄:https://github.com/vehang/ehang-spring-boot/tree/main/spring-boot-004-request-validate/src/main/java/com/ehang/validate/geoip
?
總結
以上是生活随笔為你收集整理的全网的 IP 归属地显示,带你5分钟加上,就是这么简单的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我成为程序员的稀里糊涂之路
- 下一篇: css3实现24小时时间刻度效果