• SpringBoot轻松实现ip解析(含源码)



    579a429daf314744b995f37351b46548

    前言

    IP地址一般以数字形式表示,如192.168.0.1。IP解析是将这个数字IP转换为包含地区、城市、运营商等信息的字符串形式,如“广东省深圳市 电信”。这样更方便人理解和使用。


    应用场景

    image-20231020111556058

    (1)网站访问分析

    可以解析用户IP地址,分析网站访问量的地域分布,以便进行针对性推广。

    (2)欺诈风险控制

    某地区IP地址多次进行恶意刷单、刷票等行为,可以通过IP地址解析加以拦截。

    (3)限制服务区域

    解析用户IP地址,限制仅为某地区的用户提供服务。

    (4)显示访问者来源

    在博客、论坛等,解析并显示每个帖子的发帖人来自哪个地区。

    下面带大家实践在spring boot 项目中获取请求的ip与详细地址。


    示例

    前期准备

    引用框架:Ip2region

    下载地址: https://gitee.com/lionsoul/ip2region.git

    注意:如果需要离线获取需要下载 /data/ip2region.xdb 文件

    Ip2region 特性
    1、IP 数据管理框架

    xdb 支持亿级别的 IP 数据段行数,默认的 region 信息都固定了格式:国家|区域|省份|城市|ISP,缺省的地域信息默认是0。 region 信息支持完全自定义,例如:你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编等。也就是你完全可以使用 ip2region 来管理你自己的 IP 定位数据。

    2、数据去重和压缩

    xdb 格式生成程序会自动去重和压缩部分数据,默认的全部 IP 数据,生成的 ip2region.xdb 数据库是 11MiB,随着数据的详细度增加数据库的大小也慢慢增大。

    3、极速查询响应

    即使是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:

    1. vIndex 索引缓存 :使用固定的 512KiB 的内存空间缓存 vector index 数据,减少一次 IO 磁盘操作,保持平均查询效率稳定在10-20微秒之间。
    2. xdb 整个文件缓存:将整个 xdb 文件全部加载到内存,内存占用等同于 xdb 文件大小,无磁盘 IO 操作,保持微秒级别的查询效率。
    版本依赖
    框架名版本号
    SpringBoot3.1.4
    jdk17
    tomcat-embed-core10.1.8
    lombok1.18.26
    fastjson1.2.73
    ip2region2.7.0
    导入库
    
        org.springframework.boot
        spring-boot-starter
    
    
    
        org.springframework.boot
        spring-boot-starter-test
        test
    
    
    
    
        org.lionsoul
        ip2region
        2.7.0
    
    
        org.apache.tomcat.embed
        tomcat-embed-core
        10.1.8
        compile
    
    
        org.projectlombok
        lombok
        1.18.26
        compile
    
    
    
        org.apache.commons
        commons-lang3
    
    
    
        com.alibaba
        fastjson
        1.2.73
        compile
    
    
        org.springframework
        spring-webmvc
    
    
        org.springframework
        spring-webmvc
    
    
        org.springframework
        spring-webmvc
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    具体代码
    Constant
    public interface Constant {
    
        // IP地址查询
        public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
        /**
         * 本地ip
         */
        public static final String LOCAL_IP = "127.0.0.1";
    
    
        /**
         * 数据库访问地址
         */
        public static final String DB_PATH = "springboot-ip/src/main/resources/ip2region/ip2region.xdb";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    AddressUtils(在线解析)
    import com.alibaba.fastjson.JSONObject;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.ConnectException;
    import java.net.SocketTimeoutException;
    import java.net.URL;
    import java.net.URLConnection;
    
    import static com.example.springbootip.constant.Constant.IP_URL;
    import static com.example.springbootip.constant.Constant.LOCAL_IP;
    
    /**
     * @author coderJim
     * @date 2023-10-16 14:07
     */
    @Slf4j
    public class AddressUtils {
    
        // 未知地址
        public static final String UNKNOWN = "XX XX";
    
        public static String getRealAddressByIP(String ip) {
            String address = UNKNOWN;
            // 内网不查询
            if (ip.equals(LOCAL_IP)) {
                return "内网IP";
            }
            if (true) {
                try {
                    String rspStr = sendGet(IP_URL, "ip=" + ip + "&json=true" ,"GBK");
                    if (StringUtils.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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    IpUtil(离线解析)
    import static com.example.springbootip.constant.Constant.DB_PATH;
    import static com.example.springbootip.constant.Constant.LOCAL_IP;
    
    import jakarta.servlet.http.HttpServletRequest;
    import lombok.extern.slf4j.Slf4j;
    import org.lionsoul.ip2region.xdb.Searcher;
    
    import java.net.InetAddress;
    import java.net.UnknownHostException;
    
    /**
     * @author coderJim
     * @date 2023-10-16 11:02
     */
    @Slf4j
    public class IpUtil {
    
        protected IpUtil(){ }
    
        /**
         * 获取 IP地址
         * 使用 Nginx等反向代理软件, 则不能通过 request.getRemoteAddr()获取 IP地址
         * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,
         * X-Forwarded-For中第一个非 unknown的有效IP字符串,则为真实IP地址
         */
        public static String getIpAddr(HttpServletRequest request) {
            String ipAddress;
            try {
                ipAddress = request.getHeader("x-forwarded-for");
                if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("Proxy-Client-IP");
                }
                if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("WL-Proxy-Client-IP");
                }
                if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getRemoteAddr();
                    if (ipAddress.equals(LOCAL_IP)) {
                        // 根据网卡取本机配置的IP
                        InetAddress inet = null;
                        try {
                            inet = InetAddress.getLocalHost();
                        } catch (UnknownHostException e) {
                            log.error(e.getMessage(), e);
                        }
                        ipAddress = inet.getHostAddress();
                    }
                }
                // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
                if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                    // = 15
                    if (ipAddress.indexOf(",") > 0) {
                        ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                    }
                }
            } catch (Exception e) {
                ipAddress = "";
            }
    
            return ipAddress;
        }
    
        public static  String getAddr(String ip) {
            String project_dir = System.getProperty("user.dir") +"";
            String dbPath = project_dir + "/" + DB_PATH;
            // 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.search(ip);
                return region;
            } catch (Exception e) {
                log.info("failed to search(%s): %s\n", ip, e);
            }
            return null;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    IpController
    import com.example.springbootip.util.AddressUtils;
    import com.example.springbootip.util.IpUtil;
    import jakarta.servlet.http.HttpServletRequest;
    import org.springframework.web.bind.annotation.*;
    
    /**
     * @author coderJim
     */
    @RestController
    @RequestMapping("/demo")
    public class IpController {
        
        @ResponseBody
        @GetMapping("/realAddress")
        public String realAddress(HttpServletRequest request){
            String ip = IpUtil.getIpAddr(request);
            //在线获取
    //        String realAddress = AddressUtils.getRealAddressByIP(ip);
            //离线获取
            String realAddress = IpUtil.getAddr(ip);
            return "您的ip所在地为:"+ realAddress;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    执行结果

    运行请求 http://127.0.0.1:8080/demo/realAddress

    如果执行:

    String realAddress = AddressUtils.getRealAddressByIP(ip);
    
    • 1

    显示结果:

    您的ip所在地为:浙江省 杭州市
    
    • 1

    如果执行:

    String realAddress = IpUtil.getAddr(ip);
    
    • 1

    显示结果:

    您的ip所在地为:中国|0|浙江省|杭州市|电信
    
    • 1

    总结

    通过这样的一套流程下来,我们就能实现对每一个请求进行ip 获取、ip解析

    源码获取

    ​如果需要完整源码请关注公众号"架构殿堂" ,回复 "SpringBoot+ip"即可获得


    写在最后

    感谢您的支持和鼓励! 😊🙏

    如果大家对相关文章感兴趣,可以关注公众号"架构殿堂",会持续更新AIGC,java基础面试题, netty, spring boot, spring cloud等系列文章,一系列干货随时送达!

  • 相关阅读:
    源码安装Apache
    JavaScript的综合案例
    未履行数据保护义务造成数据泄露,某大药房被罚110万
    基于卷积的高效网络设计四大要点
    【每日一读】CoRGi: Content-Rich Graph Neural Networks with Attention
    C++ 学习(八)结构体、结构体数组、结构体指针、结构体嵌套、结构体作为参数、结构体中const使用
    nodejs+vue+elementui在线公益-帮助流浪动物网站python java
    火山引擎ByteHouse联合Apache Airflow,让数据管理更加高效
    基于 jetpack compose,使用MVI架构+自定义布局实现的康威生命游戏
    数据可视化
  • 原文地址:https://blog.csdn.net/jinxinxin1314/article/details/133981213