Springboot 根据Ip地址获取归属地 国家省市城市

1,963 阅读4分钟

记录一下

java 根据Ip获取归属地分两步走

第一步:获取Ip 网上大把工具类 随便找一些就行 为了大家方便我贴一个若依的在下面

@Slf4j public class IpUtils {

/**
 * 获取IP地址
 * <p>
 * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
 * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
 */
/**
 * 获取客户端IP
 *
 * @param request 请求对象
 * @return IP地址
 */
 
public static String getIpAddr(HttpServletRequest request)
{
    if (request == null)
    {
        return "unknown";
    }
    String ip = request.getHeader("x-forwarded-for");
    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    {
        ip = request.getHeader("Proxy-Client-IP");
    }
    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    {
        ip = request.getHeader("X-Forwarded-For");
    }
    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    {
        ip = request.getHeader("WL-Proxy-Client-IP");
    }
    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    {
        ip = request.getHeader("X-Real-IP");
    }

    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
    {
        ip = request.getRemoteAddr();
    }

    return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
}

/**
 * 检查是否为内部IP地址
 *
 * @param ip IP地址
 * @return 结果
 */
public static boolean internalIp(String ip)
{
    byte[] addr = textToNumericFormatV4(ip);
    return internalIp(addr) || "127.0.0.1".equals(ip);
}

/**
 * 检查是否为内部IP地址
 *
 * @param addr byte地址
 * @return 结果
 */
private static boolean internalIp(byte[] addr)
{
    if (Objects.isNull(addr) || addr.length < 2)
    {
        return true;
    }
    final byte b0 = addr[0];
    final byte b1 = addr[1];
    // 10.x.x.x/8
    final byte SECTION_1 = 0x0A;
    // 172.16.x.x/12
    final byte SECTION_2 = (byte) 0xAC;
    final byte SECTION_3 = (byte) 0x10;
    final byte SECTION_4 = (byte) 0x1F;
    // 192.168.x.x/16
    final byte SECTION_5 = (byte) 0xC0;
    final byte SECTION_6 = (byte) 0xA8;
    switch (b0)
    {
        case SECTION_1:
            return true;
        case SECTION_2:
            if (b1 >= SECTION_3 && b1 <= SECTION_4)
            {
                return true;
            }
        case SECTION_5:
            switch (b1)
            {
                case SECTION_6:
                    return true;
            }
        default:
            return false;
    }
}

/**
 * 将IPv4地址转换成字节
 *
 * @param text IPv4地址
 * @return byte 字节
 */
public static byte[] textToNumericFormatV4(String text)
{
    if (text.length() == 0)
    {
        return null;
    }

    byte[] bytes = new byte[4];
    String[] elements = text.split("\.", -1);
    try
    {
        long l;
        int i;
        switch (elements.length)
        {
            case 1:
                l = Long.parseLong(elements[0]);
                if ((l < 0L) || (l > 4294967295L))
                {
                    return null;
                }
                bytes[0] = (byte) (int) (l >> 24 & 0xFF);
                bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
                bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
                bytes[3] = (byte) (int) (l & 0xFF);
                break;
            case 2:
                l = Integer.parseInt(elements[0]);
                if ((l < 0L) || (l > 255L))
                {
                    return null;
                }
                bytes[0] = (byte) (int) (l & 0xFF);
                l = Integer.parseInt(elements[1]);
                if ((l < 0L) || (l > 16777215L))
                {
                    return null;
                }
                bytes[1] = (byte) (int) (l >> 16 & 0xFF);
                bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
                bytes[3] = (byte) (int) (l & 0xFF);
                break;
            case 3:
                for (i = 0; i < 2; ++i)
                {
                    l = Integer.parseInt(elements[i]);
                    if ((l < 0L) || (l > 255L))
                    {
                        return null;
                    }
                    bytes[i] = (byte) (int) (l & 0xFF);
                }
                l = Integer.parseInt(elements[2]);
                if ((l < 0L) || (l > 65535L))
                {
                    return null;
                }
                bytes[2] = (byte) (int) (l >> 8 & 0xFF);
                bytes[3] = (byte) (int) (l & 0xFF);
                break;
            case 4:
                for (i = 0; i < 4; ++i)
                {
                    l = Integer.parseInt(elements[i]);
                    if ((l < 0L) || (l > 255L))
                    {
                        return null;
                    }
                    bytes[i] = (byte) (int) (l & 0xFF);
                }
                break;
            default:
                return null;
        }
    }
    catch (NumberFormatException e)
    {
        return null;
    }
    return bytes;
}

/**
 * 获取IP地址
 *
 * @return 本地IP地址
 */
public static String getHostIp()
{
    try
    {
        return InetAddress.getLocalHost().getHostAddress();
    }
    catch (UnknownHostException e)
    {
    }
    return "127.0.0.1";
}

/**
 * 获取主机名
 *
 * @return 本地主机名
 */
public static String getHostName()
{
    try
    {
        return InetAddress.getLocalHost().getHostName();
    }
    catch (UnknownHostException e)
    {
    }
    return "未知";
}

/**
 * 从多级反向代理中获得第一个非unknown IP地址
 *
 * @param ip 获得的IP地址
 * @return 第一个非unknown IP地址
 */
public static String getMultistageReverseProxyIp(String ip)
{
    // 多级反向代理检测
    if (ip != null && ip.indexOf(",") > 0)
    {
        final String[] ips = ip.trim().split(",");
        for (String subIp : ips)
        {
            if (false == isUnknown(subIp))
            {
                ip = subIp;
                break;
            }
        }
    }
    return ip;
}

/**
 * 检测给定字符串是否为未知,多用于检测HTTP请求相关
 *
 * @param checkString 被检测的字符串
 * @return 是否未知
 */
public static boolean isUnknown(String checkString)
{
    return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
}

其实以上这个ip工具类 主要就是 getIpAddr() 这个函数就够了 下面的没所谓的

第二步: 利用别人写好的 Ip2region 当一个掉包侠

GitHub:github.com/lionsoul201…

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

具体内置三种算法

  • memory 算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内,C语言的客户端单次查询在0.00x毫秒级别。

  • binary 算法:基于二分查找,基于ip2region.db文件,不需要载入内存,单次查询在0.x毫秒级别。

  • b-tree 算法:基于btree算法,基于ip2region.db文件,不需要载入内存,单词查询在0.x毫秒级别,比binary算法更快。

都是大学数据结构的东西 我也没研究源码 大概知道有这些算法

使用:

(1) 引入依赖:

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

(2)下载仓库中的ip2region.db 文件,放到工程resources目录下

2022/10/12 现在这个文件在仓库这个位置

image.png

放在这里

image.png

(3)写函数加载ip2region文件,对用户ip地址进行转换,都是一些流和大学数据结构内容基础啥的,不算得上太难的难度。

/**
* 获取ip属地
* @param ip
* @return
* @throws Exception
*/
public static String getCityInfo(String ip) throws Exception {
   //获得文件流时,因为读取的文件是在打好jar文件里面,不能直接通过文件资源路径拿到文件,但是可以在jar包中拿到文件流
   ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
   Resource[] resources = resolver.getResources("ip2region.db");
   Resource resource = resources[0];
   InputStream is = resource.getInputStream();
   File target = new File("ip2region.db");
   FileUtils.copyInputStreamToFile(is, target);
   is.close();
   if (StringUtils.isEmpty(String.valueOf(target))) {
       log.error("Error: Invalid ip2region.db file");
       return null;
   }
   DbConfig config = new DbConfig();
   DbSearcher searcher = new DbSearcher(config, String.valueOf(target));
   //查询算法
   //B-tree, B树搜索(更快)
   int algorithm = DbSearcher.BTREE_ALGORITHM;
   try {
       //define the method
       Method method;
       method = searcher.getClass().getMethod("btreeSearch", String.class);
       DataBlock dataBlock;
       if (!Util.isIpAddress(ip)) {
           log.error("Error: Invalid ip address");
       }
       dataBlock = (DataBlock) method.invoke(searcher, ip);
       String ipInfo = dataBlock.getRegion();
       if (!StringUtils.isEmpty(ipInfo)) {
           ipInfo = ipInfo.replace("|0", "");
           ipInfo = ipInfo.replace("0|", "");
       }
       return ipInfo;
   } catch (Exception e) {
       e.printStackTrace();
   }
   return null;
}

(4)一般按照各大互联网APP的规则就是 ip 属地在国内的话,只会展示省份,而国外的话,只会展示国家。所以我们还需要对这个方法进行一下封装,得到获取 IP 属地的信息。 我这个是全都要的、你也可以看着改,

//判断ip地址
public static String getIpPossession(String ip) throws Exception {
    String cityInfo = IpUtils.getCityInfo(ip);
    if (!StringUtils.isEmpty(cityInfo)) {
        cityInfo = cityInfo.replace("|", " ");
        String[] cityList = cityInfo.split(" ");
        if (cityList.length > 0) {
            // 国内的显示到具体的省
            if ("中国".equals(cityList[0])) {
                if (cityList.length > 3) {
                    StringBuilder stringBuilder = new StringBuilder();
                    stringBuilder.append(cityList[0]).append(cityList[1]).append(cityList[2]).append(cityList[3]);
                    return stringBuilder.toString();
                } else {
                    StringBuilder stringBuilder = new StringBuilder();
                    stringBuilder.append(cityList[0]).append(cityList[1]).append(cityList[2]);
                    return stringBuilder.toString();
                }
                // 国外也显示到省
            } else {
                if (cityList.length > 2) {
                    StringBuilder stringBuilder = new StringBuilder();
                    stringBuilder.append(cityList[0]).append(cityList[1]).append(cityList[2]);
                    return stringBuilder.toString();
                } else {
                    StringBuilder stringBuilder = new StringBuilder();
                    stringBuilder.append(cityList[0]).append(cityList[1]);
                    return stringBuilder.toString();
                }
            }
        }
    }
    return "未知Ip归属地";
}

大概就是改这个cityList 你不想要那么详细就不append那么多就行了。。。

感谢 juejin.cn/post/713723… 原作者分享