前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Springboot&websocket实现IP数据实时统计

Springboot&websocket实现IP数据实时统计

作者头像
不愿意做鱼的小鲸鱼
发布2022-09-26 19:10:23
1.4K0
发布2022-09-26 19:10:23
举报
文章被收录于专栏:web全栈

最近想给自己的博客网站实现一个自定义的数据后台系统,实现对外提供api数据接口,和监控站点的访问数据,并且进行数据的实时可视化出来。这可能是偶然看到一个ip的精准定位的页面引起的我的一点兴趣,通过ip获取获取信号的经纬度,来达到一个实时定位的功能。要实现这些并不难,也刚好可以应用最近学的一些东西,使用websocket可以实现完全实时统计在线人数等信息,于是就开始尝试动手干了起来。

需求分析

1、提供博客系统相关数据的api:

使用wordpress的一个插件:JSON API

Springboot&websocket实现IP数据实时统计-左眼会陪右眼哭の博客
Springboot&websocket实现IP数据实时统计-左眼会陪右眼哭の博客

2、博客数据可视化:

  • 页头总文章数、昨日访客、总访客数(自己写接口)
  • 最近发布的文章列表
  • 按日期统计文章发表数立方图
  • 文章分类饼图
  • 博客标签词云
  • 实时在线人数面板
  • TOP100访客IP信息和定位地图
  • 你是今天的第几个访问者

3、数据结构(msyql):

  • IP:IP地址
  • address:地区(address)
  • UA:访问来源(浏览器、系统等)
  • time:访问时间
  • axis 坐标:IP来源坐标
  • count 日点击数
  • status 是否在线

实现策略

后台数据策略

1、 使用websocket实时获取在线人数,并且对外提供服务 2.、新建redis表,用来存取每日最新全部访问数据(定时任务进行数据更新每天晚上3点将数据同步到MySQL,redis只用来存当天的访问数据) 3、需要获取访问者的IP等信息,然后新建一张表,对这些信息进行存储,对外提供最近访问的前100条数据 4、过滤重复IP的问题,暂时选择使用:redis使用hset结构记录数据,拿到Redis中的数据的count字段,如果为空就赋值为1,否则的话进行自增。websocket中使用 ConcurrentHashMap<String, Set<WebSocketServer>>数据结构存储(该数据每天晚上3点同步到数据库) 5、提供100条数据的策略:先从redis里查询数据,如果少于100条数据,则不够的从数据库里面取剩余需要的数据 6、判断用户是否在线:websoket主体类中,用户下线就remove对应ip的session,知道map中该ip的session全部移出后,就修改redis对应数据中status的状态值

根据IP获取位置信息的接口

可以采用百度地图或者高德地图提供的api,需要申请 1、https://api.map.baidu.com/location/ip?ak=HQi0eHpVOLlRuIFlsTZNGlYvqLO56un3&coor=bd09ll&ip=221.214.212.103 2、https://restapi.amap.com/v5/ip?key=0347f577***************2573193f16f&type=4&ip=183.17.232.207

遇到的问题

websocket无法直接获取建立连接者的ip

springboot的websocket是无法直接获取客户端ip的,网上也有人很多人用的是netty-websocket-xx 包,这包提供了api用于获取客户端的ip。 换包太麻烦了,即使是在不换包的前提下,使用ServerEndpoint加Fliter过滤器可以解决该问题。 1、定义一个拦截器 此拦截器用于获取ip,并放入session中

代码语言:javascript
复制
package cn.kt.ipcount.filter;
import cn.kt.ipcount.utils.IPUtil;
import nl.bitwalker.useragentutils.Browser;
import nl.bitwalker.useragentutils.OperatingSystem;
import nl.bitwalker.useragentutils.UserAgent;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * Created by tao.
 * Date: 2022/1/4 17:11
 * 描述:
 */
@javax.servlet.annotation.WebFilter(filterName = "sessionFilter", urlPatterns = "/*")
@Order(1)
public class WebFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        UserAgent userAgent = UserAgent.parseUserAgentString(req.getHeader("user-agent"));
        String browserName = userAgent.getBrowser().getName();
        String os = userAgent.getOperatingSystem().getName();
        req.getSession().setAttribute("ip", IPUtil.getIpAddress(req));
        req.getSession().setAttribute("ua", browserName + " " + os);
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

2、定义 WebSocketConfigurator 用于将客户端的ip传递给websocket中的session,相当于是一个中介

代码语言:javascript
复制
package cn.kt.ipcount.filter;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.Enumeration;
import java.util.Map;
/**
 * Created by tao.
 * Date: 2022/1/4 17:12
 * 描述: 服务端点类
 */
public class WebSocketConfigurator extends ServerEndpointConfig.Configurator {
    public static final String IP_ADDR = "IP.ADDR";
    public static final String IP_UA = "IP.UA";
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {

        Map<String, Object> attributes = sec.getUserProperties();
        HttpSession session = (HttpSession) request.getHttpSession();
        if (session != null) {
            attributes.put(IP_ADDR, session.getAttribute("ip"));
            attributes.put(IP_UA, session.getAttribute("ua"));
            Enumeration<String> names = session.getAttributeNames();
            while (names.hasMoreElements()) {
                String name = names.nextElement();
                attributes.put(name, session.getAttribute(name));
            }
        }
    }
}

3、配置websocket 主体类用于管理websocket连接,并配置configurator

代码语言:javascript
复制
@Component
@ServerEndpoint(value = "/websocket", configurator = WebSocketConfigurator.class)
@Slf4j
public class WebSocketServer {
    private Session session;
    private static ConcurrentHashMap<String, Set<WebSocketServer>> serverMap = new ConcurrentHashMap<>();
    @OnOpen
    public void onOpen(Session session) {
        Map<String, Object> userProperties = session.getUserProperties();
        // 获取IP和UA
        String ipAddr = (String) userProperties.get(WebSocketConfigurator.IP_ADDR);
        String ua = (String) userProperties.get(WebSocketConfigurator.IP_UA);
        Set<WebSocketServer> webSocketServers = serverMap.containsKey(ipAddr) ? serverMap.get(ipAddr) : new HashSet<>();
        webSocketServers.add(this);
        serverMap.put(ipAddr, webSocketServers);
        webSocketServers.forEach(System.out::println);
        log.info("【websocket消息】有新的连接, 总数:{}", serverMap.size());
        sendMessage(serverMap.size() + "");
    }
    ......
}

注意:加上断点注解:@ServerEndpoint(value = "/websocket", configurator = WebSocketConfigurator.class),然后通过session即可获取Filter中的数据。

websocket无法注入对象

java springboot websocket 不能注入( @Autowired ) service bean 报 null 错误 解决方法: spring 或 springboot 的 websocket 里面使用 @Autowired 注入 service 或 bean 时,报空指针异常,service 为 null(并不是不能被注入)。 解决方法:将要注入的 service 改成 static,就不会为null了。

代码语言:javascript
复制
@Controller
@ServerEndpoint(value="/chatSocket")
public class ChatSocket {
    //  这里使用静态,让 service 属于类
    private static ChatService chatService;

    // 注入的时候,给类的 service 注入
    @Autowired
    public void setChatService(ChatService chatService) {
        ChatSocket.chatService = chatService;
    }
}

原因:本质原因:spring管理的都是单例(singleton)和 websocket (多对象)相冲突。

iP详细信息和ua的获取并解析

1、获取用户的真实ip IPUtil.java

代码语言:javascript
复制
package cn.kt.ipcount.utils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
 * Created by tao.
 * Date: 2022/1/4 11:08
 * 描述:
 */
public class IPUtil {
    /**
     * 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址。
     * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
     * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串
     *
     * @param request
     * @return
     */
    public static String getIpAddress(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.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "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) {
                    e.printStackTrace();
                }
                ip = inet.getHostAddress();
            }
        }
        return ip;
    }
}

2、springboot获取请求的ua

代码语言:javascript
复制
// 获取
String userAgent = request.getHeader("user-agent");
/*
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
*/

3、springboot解析请求的ua

  • 添加依赖
代码语言:javascript
复制
    <!-- https://mvnrepository.com/artifact/eu.bitwalker/UserAgentUtils -->
    <dependency>
        <groupId>eu.bitwalker</groupId>
        <artifactId>UserAgentUtils</artifactId>
        <version>1.21</version>
    </dependency>
 

  • 解析ua
代码语言:javascript
复制
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("user-agent"));
String clientType = userAgent.getOperatingSystem().getDeviceType().toString();
LOGGER.info("clientType = " + clientType);   //客户端类型  手机、电脑、平板
String os = userAgent.getOperatingSystem().getName();
LOGGER.info("os = " + os);    //操作系统类型
String ip = IpUtil.getIpAddress(request);
LOGGER.info("ip = " + ip);    //请求ip
String browser = userAgent.getBrowser().toString();
LOGGER.info("browser = " + browser);    //浏览器类型

 

websocket压测

正常来说websoket的最大长连接数可以达到16000个。 参考文章:https://blog.csdn.net/lnkToKing/article/details/79493498

实现效果

1、来访统计:http://ip.qkongtao.cn/

Springboot&websocket实现IP数据实时统计-左眼会陪右眼哭の博客
Springboot&websocket实现IP数据实时统计-左眼会陪右眼哭の博客
Springboot&websocket实现IP数据实时统计-左眼会陪右眼哭の博客
Springboot&websocket实现IP数据实时统计-左眼会陪右眼哭の博客

2、文章数据可视化:

Springboot&websocket实现IP数据实时统计-左眼会陪右眼哭の博客
Springboot&websocket实现IP数据实时统计-左眼会陪右眼哭の博客

源码下载

下载链接:https://gitee.com/KT1205529635/ip-count

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求分析
  • 实现策略
    • 后台数据策略
      • 根据IP获取位置信息的接口
      • 遇到的问题
        • websocket无法直接获取建立连接者的ip
          • websocket无法注入对象
            • iP详细信息和ua的获取并解析
              • websocket压测
              • 实现效果
              • 源码下载
              相关产品与服务
              云数据库 Redis
              腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档