Java实现ip属地功能开发教程 | ip2region2.x使用总结

ip属地功能开发-ip2region2.x使用总结

一、前言

如今许多软件如B站、微博、抖音等都加上 IP 归属地防止恶意评论,境外用户显示的是国家,国内的用户显示的省份。兴致一起,我便想自己的博客项目加上此功能。

目前存在几种方式:

  • 方式一:IP地址库ip2region(本文采取的方式)

  • 方式二:使用第三方常见API(查询效率低,不稳定,容易失效)

    1. 淘宝--IP库地址:https://ip.taobao.com/已经停止服务
    2. 太平洋IP地址库http://whois.pconline.com.cn/

二、ip2region简介

ip2region - 准确率99.9%的离线IP地址定位库,0.0x毫秒级查询,ip2region.db数据库只有数MB,提供了java、php、c、python、nodejs、golang、c#等查询绑定和Binary,B树,内存三种查询算法。

  • 地址:

    GitHub地址:https://github.com/lionsoul2014/ip2region

    Gitee地址:https://gitee.com/lionsoul/ip2region

  • 特性/优势

    1. 标准化的数据格式——每个 ip 数据段的 region 信息都固定了格式:国家|区域|省份|城市|ISP
    2. 数据去重和压缩
    3. 极速查询响应——即使是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别
    4. IP 数据管理框架

三、ip2region的使用

1、下载ip2region.db

将项目从GitHub/Gitee仓库down下来,找到data目录,有三种不同文件格式的ip地址库,进入找到ip2region.xdb,将ip2region.xdb复制出来放到resources下。

2、pom.xml中引入依赖

<dependency>
     <groupId>org.lionsoul</groupId>
     <artifactId>ip2region</artifactId>
     <version>2.6.5</version>
 </dependency>

3、IpUtils工具类

其中有三个方法,

其一是去获取请求中的真实客户端ip。

其二是通过ip获取城市信息。

其三在服务启动时加载 ip2region.db 这个数据资源到内存中。

package com.lll.blog.utils;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class IPUtils {

    private static Logger logger = LoggerFactory.getLogger(IPUtils.class);

    private static Searcher searcher;

    /**
     * 获取客户端IP地址
     *
     * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = null;
        try {
            ip = request.getHeader("x-forwarded-for");
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
                if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
                    //根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        log.error("getIpAddress exception:", e);
                    }
                    ip = inet.getHostAddress();
                }
            }
        } catch (Exception e) {
            logger.error("IPUtils ERROR ", e);
        }
        return ip;
    }

    /**
     * 根据ip从 ip2region.db 中获取地理位置
     *
     * @param ip
     * @return 地理位置
     */
    public static Map getCityInfo(String ip) {
        //数据格式: 国家|区域|省份|城市|ISP
        //192.168.31.160 0|0|0|内网IP|内网IP
        //47.52.236.180 中国|0|香港|0|阿里云
        //220.248.12.158 中国|0|上海|上海市|联通
        //164.114.53.60 美国|0|华盛顿|0|0
        HashMap<String, Object> cityInfo = new HashMap<>();
        try {
            String searchIpInfo = searcher.search(ip);
            String[] splitIpInfo = searchIpInfo.split("\\|");
            cityInfo.put("ip",ip);
            cityInfo.put("searchInfo", searchIpInfo);
            cityInfo.put("country",splitIpInfo[0]);
            cityInfo.put("region",splitIpInfo[1]);
            cityInfo.put("province",splitIpInfo[2]);
            cityInfo.put("city",splitIpInfo[3]);
            cityInfo.put("ISP",splitIpInfo[3]);
            return cityInfo;
        } catch (Exception e) {
            log.info("failed to search(%s): %s\n", ip, e);
        }
        return null;
    }

    /**
     * 在服务启动时加载 ip2region.db 到内存中
     * 解决打包jar后找不到 ip2region.db 的问题
     *
     * @throws Exception 出现异常应该直接抛出终止程序启动,避免后续invoke时出现更多错误
     */
    @PostConstruct
    private static void initIp2regionResource() {
        try {
            InputStream inputStream = new ClassPathResource("/ipdb/ip2region.xdb").getInputStream();
            byte[] dbBinStr = FileCopyUtils.copyToByteArray(inputStream);
            // 创建一个完全基于内存的查询对象
            searcher = Searcher.newWithBuffer(dbBinStr);
        } catch (Exception e) {
            log.info("failed to create content cached searcher: %s\n", e);
        }
    }

}

4、测试类

package com.lll.blog.controller;

import com.lll.blog.utils.IPUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;


/**
 * @author Rain
 * @date 2022/10/28 15:57
 */
@Controller
public class IpController {

    @GetMapping(value = "/testIp")
    @ResponseBody
    public Object testIp(HttpServletRequest request) {

        String ipAddr = IPUtils.getIpAddr(request);
        Map cityInfo = IPUtils.getCityInfo(ipAddr);
        System.out.println(cityInfo.get("province"));
        //数据格式: 国家|区域|省份|城市|ISP
        //192.168.31.160 0|0|0|内网IP|内网IP
        //47.52.236.180 中国|0|香港|0|阿里云
        //220.248.12.158 中国|0|上海|上海市|联通
        //164.114.53.60 美国|0|华盛顿|0|0
        String ip = "183.213.81.138";
        Map result = IPUtils.getCityInfo(ip);
        System.out.println(result.get("province"));
        return cityInfo;
    }
}

四、需求实现-博客项目归属地

项目技术:Springboot + Vue

1、后端

Controller

package com.lll.blog.controller;

import com.lll.blog.service.SysUserService;
import com.lll.blog.utils.IPUtils;
import com.lll.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@RestController
@RequestMapping("users")
public class UsersController {

    @Autowired
    private SysUserService SysUserService;
    
    @GetMapping("getIpRegion")
    public Result getIpRegion(HttpServletRequest request){
        String ipAddr = IPUtils.getIpAddr(request);
        return SysUserService.getIpRegion(ipAddr);
    }
}

Service

public interface SysUserService {

    Result getIpRegion(String ip);
}

Impl

package com.lll.blog.service.impl;

import com.lll.blog.utils.IPUtils;
import com.lll.blog.vo.ErrorCode;
import com.lll.blog.vo.Result;

import java.util.Map;


@Service
public class SysUserServiceImpl implements SysUserService {

    @Override
    public Result getIpRegion(String ip) {
        Map cityInfo = IPUtils.getCityInfo(ip);
        if ("中国".equals(cityInfo.get("country"))){
            System.out.println(cityInfo.get("province"));
            Object province = cityInfo.get("province");
            return Result.success(province);
        }
        else if ("0".equals(cityInfo.get("country"))){
            System.out.println(cityInfo);
            if ("内网IP".equals(cityInfo.get("ISP"))){
                return Result.success("内网");
            }
            return Result.success("未知");
        }
        else {
            System.out.println(cityInfo.get("country"));
            return Result.success(cityInfo.get("country"));
        }
    }

}

2、前端

(下面仅仅展示部分关键代码)

api

export function getIpRegionInfo() {
  return request({
    url: '/users/getIpRegion',
    method: 'get'
  })

vue页面

<template>
  <el-card>
    <div class="me-author-description" v-if="!user.login">
      <span><i class="el-icon-location-outline"></i> &nbsp;IP属地:{{ip}}</span>
    </div>
  </el-card>
</template>


<script>
  import {getIpRegionInfo} from '@/api/login'

  export default {
    name: 'CardMe',
    created() {
      this.getIpRegionInfo()
    },
    data() {
      return {
        ip: null,
      }
    },
    methods: {
      getIpRegionInfo() {
        getIpRegionInfo().then((data => {
          this.ip = data.data
        })).catch(error => {
          if (error !== 'error') {
            that.$message({type: 'error', message: 'ip归属地获取失败!', showClose: true})
          }
        })
      }
    }
  }
</script>

五、扩展

1、好文

2、待尝试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeRain程序雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值