Java实现ip属地功能开发教程 | ip2region2.x使用总结
ip属地功能开发-ip2region2.x使用总结
一、前言
如今许多软件如B站、微博、抖音等都加上 IP 归属地防止恶意评论,境外用户显示的是国家,国内的用户显示的省份。兴致一起,我便想自己的博客项目加上此功能。
目前存在几种方式:
-
方式一:IP地址库ip2region(本文采取的方式)
-
方式二:使用第三方常见API(查询效率低,不稳定,容易失效)
淘宝--IP库地址:https://ip.taobao.com/
已经停止服务- 太平洋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树,内存三种查询算法。
-
地址:
-
特性/优势
- 标准化的数据格式——每个 ip 数据段的 region 信息都固定了格式:国家|区域|省份|城市|ISP
- 数据去重和压缩
- 极速查询响应——即使是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别
- 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> 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、好文
-
ip2region2.x
-
1.x
2、待尝试
-
@IP注解
后续有时间尝试一下 @IP注解切面开发参考
之后在接口方法上加上 @IP 注解就可以实现监控Controller接口的访问地址
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人