SpringBoot后端统计网站的IP访问次数及地区

这篇具有很好参考价值的文章主要介绍了SpringBoot后端统计网站的IP访问次数及地区。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

         项目是用SpringBoot+Vue实现,前后端分离的,前端是用nginx部署的,虽说可以通过Nginx的日志来统计网站的IP的访问次数,但想在前端用图形化的方式来展示是不太可行的,所以我想着是在SpringBoot后端来实时统计访问的网站的IP及其次数和地区,然后存储在数据库中,前端可以通过后端提供的相应的接口获取到数据,接着图形化的渲染访问数据,这里只讲SpringBoot后端这部分。

        这里统计IP的地区用的是ip2region,可以参考我之前写的入门文章

Java中使用ip2region获取IP的地址_像向日葵一样~的博客-CSDN博客_ip2region javaip2region是准确率很高的 ip 地址定位库,本文介绍在Java中使用ip2region获取IP的地址https://blog.csdn.net/zhiwenganyong/article/details/122755057?spm=1001.2014.3001.5502        不过本次我用的是IP库是 ip2region.xdb,是一个更新的库

ip2region: Ip2region (2.0 - xdb) 是一个离线 IP 数据管理框架和定位库,支持亿级别的数据段,10微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现。 - Gitee.comhttps://gitee.com/lionsoul/ip2region/tree/master/data         其中累加IP的访问次数是用redis的incr,后端会提供给前端一个接口当访问网站前端会调这个接口,然后用AOP在接口返回加入逻辑(也可以通过拦截器来拦截接口加入逻辑,但得先解决自动注入为null的问题,注入为null是因为拦截器加载是在springcontext创建之前完成的),先捕获请求获取真实IP,接着封装好消息发给MQ,MQ在进行后续操作。

 引入依赖

        Gradle

// https://mvnrepository.com/artifact/org.lionsoul/ip2region
implementation group: 'org.lionsoul', name: 'ip2region', version: '2.6.4'

        Maven

<!-- https://mvnrepository.com/artifact/org.lionsoul/ip2region -->
<dependency>
    <groupId>org.lionsoul</groupId>
    <artifactId>ip2region</artifactId>
    <version>2.6.4</version>
</dependency>

 配置文件

        配置好redis、rabbitmq及IP库的地址

  redis:
    # 数据库索引
    database: 0
    # 单节点redis配置
    host: xxx.xxx.xxx.xxx
    port: 6379
    timeout: 60000
    password: xxxxxx


  # 配置Rabbitmq服务
  rabbitmq:
    host: xxx.xxx.xxx.xxx
    port: 5672
    username: admin
    password: xxx.xxx.xxx.xxx
    publisher-confirm-type: correlated
    listener:
      simple:
        retry:
          # 开启消费者重试机制(默认就是true,false则取消重试机制)
          enabled: true
          # 最大重试次数
          max-attempts: 3
          # 重试间距(单位:秒)
          initial-interval: 2s

  xdb:
    profile: /usr/local/xdb/

 MQ的配置类

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 像向日葵一样~
 * @date 2022-08-03-12:51
 */
@Configuration
public class MQConfig {

    // 定义交换机名称
    public static final String VISIT_COUNT_MSG_EXCHANGE = "visit_count_msg_exchange";
    // 定义队列  系统发给访问统计的消息队列
    public static final String VISIT_HANDLE_QUEUE = "visit_handle_queue";
    // 定义routingKey
    public static final String VISIT_HANDLE_RK = "visit_handle_rk";

    // 声明交换机
    @Bean
    public DirectExchange visitMsgExchange() {
        return ExchangeBuilder.directExchange(VISIT_COUNT_MSG_EXCHANGE).durable(true).build();
    }

    // 声明队列
    @Bean
    public Queue handleMsgQueue() {
        return QueueBuilder.durable(VISIT_HANDLE_QUEUE).build();
    }

    // 绑定交换机和队列
    @Bean
    public Binding type1QueueBindingExchange(@Qualifier("handleMsgQueue") Queue handleMsgQueue,
                                             @Qualifier("visitMsgExchange") DirectExchange visitMsgExchange) {
        return BindingBuilder.bind(handleMsgQueue).to(visitMsgExchange).with(VISIT_HANDLE_RK);
    }
}

 redis incr自增方法

        incr命令如果指定的key中存储的值不是字符串类型或者存储的字符串类型不能表示为一个整数,那么执行这个命令时服务器会返回一个错误(ERR value is not an integer or out of range)。

        这里我使用的是StringRedisTemplate 而不是RedisTemplate,因为redisTemplate我已经用于存储对象类型,如果这里也用RedisTemplate来存则会报异常。

  • RedisTemplate使用的是 JdkSerializationRedisSerializer
  • StringRedisTemplate使用的是 StringRedisSerializer

        同时RedisTemplate和StringRedisTemplate两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。    

        或者是重写Redis序列化方式来解决

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    public Long incr(String key, long liveTime) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        Long increment = entityIdCounter.getAndIncrement();
        if ((null == increment || increment.longValue() == 0) && liveTime > 0) {//初始设置过期时间
            entityIdCounter.expire(liveTime, TimeUnit.HOURS);
        }
        return increment;
    }

MQ发送消息

    @Autowired
    private RabbitTemplate rabbitTemplate;

    rabbitTemplate.convertAndSend(MQConfig.VISIT_COUNT_MSG_EXCHANGE,MQConfig.VISIT_HANDLE_RK,visitCountJson, msg -> {
                return msg;
        });

获得访问IP

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    String ip = getRemortIP(request);

    public String getRemortIP(HttpServletRequest request) {
        if (request.getHeader("x-forwarded-for") == null) {
            return request.getRemoteAddr();
        }
        return request.getHeader("x-forwarded-for");
    }

 AOP

/**
 * @author 像向日葵一样~
 * @date 2022-08-03-14:39
 */
@Component
@Aspect
public class ApiVisitHistory {
    private Logger log = LoggerFactory.getLogger(ApiVisitHistory.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 定义切面
     */
    @Pointcut("execution(public * com.xxx.xxx.xxx.xxx(..)))")
    public void pointCut()    {
        ......

    }

    /**
     * 在接口原有的方法执行前,将会首先执行此处的代码
     */
    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) {
        ......
    }

    /**
     * 只有正常返回才会执行此方法
     * 如果程序执行失败,则不执行此方法
     */
    @AfterReturning(returning = "returnVal", pointcut = "pointCut()")
    public void doAfterReturning(JoinPoint joinPoint, Object returnVal) {

       //封装好消息发送给MQ
       ......
    }

    /**
     * 当接口报错时执行此方法
     */
    @AfterThrowing(pointcut = "pointCut()")
    public void doAfterThrowing(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.info("接口访问失败,URI:[{}]", request.getRequestURI());
    }

}

根据IP查询地址

import org.lionsoul.ip2region.xdb.Searcher;
import java.util.concurrent.TimeUnit;

/**
 * ip 转 ip归属地工具类
 * @author 像向日葵一样~
 * @date 2022-08-03-15:21
 */
public class Ip2regionUtils {

    private Ip2regionUtils() {
    }

    /**
     * 系统默认的ip库文件名
     */
    private static final String DB_NAME = "ip2region.xdb";


    public static String searchAddrByIp(String ip,String xdbRealPath){
        // 1、从 dbPath 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
        byte[] vIndex = null;
            String dbPath = xdbRealPath+DB_NAME;

        try {
            vIndex = Searcher.loadVectorIndexFromFile(dbPath);
        } catch (Exception e) {
            System.out.printf("failed to load vector index from `%s`: %s\n", dbPath, e);
        }

        // 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
        Searcher searcher = null;
        String region;
        try {
            searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
        } catch (Exception e) {
            System.out.printf("failed to create vectorIndex cached searcher with `%s`: %s\n", dbPath, e);
        }

        // 3、查询
        try {
            long sTime = System.nanoTime();
            region = searcher.search(ip);
            long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
            System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
            return region;
        } catch (Exception e) {
            System.out.printf("failed to search(%s): %s\n", ip, e);
        }
        return null;
    }

}

         我们也可以预先加载整个 ip2region.xdb 的数据到内存,然后基于这个数据创建查询对象来实现完全基于文件的查询文章来源地址https://www.toymoban.com/news/detail-404455.html

import org.lionsoul.ip2region.xdb.Searcher;
import java.util.concurrent.TimeUnit;

/**
 * ip 转 ip归属地工具类
 * @author 像向日葵一样~
 * @date 2022-08-03-15:21
 */
public class Ip2regionUtils {

    private Ip2regionUtils() {
    }

    /**
     * 系统默认的ip库文件名
     */
    private static final String DB_NAME = "ip2region.xdb";


   public static String searchAddrByIp(String ip,String xdbRealPath) {
        // 1、从 dbPath 加载整个 xdb 到内存。
        byte[] cBuff;
        String dbPath = xdbRealPath+DB_NAME;
        try {
            cBuff = Searcher.loadContentFromFile(dbPath);
            // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
            Searcher searcher;
            // 3、查询
            searcher = Searcher.newWithBuffer(cBuff);
            long sTime = System.nanoTime();
            String region = searcher.search(ip);
            long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
            System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
            return region;
        } catch (Exception e) {
            System.out.printf("failed to search(%s): %s\n", ip, e);
        }
        // 4、关闭资源 - 该 searcher 对象可以安全用于并发,等整个服务关闭的时候再关闭 searcher
        // searcher.close();
        // 备注:并发使用,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。
        return null;
    }

}

MQ监听消息

/**
 * @author 像向日葵一样~
 * @date 2022-08-03-16:10
 */
@Component
public class VisitMsgConsumer {

    @Value("${ipxdb.profile}")
    private String xdbPath;


    @RabbitListener(queues = MQConfig.VISIT_HANDLE_QUEUE) // 访问IP处理消息队列
    public void receiveMsgFromBuyerQueue(Message message) {
        VisitCountDTO visitCountDTO = (VisitCountDTO)JsonUtil.fromJson(new String(message.getBody()), VisitCountDTO.class);
        String ipAddress = Ip2regionUtils.searchAddrByIp(visitCountDTO.getIp(),xdbPath);
        //接收到消息及获得IP、IP地址后,接着跟数据库进行交互
        ......
    }
}

到了这里,关于SpringBoot后端统计网站的IP访问次数及地区的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java接口访问限制次数(使用IP作为唯一标识)

    1、获取用户IP工具类 2、接口限制注解(切面) (1)controller层 (2)注解类 (3)切面方法

    2024年02月13日
    浏览(22)
  • 基于JAVA地区土特产介绍网站设计与实现(Springboot框架) 研究背景与意义、国内外研究现状

     博主介绍 :黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程,免费 项目配有对应开发文档、开题报告、任务书、

    2024年02月03日
    浏览(47)
  • 一键部署 Umami 统计个人网站访问数据

    谈到网站统计,大家第一时间想到的肯定是 Google Analytics。然而,我们都知道 Google Analytics 会收集所有用户的信息,对数据没有任何控制和隐私保护。 Google Analytics 收集的指标实在是太多了,有很多都是不必要的,没有博士学位可能都不太容易理解这些指标。 相比较而言,开

    2024年02月13日
    浏览(33)
  • Zabbix结合Grafana统计日志网站访问量

    Zabbix除了可以通过HTTP代理及WEB场景监控网站的响应结果、响应时间和传输速度等,也可以通过读取网站的后台日志,获取有用的统计信息。 下面我以Grafana为例,通过日志统计网站的访问量。 操作如下: 1、读取grafana日志 首先要基于Zabbix Agent针对日志进行读取,其安装请参

    2024年02月06日
    浏览(33)
  • 【Azure API 管理】APIM如何实现对部分固定IP进行访问次数限制呢?如60秒10次请求

    使用Azure API Management, 想对一些固定的IP地址进行访问次数的限制,如被限制的IP地址一分钟可以访问10次,而不被限制的IP地址则可以无限访问?   最近ChatGPT爆火,所以也把这个问题让ChatGPT来解答,然后人工验证它的回答正确与否? 根据对APIM Policy的文档参考, choose 和 rat

    2023年04月24日
    浏览(31)
  • 网站被劫持怎么办?传奇网站打开跳到其他站的解决方法

    网站劫持一直是工信部着重打击的,刚开始流量劫持行为还没有被定义为刑事犯罪,不少人把它当做一种快速牟利的手段。2015年流量劫持首次被认定为犯罪,上海浦东新区人民法院判决了全国首起流量劫持刑事案件,两名被告人被判有期徒刑3年,缓刑3年。尤其在sf这个灰色

    2024年02月12日
    浏览(51)
  • 中国绿色专利分地区统计数据

    一、数据简介        根据知识产权局2018年发布的《中国绿色专利统计报告(2014-2017年)》可知,当前我国的绿色创新活动活跃,绿色专利拥有量逐步提升,截至2017年底,我国绿色专利有效量达13.6万件,在专利申请方面,2014年至2017年,我国绿色专利申请量达24.9万件,年均

    2024年02月06日
    浏览(30)
  • IP可以正常访问网站、域名无法正常访问

    问题: 域名解析都是都是正常的 可以ping通所指向的IP,端口也可以telnet通 但是就是无法正常访问平台,错误提示403 解决方案: 将tomcat里面的server配置文件中的defaultHost这个配置属性修改成网站访问的公网IP地址; 修改好了之后重启使用tomcat相应的程序即可

    2024年02月16日
    浏览(34)
  • 《尚贤达猎头网站流量统计模块》,通过HTTP自定义模块实时获取asp.net网站访问流量,并保存到数据库

    开发了个网站流量统计模块,实时获取asp.net网站访问流量,并保存到数据库。 一、功能: 通过HTTP自定义模块实时获取网站流量 二、支持平台:windows+IIS 三、安装方法: 1、将文件www.sunsharer.cn.dll复制到网站bin目录下; 2、将配置好的sqlstr.txt复制到网站bin目录下; 3、将数据

    2024年01月16日
    浏览(33)
  • 宝塔面板配置使用ip访问网站

    要配置宝塔面板可以使用IP地址访问,请按照以下步骤操作: 登录宝塔面板并选择您要配置的站点。 在站点设置页面中,找到“域名管理”选项卡,将默认域名删除,替换为服务器的IP地址。 单击“添加域名”按钮以添加新的绑定IP。 在“Apache”或“Nginx”选项卡下,找到“

    2024年02月11日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包