Spring Boot快速实现 IP地址解析的示例详解

目录
  • 前言
  • 引入:
  • 开发:
  • 在线解析:
  • 场景:

大家好! 我是慕歌,一只想教你学习 Spring Boot的野生coder! 欢迎来到慕歌的 Sping boot系列教程,希望通过这个教程带大家搭建基础的 Spring Boot项目,该教程所有知识点均来源于本人的真实开发!

前言

在前一节的学习中,慕歌分享了如何构建自己的小型日志用于记录一些关键性的信息,监测用户的登录状态等... 在这一节中慕歌将就上一节中关于ip 的点进行详细的讲解,带大家在spring boot 项目中获取请求的ip与详细地址,我们的很多网站app 中都已经新增了ip 地址显示,大家也可以用在自己的开发中,显得更高级。

引入:

如果使用本地ip 解析的话,我们将会借助ip2region,该项目维护了一份较为详细的本地ip 地址对应表,如果为了离线环境的使用,需要导入该项目依赖,并指定版本,不同版本的方法可能存在差异。

<!--    ip库-->
<dependency>
	<groupId>org.lionsoul</groupId>
	<artifactId>ip2region</artifactId>
	<version>2.6.3</version>
        </dependency>

官方gitee:gitee.com/lionsoul/ip…

开发:

在使用时需要将 xdb 文件下载到工程文件目录下,使用ip2region即使是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:

  • vIndex 索引缓存 :使用固定的 512KiB 的内存空间缓存 vector index 数据,减少一次 IO 磁盘操作,保持平均查询效率稳定在10-20微秒之间。
  • xdb 整个文件缓存:将整个 xdb 文件全部加载到内存,内存占用等同于 xdb 文件大小,无磁盘 IO 操作,保持微秒级别的查询效率。
/**
 * ip查询
 */
@Slf4j
public class IPUtil {
    private static final String UNKNOWN = "unknown";
    protected IPUtil(){ }
    /**
     * 获取 IP地址
     * 使用 Nginx等反向代理软件, 则不能通过 request.getRemoteAddr()获取 IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,
     * X-Forwarded-For中第一个非 unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        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("WL-Proxy-Client-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" : ip;
    }
    public static  String getAddr(String ip){
        String dbPath = "src/main/resources/ip2region/ip2region.xdb";
        // 1、从 dbPath 加载整个 xdb 到内存。
        byte[] cBuff;
        try {
            cBuff = Searcher.loadContentFromFile(dbPath);
        } catch (Exception e) {
            log.info("failed to load content from `%s`: %s\n", dbPath, e);
            return null;
        }
        // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
        Searcher searcher;
        try {
            searcher = Searcher.newWithBuffer(cBuff);
        } catch (Exception e) {
           log.info("failed to create content cached searcher: %s\n", e);
            return null;
        }
        // 3、查询
        try {
            String region = searcher.searchByStr(ip);
            return region;
        } catch (Exception e) {
            log.info("failed to search(%s): %s\n", ip, e);
        }
        return null;
    }

这里我们将ip 解析封装成一个工具类,包含获取IP和ip 地址解析两个方法,ip 的解析可以在请求中获取。获取到ip后,需要根据ip ,在xdb 中查找对应的IP地址的解析,由于是本地数据库可能存在一定的缺失,部分ip 存在无法解析的情况。

在线解析:

如果想要获取更加全面的ip 地址信息,可使用在线数据库,这里提供的是 whois.pconline.com 的IP解析,该IP解析在我的使用过程中表现非常流畅,而且只有少数的ip 存在无法解析的情况。

@Slf4j
public class AddressUtils {
    // IP地址查询
    public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";

    // 未知地址
    public static final String UNKNOWN = "XX XX";

    public static String getRealAddressByIP(String ip) {
        String address = UNKNOWN;
        // 内网不查询
        if (IpUtils.internalIp(ip)) {
            return "内网IP";
        }
        if (true) {
            try {
                String rspStr = sendGet(IP_URL, "ip=" + ip + "&json=true" ,"GBK");
                if (StrUtil.isEmpty(rspStr)) {
                    log.error("获取地理位置异常 {}" , ip);
                    return UNKNOWN;
                }
                JSONObject obj = JSONObject.parseObject(rspStr);
                String region = obj.getString("pro");
                String city = obj.getString("city");
                return String.format("%s %s" , region, city);
            } catch (Exception e) {
                log.error("获取地理位置异常 {}" , ip);
            }
        }
        return address;
    }
    public static String sendGet(String url, String param, String contentType) {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            log.info("sendGet - {}" , urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept" , "*/*");
            connection.setRequestProperty("connection" , "Keep-Alive");
            connection.setRequestProperty("user-agent" , "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            log.info("recv - {}" , result);
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception ex) {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }
}

场景:

那么在开发的什么流程获取ip 地址是比较合适的,这里就要用到我们的拦截器了。拦截进入服务的每个请求,进行前置操作,在进入时就完成请求头的解析,ip 获取以及ip 地址解析,这样在后续流程的全环节,都可以复用ip 地址等信息。

/**
 * 对ip 进行限制,防止IP大量请求
 */
@Slf4j
@Configuration
public class IpUrlLimitInterceptor implements HandlerInterceptor{

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) {

        //更新全局变量
        Constant.IP = IPUtil.getIpAddr(httpServletRequest);
        Constant.IP_ADDR = AddressUtils.getRealAddressByIP(Constant.IP);
        Constant.URL = httpServletRequest.getRequestURI();
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {
        //通过本地获取
//        获得ip
//        String ip = IPUtil.getIpAddr(httpServletRequest);
        //解析具体地址
//        String addr = IPUtil.getAddr(ip);

        //通过在线库获取
//        String ip = IpUtils.getIpAddr(httpServletRequest);
//        String ipaddr = AddressUtils.getRealAddressByIP(ipAddr);
//        log.info("IP >> {},Address >> {}",ip,ipaddr);
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

    }
}
如果想要执行我们的ip 解析拦截器,需要在spring boot的视图层进行拦截才会触发我们的拦截器。
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    IpUrlLimitInterceptor ipUrlLimitInterceptor;

	    //执行ip拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(ipUrlLimitInterceptor)
                // 拦截所有请求
                .addPathPatterns("/**");
    }
}
通过这样的一套流程下来,我们就能实现对每一个请求进行ip 获取、ip解析,为每个请求带上具体ip地址的小尾巴。

到此这篇关于Spring Boot快速实现 IP地址解析的文章就介绍到这了,更多相关Spring Boot IP地址内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot获取真实ip地址的方法实例

    需求是用户在登录时,密码输错了5次以上要将这个ip禁用,所以如题,我需要在登录接口代码中获得这次请求的原始ip地址.禁用ip的逻辑已经写好了,目前痛点是获取ip地址,也正是这块代码让我被领导叼了. 既然项目一般都是走nginx和网关代理,那肯定得百度了,于是百度的代码如下: /** * 获取ip地址 * * @param request * @return */ public String getIpAddr(HttpServletRequest request) { String ip = r

  • springboot 获取访问接口的请求的IP地址的实现

    工具类: import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; import java.net.UnknownHostException; /** * @Author : JCccc * @CreateTime : 2018-11-23 * @Description : * @Point: Keep a good mood **/ public class IpUtil { public static

  • Spring Boot快速实现 IP地址解析的示例详解

    目录 前言 引入: 开发: 在线解析: 场景: 大家好! 我是慕歌,一只想教你学习 Spring Boot的野生coder! 欢迎来到慕歌的 Sping boot系列教程,希望通过这个教程带大家搭建基础的 Spring Boot项目,该教程所有知识点均来源于本人的真实开发! 前言 在前一节的学习中,慕歌分享了如何构建自己的小型日志用于记录一些关键性的信息,监测用户的登录状态等... 在这一节中慕歌将就上一节中关于ip 的点进行详细的讲解,带大家在spring boot 项目中获取请求的ip与详细

  • Spring Boot中slf4j日志依赖关系示例详解

    前言 SpringBoot底层使用的是slf4j+logback来进行日志记录 把其他common-logging.log4j.java.util.logging转换为slf4j 下面这篇文章主要给大家介绍了关于Spring Boot slf4j日志依赖关系的相关内容,下面话不多说了,来一起看看详细的介绍吧 底层依赖关系 关系如何转化 底层通过偷梁换柱的方法,用jcl.jul.log4j中间转换包进行转化 如果要引入其他框架,必须将其中默认日志依赖剔除 SpringBoot从maven依赖中剔除

  • spring boot拦截器的使用场景示例详解

    前言 在用户登陆之后,我们一般会把用户登陆的状态和相关信息进行存储,把对应的token返回到客户端进行存储,下次请求过来时,系统可以通过token拿到当前这个用户的相关信息,这是授权通常的作法,而有时一些业务里,你存储的用户信息不是全局的,可能只是某几个接口会用户某些信息,而你把它存储起来就不是很合理:并且一些隐私信息持久化到redis也不合理,这时就需要统一对这种接口的请求做一起处理了. 拦截器HandlerInterceptor 我们可以去实现这个HandlerInterceptor接口,它

  • Spring Boot之@Async异步线程池示例详解

    目录 前言 一. Spring异步线程池的接口类 :TaskExecutor 二.简单使用说明 三.定义通用线程池 1.定义线程池 2.异步方法使用线程池 3.通过xml配置定义线程池 四.异常处理 五.问题 前言 很多业务场景需要使用异步去完成,比如:发送短信通知.要完成异步操作一般有两种: 1.消息队列MQ 2.线程池处理. 我们来看看Spring框架中如何去使用线程池来完成异步操作,以及分析背后的原理. 一. Spring异步线程池的接口类 :TaskExecutor 在Spring4中,

  • Spring Boot框架中的@Conditional注解示例详解

    目录 1. @Conditional 注解 2. Spring boot 扩展 1) @ConditionalOnClass和@ConditionalOnMissingClass注解 2) @ConditionalOnBean 和@ConditionalOnMissingBean注解 3) @ConditionalOnProperty注解 1. @Conditional 注解 @Conditional注解是Spring-context模块提供了一个注解,该注解的作用是可以根据一定的条件来使@Co

  • spring boot 图片上传与显示功能实例详解

    首先描述一下问题,spring boot 使用的是内嵌的tomcat, 所以不清楚文件上传到哪里去了, 而且spring boot 把静态的文件全部在启动的时候都会加载到classpath的目录下的,所以上传的文件不知相对于应用目录在哪,也不知怎么写访问路径合适,对于新手的自己真的一头雾水. 后面想起了官方的例子,没想到一开始被自己找到的官方例子,后面太依赖百度谷歌了,结果发现只有官方的例子能帮上忙,而且帮上大忙,直接上密码的代码 package hello; import static org

  • Spring Boot的listener(监听器)简单使用实例详解

    监听器(Listener)的注册方法和 Servlet 一样,有两种方式:代码注册或者注解注册 1.代码注册方式 通过代码方式注入过滤器 @Bean public ServletListenerRegistrationBean servletListenerRegistrationBean(){ ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean

  • Spring boot进行参数校验的方法实例详解

    Spring boot开发web项目有时候我们需要对controller层传过来的参数进行一些基本的校验,比如非空.整数值的范围.字符串的长度.日期.邮箱等等.Spring支持JSR-303 Bean Validation API,可以方便的进行校验. 使用注解进行校验 先定义一个form的封装对象 class RequestForm { @Size(min = 1, max = 5) private String name; public String getName() { return n

  • 浅谈使用Java Web获取客户端真实IP的方法示例详解

    Java-Web获取客户端真实IP: 发生的场景:服务器端接收客户端请求的时候,一般需要进行签名验证,客户端IP限定等情况,在进行客户端IP限定的时候,需要首先获取该真实的IP. 一般分为两种情况: 方式一.客户端未经过代理,直接访问服务器端(nginx,squid,haproxy): 方式二.客户端通过多级代理,最终到达服务器端(nginx,squid,haproxy): 客户端请求信息都包含在HttpServletRequest中,可以通过方法getRemoteAddr()获得该客户端IP.

  • Spring Boot 中PageHelper 插件使用配置思路详解

    使用思路 1.引入myabtis和pagehelper依赖 2.yml中配置mybatis扫描和实体类 这2行代码 pageNum:当前第几页 pageSize:显示多少条数据 userList:数据库查询的数据数据列表 PageHelper.startPage(pageNum, pageSize); PageInfo pageInfo = new PageInfo(userList); 最后返回一个pageInfo 对象即可,pageInfo 这个对象中只有数据一些信息,但是,没有成功失败的状

随机推荐