Skip to content

整合 GeoLite 离线库 ​

整合 GeoLite 离线库:Java 通过 IP 获取地理位置信息

本文档介绍如何使用 GeoIP2 的离线库 GeoLite2-City,通过 Java 代码获取 IP 地址对应的地理信息。通过该示例,读者可以了解到如何获取城市、国家、经纬度以及邮政编码等数据,同时了解哪些信息需要额外的数据库支持。

1. GeoIP 简介 ​

GeoIP 是一种利用 IP 地址定位地理位置的技术。下面是一段示例信息,其中部分数据可以通过 GeoIP2 City 数据库获取,而部分(如组织、ASN 和 ISP 名称)则需要专门的 ISP/ASN 数据库支持:

City:           Los Angeles
Country:        United States
Country Code:   US
Latitude:       34.0544
Longitude:      -118.244
Postal Code:    90060
Organization:   Datacamp
ASN:            212238
ISP Name:       Datacamp

可以从 GeoIP2 City 数据库获取的信息包括:

  • City(城市): 如 Los Angeles
  • Country(国家)和 Country Code(国家代码): 如 United States / US
  • Latitude(纬度)和 Longitude(经度): 如 34.0544 和 -118.244
  • Postal Code(邮政编码): 如果数据库中包含此信息,也可以获取

需要额外数据库获取的信息:

  • Organization(组织)、ASN(自治系统编号)和 ISP Name(ISP 名称):
    这些信息通常由 GeoIP2 ISP 或 GeoIP2 ASN 数据库提供,而不是 GeoIP2 City 数据库。

总结:基于 GeoIP2 City 数据库,我们可以获取城市、国家、国家代码、经纬度和邮政编码等信息;而组织、ASN 与 ISP 信息则需要额外的数据库支持。

GeoLite2-City ​

1. 简介 ​

GeoLite2-City 数据库是 MaxMind 提供的离线库,可用于查询 IP 地址的地理信息。支持 IPv6

2. 离线库下载 ​

下载地址为:GeoLite2 Download
下载后解压,文件名为 GeoLite2-City.mmdb,文件大小约 58.2 MB。

3. Maven 依赖 ​

在项目中使用 GeoIP2 库时,需要在 Maven 配置文件中添加如下依赖:

xml
<dependency>
  <groupId>com.maxmind.geoip2</groupId>
  <artifactId>geoip2</artifactId>
  <version>2.8.1</version>
</dependency>

4. 编写工具类 ​

4.1 定义数据封装类 ​

首先,我们定义一个简单的 POJO 类 IpGeoInfo 用于封装从 GeoIP 查询到的经度、纬度和位置信息。这里使用了 Lombok 注解来简化 getter、setter 及构造方法的生成。

java
package nexus.io.chats.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class IpGeoInfo {
  private Double longitude;
  private Double latitude;
  private String location;
}

4.2 编写 IP 查询工具类 ​

接下来,我们创建 IpDatabaseUtils 工具类,封装了查询 IP 信息的逻辑。为了避免重复调用 reader.city(),我们提取了公共方法 getCityResponse。此外,分别提供了获取国家、省份、城市、经纬度的方法,以及整合后的 getGeoInfo 方法用于返回封装后的 IpGeoInfo 对象。

java
package nexus.io.chats.utils;

import java.io.IOException;
import java.net.InetAddress;
import java.util.Objects;

import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CityResponse;

public class IpDatabaseUtils {

  /**
   * 获取 CityResponse,避免多次调用 reader.city() 进行重复查询
   */
  private static CityResponse getCityResponse(DatabaseReader reader, String ip) throws IOException, GeoIp2Exception {
    InetAddress ipAddress = InetAddress.getByName(ip);
    return reader.city(ipAddress);
  }

  /**
   * 获取国家信息(中文名称)
   */
  public static String getCountry(DatabaseReader reader, String ip) throws Exception {
    CityResponse response = getCityResponse(reader, ip);
    return response.getCountry().getNames().get("zh-CN");
  }

  /**
   * 获取省份信息(中文名称)
   */
  public static String getProvince(DatabaseReader reader, String ip) throws Exception {
    CityResponse response = getCityResponse(reader, ip);
    return response.getMostSpecificSubdivision().getNames().get("zh-CN");
  }

  /**
   * 获取城市信息(中文名称)
   */
  public static String getCity(DatabaseReader reader, String ip) throws Exception {
    CityResponse response = getCityResponse(reader, ip);
    return response.getCity().getNames().get("zh-CN");
  }

  /**
   * 获取经度
   */
  public static Double getLongitude(DatabaseReader reader, String ip) throws Exception {
    CityResponse response = getCityResponse(reader, ip);
    return response.getLocation().getLongitude();
  }

  /**
   * 获取纬度
   */
  public static Double getLatitude(DatabaseReader reader, String ip) throws Exception {
    CityResponse response = getCityResponse(reader, ip);
    return response.getLocation().getLatitude();
  }

  /**
   * 根据 IP 返回位置信息,包括经度、纬度和城市+国家组合的位置信息
   */
  public static IpGeoInfo getGeoInfo(DatabaseReader reader, String ip) {
    CityResponse response;
    try {
      response = getCityResponse(reader, ip);
    } catch (IOException | GeoIp2Exception e) {
      throw new RuntimeException(e);
    }

    // 获取经度和纬度
    Double longitude = response.getLocation().getLongitude();
    Double latitude = response.getLocation().getLatitude();

    // 获取城市和国家信息(使用默认名称)
    String city = response.getCity().getName();
    String country = response.getCountry().getName();
    // 使用 String.join 拼接城市和国家,过滤首尾多余的逗号和空格
    String location = String.join(", ", Objects.toString(city, ""), Objects.toString(country, ""))
                          .replaceAll("(^,\\s*|,\\s*$)", "");

    return new IpGeoInfo(longitude, latitude, location);
  }
}

5. 测试代码 ​

下面是一个简单的测试用例,通过 JUnit 调用 IpDatabaseUtils.getGeoInfo 方法,打印出查询结果。测试代码中先创建了 DatabaseReader 实例,并读取本地的 GeoLite2-City.mmdb 文件。

java
package nexus.io.chats.utils;

import java.io.File;
import java.io.IOException;

import org.junit.Test;

import nexus.io.tio.utils.json.JsonUtils;
import com.maxmind.geoip2.DatabaseReader;

public class IpDatabaseUtilsTest {

  @Test
  public void test() {
    DatabaseReader reader = getDatabase();
    // 查询指定的 IP 地址
    String ip = "149.88.30.12";
    IpGeoInfo geoInfo = IpDatabaseUtils.getGeoInfo(reader, ip);
    System.out.println(JsonUtils.toJson(geoInfo));
  }

  private DatabaseReader getDatabase() {
    String path = "GeoLite2-City.mmdb";
    File database = new File(path);
    DatabaseReader reader;
    try {
      reader = new DatabaseReader.Builder(database).build();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    return reader;
  }
}

执行测试后,控制台会输出类似如下的 JSON 格式信息:

json
{
  "location": "Los Angeles, United States",
  "longitude": -118.2441,
  "latitude": 34.0544
}

6. 总结 ​

  • GeoIP2 City 数据库功能:
    使用 GeoIP2 City 数据库,可以获取城市、国家、国家代码、经纬度和邮政编码等信息。
  • 额外数据:
    组织、ASN 及 ISP 名称等信息需要使用专门的 GeoIP2 ISP 或 GeoIP2 ASN 数据库。

MIT Licensed