Springboot 使用GeoIP数据库查询用户IP位置信息

Springboot 使用GeoIP数据库查询用户IP位置信息

2024-6-28·devcxl
devcxl

引言

GeoIP 数据库是一个将 IP 地址映射到地理位置信息的工具。它在网络安全、内容本地化、广告投放和用户分析等领域发挥着重要作用。

虽然在线 IP 查询服务便捷,但存在网络依赖、隐私风险、查询限制和潜在高成本等问题。相比之下,使用 GeoIP 数据库进行离线查询具有显著优势:

  1. 高性能和稳定性
  2. 更好的隐私保护
  3. 无查询次数限制
  4. 长期使用更具成本效益
  5. 灵活性和可定制性更高

这种离线查询方式特别适合需要频繁、快速、私密 IP 地理信息查询的应用场景。

项目准备

  1. 首先在 MaxMaind 官网注册一个账号。
  2. 获取自己的账号和许可证密钥
  3. 手动下载对应的mmd数据库或使用命令下载
    curl -O -J -L -u ACCOUNT:LICENSE_KEY 'https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz'

实现步骤

  1. 为 Springboot 项目引入依赖

    <dependency>
        <groupId>com.maxmind.geoip2</groupId>
        <artifactId>geoip2</artifactId>
        <version>${latest.version}</version>
    </dependency>

    ${latest.version}替换为最新版本。

  2. 添加对应的配置类

    /** @author devcxl */
    @Getter
    @Setter
    @ConfigurationProperties(prefix = "geoip")
    public class GeoipProperties {
        /**
         * mmdb数据库资源路径
        */
        private Resource dbFile;
        /**
         * 地理信息展示语言
        */
        private String local = "zh-CN";
    }
  3. 创建 DatabaseReader 实例

        import com.example.demo.config.properties.GeoipProperties;
        import com.maxmind.db.CHMCache;
        import com.maxmind.geoip2.DatabaseReader;
        import java.io.IOException;
        import java.util.List;
        import org.springframework.boot.context.properties.EnableConfigurationProperties;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.core.io.Resource;
    
        /** @author devcxl */
        @Configuration
        @EnableConfigurationProperties({GeoipProperties.class})
        public class Geoip2Config {
    
            @Bean
            DatabaseReader databaseReader(GeoipProperties geoipProperties) throws IOException {
                Resource dbFile = geoipProperties.getDbFile();
                return new DatabaseReader.Builder(dbFile.getFile())
                        .withCache(new CHMCache())
                        .locales(List.of(geoipProperties.getLocal()))
                        .build();
            }
        }
  4. 配置 GeoIP 数据库路径

    geoip:
        db-file: 'file:/usr/share/geoip/GeoLite2-City.mmdb'
        local: 'zh-CN'
  5. 编写 IP 查询服务


/**
 * @author devcxl
 */
@Slf4j
@Service
public class GeoIpService {
  
    @Resource
    private DatabaseReader databaseReader;
  
    /**
     * 获取访问者IP的国家ISO代码
     *
     * @param ip
     * @return
     */
    public String getCountryIsoCode(String ip) {
        try {
            InetAddress ipAddress = InetAddress.getByName(ip);
            CountryResponse country = databaseReader.country(ipAddress);
            return country.getCountry().getIsoCode();
        } catch (IOException | GeoIp2Exception exception) {
            log.error("获取访问者IP的国家ISO代码 {} 错误:{}", ip, exception.getMessage());
            return "US";
        }
    }
  
    /**
     * 获取城市名称
     *
     * @param ip
     * @return
     */
    public String getCity(String ip) {
        try {
            InetAddress ipAddress = InetAddress.getByName(ip);
            CityResponse city = databaseReader.city(ipAddress);
            return city.getCity().getName();
        } catch (IOException | GeoIp2Exception exception) {
            log.error("获取访问者IP的城市 {} 错误: {}", ip, exception.getMessage());
            return null;
        }
    }
  
    /**
     * 获取国家名称
     *
     * @param ip
     * @return
     */
    public String getCountry(String ip) {
        try {
            InetAddress ipAddress = InetAddress.getByName(ip);
            CountryResponse country = databaseReader.country(ipAddress);
            return country.getCountry().getName();
        } catch (IOException | GeoIp2Exception exception) {
            log.error("获取访问者IP的国家 {} 错误:{}", ip, exception.getMessage());
            return null;
        }
    }
  
    /**
     * 获取省/州名称
     *
     * @param ip
     * @return
     */
    public String getSubdivision(String ip) {
        try {
            InetAddress ipAddress = InetAddress.getByName(ip);
            CityResponse city = databaseReader.city(ipAddress);
            return city.getLeastSpecificSubdivision().getName();
        } catch (IOException | GeoIp2Exception exception) {
            log.error("获取访问者IP的省/州 {} 错误:{}", ip, exception.getMessage());
            return null;
        }
    }

    /**
     * 获取用户的登录位置
     *
     * @param request
     * @return
     */
    public LoginLocation getLoginLocation(HttpServletRequest request) {
        String clientIp = ServletUtils.getClientIp(request);
        return getLoginLocationFromIp(clientIp);
    }

    /**
     * 获取用户的登录位置
     *
     * @param ip
     * @return
     */
    public LoginLocation getLoginLocationFromIp(String ip) {
        try {
            LoginLocation loginLocation = new LoginLocation();
            InetAddress ipAddress = InetAddress.getByName(ip);
            CityResponse city = databaseReader.city(ipAddress);
            loginLocation.setCity(city.getCity().getName());
            loginLocation.setCountry(city.getCountry().getName());
            loginLocation.setProvince(city.getLeastSpecificSubdivision().getName());
            return loginLocation;
        } catch (IOException | GeoIp2Exception exception) {
            log.error("获取访问者的登录位置 ip:{} 错误:{}", ip, exception.getMessage());
            return null;
        }

    }

}

相关文档