本文主要讲解IP查询属地信息的实现方法,以及基于Spring Boot实现IP属地信息查询项目。
〇、前言
近日,多个网络公众平台纷纷公开显示用户的IP属地
,并且用户无法开启或关闭此功能。
用户的IP信息,平台是怎么知道的?
其大致流程是这样的:
用户
的手机、电脑等设备必须通过网络运营商(电信、移动等)进行联网服务,这个连接网络的过程中,基站、路由器将为该设备动态分配一个IP
;用户
发布一篇文章或微博,或者进行评论时,就会向平台服务器
发送一个HTTP请求
。而IP
信息,就包含在这个HTTP请求头
里;平台服务器
接收到请求信息,就可以通过HTTP请求(Request请求)
信息解析出该用户的IP
。
在这个过程中,请求信息
只包含IP
,并不包含用户的属地信息
。
另外,对于交互式平台
来说,《互联网交互式服务安全管理要求》(GA 1277.1-2020)的第1部分 基本要求
的第8.3条 日志与用户数据记录
明确,平台必须记录用户的活动、信息发布等操作的IP地址及源端口
。详见:http://www.beian.gov.cn/portal/downloadcenter 。
平台如何通过IP查询到属地信息?
一种方案是请求第三方IP属地信息服务
的API,通过该接口查询用户的IP属地信息
。但这种方案通常是按量付费
的,大的平台请求量巨大,不划算。
另外一种方案是使用离线的IP地址数据库
,该数据库通常是一个文件,甚至可能是cvs
文件、txt
文件。
该数据库一般是由专业人士通过专业技术人工生成,其中记录了大量的IP地址(段)
,并记录了该IP地址(段)
所属的国家
、省份
、城市
、运营商名称
、邮政编码
、经纬度
等信息。
接下来,将基于Java
和MaxMind
的IP地址数据库
,讲解如何实现通过IP获取属地信息
。
一、下载IP地址库
提供离线IP地址数据库
的平台有很多,比如:
- http://www.ipip.net
- https://user.ip138.com/ip/lib/
这些平台通常是付费
的,价格不菲。不过也有平台提供免费
版,比如:
- https://www.maxmind.com/en/geoip2-services-and-databases
MaxMind
平台提供的IP地址数据库
有免费
和付费
两种版本。
虽然免费版更新比较慢,但对于精确度不高的项目来说,够用。
MaxMind
的免费版IP地址数据库
叫做GeoLite2
,其下载方式如下。
1、注册并登录账号
访问如下地址,注册平台账号;
- https://www.maxmind.com/en/geolite2/signup
访问如下地址,登录账号;
- https://www.maxmind.com/en/account/login
登录成功后进入如下页面:
2、下载IP地址数据库
访问左侧的Download Files
按钮,进入IP地址数据库
下载页面,如下图所示:
我们选择精确到City
的免费版mmdb
格式的IP地址数据库
文件,并下载。
下载完成后是一个压缩包,打开即可看到mmdb
格式的数据库文件。
mmdb
即MaxMind Database
,是MaxMind
设计的一种用于存储IP地址数据信息的数据库,其采用二分查找树来加速IP信息的查询。
二、IP地址数据库的简单使用
根据MaxMind
的开发文档,该数据库的使用方法很简单,一共分两步:
1、导入依赖包
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.16.1</version>
</dependency>
2、配置并查询
File database = new File("/path/to/maxmind-database.mmdb")
// This reader object should be reused across lookups as creation of it is expensive.
// 译:这个reader对象应该在整个查找中重用,因为创建它的成本很高。
DatabaseReader reader = new DatabaseReader.Builder(database).build();
// If you want to use caching at the cost of a small (~2MB) memory overhead:
// 译:如果要以较小的内存开销(约2MB)为代价使用缓存,请执行以下操作:
// new DatabaseReader.Builder(file).withCache(new CHMCache()).build();
InetAddress ipAddress = InetAddress.getByName("128.101.101.101");
CityResponse response = reader.city(ipAddress);
Country country = response.getCountry();
System.out.println(country.getIsoCode());
详情:https://dev.maxmind.com/geoip/geolocate-an-ip/databases
三、Spring Boot中使用IP地址库
以下代码只展示关键部分。完整代码详见后文。
1、创建Spring Boot项目
创建完成的Spring Boot项目目录结构,如下图所示。
其中,将下载的IP地址数据库
文件,放在resources文件夹
中的mmdb目录
下。
2、导入依赖
在pom.xml
文件中,导入如下依赖。
<!-- GeoIp2的依赖 -->
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.16.1</version>
</dependency>
3、创建IP属地信息实体
在entity包
下,新建一个名为Ip2CityEntity
的实体,用来保存查询后的IP属地信息
。
代码如下,逻辑见注释。
package com.zxdmy.tool.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* IP属地信息实体类
*
* @author 拾年之璐
* @since 2022/5/1 13:24
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class Ip2CityEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* IP地址
*/
private String ip;
/**
* 国家名称
*/
private String country;
/**
* 省份名称
*/
private String province;
/**
* 城市名称
*/
private String city;
/**
* 经度
*/
private Double longitude;
/**
* 维度
*/
private Double latitude;
/**
* 查询耗时
*/
private String cost;
}
4、创建并实现查询工具类
在utils包
中,新建名为GeoIPUtils
的工具类,实现通过IP查询属地信息功能
方法。
详细代码如下所示,具体逻辑见注释。
package com.zxdmy.tool.utils;
import com.maxmind.db.CHMCache;
import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.model.CityResponse;
import com.maxmind.geoip2.record.*;
import com.zxdmy.tool.entity.Ip2CityEntity;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
/**
* GeoIP数据库操作工具类
*
* @author 拾年之璐
* @since 2022/5/1 12:53
*/
public class GeoIPUtils {
/**
* 数据库文件所在的路径(resources文件内)
*/
private static final String mmdbPath = "mmdb/GeoLite2-City.mmdb";
/**
* 设置返回的语言:简体中文
*/
private static final String CHS = "zh-CN";
/**
* 读取resources文件中的静态数据库
*/
private static final InputStream mmdbStream = GeoIPUtils.class.getClassLoader().getResourceAsStream(mmdbPath);
/**
* 数据库加载器,全局静态,只加载一次
*/
private static DatabaseReader databaseReader;
// 静态代码块,初始化时执行一次
static {
try {
databaseReader = new DatabaseReader.Builder(mmdbStream).withCache(new CHMCache()).build();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 通过IP查询地址信息
*
* @param ip IP地址
* @return 城市地址信息
*/
public static Ip2CityEntity getIpInfo(String ip) {
Ip2CityEntity ip2CityEntity = new Ip2CityEntity();
try {
// 获取主机的IP(IP可以是域名)
InetAddress inetAddress = InetAddress.getByName(ip);
// 通过数据库查询该IP的信息
CityResponse response = databaseReader.city(inetAddress);
// 解析国家
String countryName = response.getCountry().getNames().get(CHS);
// 解析二级分支(一般是省份)
String provinceName = response.getMostSpecificSubdivision().getNames().get(CHS);
// 解析城市
String cityName = response.getCity().getNames().get(CHS);
// 解析坐标
Location location = response.getLocation();
// 写入实体
ip2CityEntity.setIp(ip)
.setCountry(countryName)
.setProvince(provinceName)
.setCity(cityName)
// 经度
.setLatitude(location.getLatitude())
// 维度
.setLongitude(location.getLongitude());
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
return ip2CityEntity;
}
}
5、调用工具类实现具体业务
接下来就可以在控制类
、服务类
或切面
等场景中,根据实际业务需求,调用GeoIPUtils工具类
中的通过IP查询属地信息
静态方法,获取某个IP
的属地信息。
下面是一种使用实例。
package com.zxdmy.tool.controller;
import com.zxdmy.tool.entity.Ip2CityEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import static com.zxdmy.tool.utils.GeoIPUtils.getIpInfo;
/**
* 请求查询的控制器
*
* @author 拾年之璐
* @since 2022/5/1 17:19
*/
@Controller
public class IPController {
/**
* 请求的入口
*
* @param ip IP地址
* @return 查询结果
*/
@GetMapping({"query"})
@ResponseBody
public Ip2CityEntity index(@RequestParam(name = "ip", required = false) String ip) {
// 请求的开始时间
long start = System.currentTimeMillis();
// 查询结果
Ip2CityEntity ip2CityEntity = getIpInfo(ip);
// 判断查询结果
if (null != ip2CityEntity) {
// 写入查询耗时
ip2CityEntity.setCost(System.currentTimeMillis() - start + " ms");
}
// 返回
return ip2CityEntity;
}
}
四、结果演示与源码下载
在作者的演示项目
中,控制类
的实现方法如下图所示,查询返回的是JSON数据
。
4.1 线上演示
注:
线上演示
项目不提供长久服务,随时下线。若无法访问,请使用本地演示
。
直接访问演示项目
的主页将查询访问者的IP属地信息
。
主页连接(点击可访问):
- http://ip.tool.zxdmy.com
访问结果如下图所示。
如果通过Get方式
传入IP参数
,则将查询该IP的属地信息
。
访问格式如下:
- http://ip.tool.zxdmy.com/?ip=114.44.227.87
访问结果如下图所示。
4.2 本地演示
本地演示前,请先保证本机有
JDK 1.8
环境。
访问如下链接,即可下载演示项目的jar包
。
https://download.csdn.net/download/cxh_1231/85259992
使用如下命令运行jar包
即可启动项目
java -jar Ip2CityDemo-1.1.jar
详情如下图所示。
关闭该CMD命令行窗口
,即结束项目。
4.3 源码下载
请访问如下链接下载。
https://download.csdn.net/download/cxh_1231/85259992
— End —