admin管理员组

文章数量:1531709

本文主要讲解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/portal/downloadcenter 。

平台如何通过IP查询到属地信息?

一种方案是请求第三方IP属地信息服务的API,通过该接口查询用户的IP属地信息。但这种方案通常是按量付费的,大的平台请求量巨大,不划算。

另外一种方案是使用离线的IP地址数据库,该数据库通常是一个文件,甚至可能是cvs文件、txt文件。

该数据库一般是由专业人士通过专业技术人工生成,其中记录了大量的IP地址(段),并记录了该IP地址(段)所属的国家省份城市运营商名称邮政编码经纬度等信息。

接下来,将基于JavaMaxMindIP地址数据库,讲解如何实现通过IP获取属地信息

一、下载IP地址库

提供离线IP地址数据库的平台有很多,比如:

  • http://www.ipip
  • https://user.ip138/ip/lib/

这些平台通常是付费的,价格不菲。不过也有平台提供免费版,比如:

  • https://www.maxmind/en/geoip2-services-and-databases

MaxMind平台提供的IP地址数据库免费付费两种版本。

虽然免费版更新比较慢,但对于精确度不高的项目来说,够用。

MaxMind免费版IP地址数据库叫做GeoLite2,其下载方式如下。

1、注册并登录账号

访问如下地址,注册平台账号;

  • https://www.maxmind/en/geolite2/signup

访问如下地址,登录账号;

  • https://www.maxmind/en/account/login

登录成功后进入如下页面:

2、下载IP地址数据库

访问左侧的Download Files按钮,进入IP地址数据库下载页面,如下图所示:

我们选择精确到City的免费版mmdb格式的IP地址数据库文件,并下载。

下载完成后是一个压缩包,打开即可看到mmdb格式的数据库文件。

mmdbMaxMind 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/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

访问结果如下图所示。

如果通过Get方式传入IP参数,则将查询该IP的属地信息

访问格式如下:

  • http://ip.tool.zxdmy/?ip=114.44.227.87

访问结果如下图所示。

4.2 本地演示

本地演示前,请先保证本机有JDK 1.8环境。

访问如下链接,即可下载演示项目的jar包

https://download.csdn/download/cxh_1231/85259992

使用如下命令运行jar包即可启动项目

java -jar Ip2CityDemo-1.1.jar

详情如下图所示。

关闭该CMD命令行窗口,即结束项目。

4.3 源码下载

请访问如下链接下载。

https://download.csdn/download/cxh_1231/85259992

— End —

本文标签: 属地信息手把手教你ip