评论显示IP地址

3,879 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的5天,点击查看活动详情

近期很多平台(微博,抖音,微信等)都开放了 IP 属地的显示,这个功能挺有趣的,同时我也琢磨了很久,想在自己的项目中使用,现在就写下这篇文章谈谈我的思路;

实现

如何获取IP地址

从 HttpServletRequest 中可以获取到客户端在请求头中携带的IP地址

头部名词的解释

  • X-Forwarded-For:Web 服务器获取访问用户的真实 IP 地址,若存在代理,最左边是最原始客户端的 IP 地址;
  • Proxy-Client-IP:经过 Apache http 服务器的请求才会有的请求头
  • WL-Proxy-Client-IP:也是通过 Apache http 服务器,在 weblogic 插件加上的头。
// 获取IP
    public static String getIP(HttpServletRequest request) {
        String ipStr = request.getHeader("x-forwarded-for");
        if (StringUtils.isBlank(ipStr) || "unknown".equalsIgnoreCase(ipStr)) {
            ipStr = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(ipStr) || "unknown".equalsIgnoreCase(ipStr)) {
            ipStr = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(ipStr) || "unknown".equalsIgnoreCase(ipStr)) {
            ipStr = request.getRemoteAddr();
        }

        // 多个路由时,取第一个非unknown的ip
        final String[] arr = ipStr.split(",");
        for (final String str : arr) {
            if (!"unknown".equalsIgnoreCase(str)) {
                ipStr = str;
                break;
            }
        }
        //目的是将localhost访问对应的ip 0:0:0:0:0:0:0:1 转成 127.0.0.1。
        return ipStr.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ipStr;
    }

如何获取位置

  1. 使用百度开放平台的IP定位Api;
public static String getLocationByIP(HttpServletRequest request) {
        // 根据请求获取IP
        String ip = getIP(request);
        String location = null;

        // 地址模板 https://api.map.baidu.com/location/ip?ak=AK&ip=ip&coor=xxx
        String urlString = "https://api.map.baidu.com/location/ip?ak=" + AK + "&ip=" + ip + "&coor=bd09ll&oe=utf8";
        try {
            // 创建请求链接
            URL url = new URL(urlString);

            // 开始请求
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            // 设置请求
            urlConnection.setRequestMethod("GET");

            // 设置请求超时时间
            urlConnection.setConnectTimeout(15000);

            int responseCode = urlConnection.getResponseCode();
            // 判断请求是否成功
            if (responseCode >= 200 && responseCode < 300) {
                InputStream inputStream = urlConnection.getInputStream();

                // 获取响应的信息
                byte[] bytes = new byte[1024];
                int len = 0;
                StringBuilder stringBuilder = new StringBuilder();
                while ((len = inputStream.read(bytes)) != -1) {
                    String s = new String(bytes, 0, len);
                    stringBuilder.append(s);
                }
                // 将json转换为IPEntity对象
                String string = stringBuilder.toString();
                Gson gson = new Gson();
                IPEntity ipEntity = gson.fromJson(string, IPEntity.class);

                // 判断获取的信息是否有误
                String status = ipEntity.getStatus();
                if (Integer.parseInt(status) == 0) {
                    // 获取省份
                    location = ipEntity.getContent().getAddress_detail().getProvince();
                    log.info("IP地址为:" + location);
                } else {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "请求的IP有误");
                }
                inputStream.close();
            } else {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "请求百度API失败");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return location;
    }
  1. 使用GitHub上的 IP2region,读者自行前去查看;

方式

我在实现的时候一直纠结于以下两种方式:

  1. 🔥用户表添加 IP 字段,每次 登录或注册 的时候进行查询,并且更新,即将 IP 捆绑在用户表中;

优点:

  • 统一管理,其他业务可以直接从该表获取用户的 IP;
  • 若用户前往另一个属地,也可以准确地查找出来;

缺点:

  • 当其他地方需要使用到 IP 地址时,就要进行多一次查询
  1. 🔥直接在评论表中添加 IP 的字段,即在相应的业务表中添加字段;

优点:

  • 查询速度快,直接从表中获取到 IP 字段;

缺点:

  • 不可重复使用,当有其他地方需要显示IP时,就要在其他地方继续添加IP字段;
  • 后期更改不够方便,因为用户一旦改变了IP地址,之前所存储到的用户IP都是错误的,但是这种事情发生的概率较低,因为我们记录的是省份,用户跨省流动的概率较低;

效果

我是基于第一种方法来实现的,在用户表中记录用户的IP地址,其他业务中用到就直接查询即可;