大家好,我是一航!
最近,继新浪微博之后,今日头条、腾讯、抖音、知乎、快手、小红书、百家号等各大平台陆陆续续都上线了"网络用户IP地址显示功能",境外用户显示的是国家,国内的用户显示的省份,而且此项显示无法关闭,归属地强制显示;
作为技术人,那!这个功能要怎么实现呢?
其实要想实现这个功能还是非常的容易,基于现成 GeoLite2离线库+免费的在线解析资源,5分钟就能整合了;
在整合之前,我们先简单了解一下,要想拿到用户的位置信息,有那些方式:
-
终端定位 我们的手机等电子设备都是带有GPS定位功能的,APP可以申请权限获取用户所处的经纬度坐标,根据坐标,就可以知道到用户所处的位置;比如百度、高德等地图厂商,就提供了完善的SDK,能非常方便的集成到应用,快速根据经纬度获取详细的位置详细; 优点
缺点
- 依赖硬件支持;
- 依赖用户授权,如果用户不授权,APP将拿不到经纬度信息,导致失败;
-
IP地址获取 用户向服务端发起的请求都会带上IP地址,服务端拿到IP地址后,就能基于IP解析出用户的所处的位置; 优点
- 无需授权,只要用户跟服务端交互,服务端就能拿到对应的IP信息
缺点
-
准确性不高,位置可能存在偏差; -
IP库更新不及时,导致部分IP归属地解析失败。 -
三方终端上报 比如,我们骑共享单车的时候,我们的位置信息就是通过单车的设备上报到服务器; 优点
- 由三方终端基于GPS定位上报,不会获取个人设备的信息;
- 准确快捷;
- 专业设备,误差小;
缺点
- 用户无法干预,信息会被迫强制上传至服务端,用户无法取消上传;
下面就来试着将 GeoLite2 免费 IP 库整合值SpringBoot项目,来获取用户的归属地信息;
什么是GeoLite2?
GeoLite2数据库是免费的IP地理定位数据库;
优点:
缺点:
下载 GeoLite2 离线库
官网地址:https://www.maxmind.com/en/home
下载过程稍微有点点麻烦,这里下载了一份最新的,放在网盘,需要测试的可以直接通过这个链接下载:https://www.123pan.com/s/xPY9-J37vH
SpringBoot 获取用户的IP
-
工具类 public class IpUtils {
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");
}
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) {
return IpUtils.getIpAddr(request);
}
}
SpringBoot 整合 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 {
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所处的国家、省份、城市、经纬度等详细信息,可以根据自己的业务需要,对这些数据再做进一步的封装。
GeoLite2的其他用法
上面介绍的时SpringBoot整合GeoLite2,同样在其他的一些场景下,也是可以利用GeoLite2获取归属地信息;
在线方案
上面一开始介绍GeoLite2时就列举了其离线库更新收录不及时的问题,可能导致一些IP在离线库中并不存在,查找的时候,就会报AddressNotFoundException 的错误,如下示例:
遇到这种请求,我们要怎么办呢?
下面就来介绍几种在线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解析模块,作为公司的基础服务,提供给内部其他模块使用。
好了,今天的分享就到这里,感谢你的点赞、关注、收藏!
|