SpringBoot轻松实现ip解析(含源码)
作者:mmseoamin日期:2023-12-11

文章目录

      • 前言
      • 应用场景
        • (1)网站访问分析
        • (2)欺诈风险控制
        • (3)限制服务区域
        • (4)显示访问者来源
        • 示例
          • 前期准备
            • Ip2region 特性
              • 1、IP 数据管理框架
              • 2、数据去重和压缩
              • 3、极速查询响应
              • 版本依赖
              • 导入库
              • 具体代码
                • Constant
                • AddressUtils(在线解析)
                • IpUtil(离线解析)
                • IpController
                • 执行结果
                • 总结
                • 源码获取
                • 写在最后

                  SpringBoot轻松实现ip解析(含源码),579a429daf314744b995f37351b46548,第1张

                  前言

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


                  应用场景

                  SpringBoot轻松实现ip解析(含源码),image-20231020111556058,第2张

                  (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
                  
                  
                  具体代码
                  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";
                  }
                  
                  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();
                      }
                  }
                  
                  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;
                      }
                  }
                  
                  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;
                      }
                  }
                  
                  执行结果

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

                  如果执行:

                  String realAddress = AddressUtils.getRealAddressByIP(ip);
                  

                  显示结果:

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

                  如果执行:

                  String realAddress = IpUtil.getAddr(ip);
                  

                  显示结果:

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

                  总结

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

                  源码获取

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


                  写在最后

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

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