• 实现一个简单的类似不蒜子的PV统计器


    内部的放到gitlab pages的博客,需要统计PV,不蒜子不能准确统计,原因在于gitlab的host设置了strict-origin-when-cross-origin, 导致不蒜子不能正确获取referer,从而PV只能统计到网站的PV。

    为了方便统计页面的PV,这里简单的写了一个java程序,用H2作为db存储,实现类似不蒜子的后端。

    step0

    下载编译:

    git clone https://github.com/jadepeng/simplepv
    cd simplepv
    mvn package -DskipTests
    

    部署 web 程序

        java -jar simplepv-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
    

    输出

    2021-12-02 20:25:49.014  INFO 35916 --- [           main] com.jadepeng.simplepv.SimplepvApp        : The following profiles are active: prod
    2021-12-02 20:25:53.585  INFO 35916 --- [           main] c.j.simplepv.config.WebConfigurer        : Web application configuration, using profiles: prod
    2021-12-02 20:25:53.589  INFO 35916 --- [           main] c.j.simplepv.config.WebConfigurer        : Web application fully configured
    2021-12-02 20:26:02.580  INFO 35916 --- [           main] org.jboss.threads                        : JBoss Threads version 3.1.0.Final
    2021-12-02 20:26:02.697  INFO 35916 --- [           main] com.jadepeng.simplepv.SimplepvApp        : Started SimplepvApp in 15.936 seconds (JVM running for 16.79)
    2021-12-02 20:26:02.707  INFO 35916 --- [           main] com.jadepeng.simplepv.SimplepvApp        :
    ----------------------------------------------------------
    	Application 'simplepv' is running! Access URLs:
    	Local: 		http://localhost:58080/
    	External: 	http://172.1.1.12:58080/
    	Profile(s): 	[prod]
    ----------------------------------------------------------
    
    

    本程序默认使用 h2 作为存储,所以不用另外安装 mysql。

    step1

    引用 client.js, 也可以直接放入到网页中

    var bszCaller, bszTag, scriptTag, ready;
    
    var t,
      e,
      n,
      a = !1,
      c = [];
    
    // 修复Node同构代码的问题
    if (typeof document !== 'undefined') {
      (ready = function (t) {
        return (
          a || 'interactive' === document.readyState || 'complete' === document.readyState
            ? t.call(document)
            : c.push(function () {
                return t.call(this);
              }),
          this
        );
      }),
        (e = function () {
          for (var t = 0, e = c.length; t < e; t++) c[t].apply(document);
          c = [];
        }),
        (n = function () {
          a ||
            ((a = !0),
            e.call(window),
            document.removeEventListener
              ? document.removeEventListener('DOMContentLoaded', n, !1)
              : document.attachEvent &&
                (document.detachEvent('onreadystatechange', n), window == window.top && (clearInterval(t), (t = null))));
        }),
        document.addEventListener
          ? document.addEventListener('DOMContentLoaded', n, !1)
          : document.attachEvent &&
            (document.attachEvent('onreadystatechange', function () {
              /loaded|complete/.test(document.readyState) && n();
            }),
            window == window.top &&
              (t = setInterval(function () {
                try {
                  a || document.documentElement.doScroll('left');
                } catch (t) {
                  return;
                }
                n();
              }, 5)));
    }
    
    bszCaller = {
      fetch: function (t, e) {
        var n = 'SimplePVCallback' + Math.floor(1099511627776 * Math.random());
        t = t.replace('=SimplePVCallback', '=' + n);
        (scriptTag = document.createElement('SCRIPT')),
          (scriptTag.type = 'text/javascript'),
          (scriptTag.defer = !0),
          (scriptTag.src = t),
          document.getElementsByTagName('HEAD')[0].appendChild(scriptTag);
        window[n] = this.evalCall(e);
      },
      evalCall: function (e) {
        return function (t) {
          ready(function () {
            try {
              e(t),
                scriptTag && scriptTag.parentElement && scriptTag.parentElement.removeChild && scriptTag.parentElement.removeChild(scriptTag);
            } catch (t) {
              console.log(t), bszTag.hides();
            }
          });
        };
      },
    };
    
    const fetch = siteUrl => {
      bszTag && bszTag.hides();
      bszCaller.fetch(`${siteUrl}/api/pv/${window.btoa(location.href)}?jsonpCallback=SimplePVCallback`, function (t) {
        bszTag.texts(t), bszTag.shows();
      });
    };
    
    bszTag = {
      bszs: ['site_pv', 'page_pv'],
      texts: function (n) {
        this.bszs.map(function (t) {
          var e = document.getElementById('busuanzi_value_' + t);
          e && (e.innerHTML = n[t]);
        });
      },
      hides: function () {
        this.bszs.map(function (t) {
          var e = document.getElementById('busuanzi_container_' + t);
          e && (e.style.display = 'none');
        });
      },
      shows: function () {
        this.bszs.map(function (t) {
          var e = document.getElementById('busuanzi_container_' + t);
          e && (e.style.display = 'inline');
        });
      },
    };
    
    if (typeof document !== 'undefined') {
      fetch('http://localhost:8080/');
    }
    

    上面 fetch 的地址,填写 webserver 部署后的地址。

    step2

    在需要显示 pv 的地方

    <span id="busuanzi_container_site_pv">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
    <span id="busuanzi_container_page_pv">本文总阅读量<span id="busuanzi_value_page_pv"></span>次</span>
    

    原理

    当前只统计了PV,未统计uv,后续有空可以增加。

    原理,每个url存储一条记录

    public class PV implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Long id;
    
        @Column(name = "url")
        private String url;
    
        @Column(name = "pv")
        private Integer pv;
    
    }
    

    统计PV时,lock url的host,获取pv对象,如果不存在则新增,然后pv+1

    注意: 这里用了个lock,防止并发出错

    @Override
        public PVDTO increment(String url) {
            Lock lock = null;
            // 简单锁一下 
            try {
                URL uri = new URL(url);
                lock = this.lock(uri.getHost(), 30000);
                if (lock == null) {
                    throw new RuntimeException("请稍后重试");
                }
    
                PV pv = incrementPV(url);
    
                PV sitePv = incrementPV(uri.getHost());
    
                return new PVDTO(pv.getPv(), sitePv.getPv());
            } catch (MalformedURLException e) {
                throw new RuntimeException("url not support");
            } finally {
                if (lock != null) {
                    this.releaseLock(lock);
                }
            }
        }
    
        private PV incrementPV(String url) {
            PV pv = this.pVRepository.findFirstByUrl(url).orElse(new PV().url(url).pv(0));
            pv.setPv(pv.getPv() + 1);
            this.pVRepository.save(pv);
            return pv;
        }
    

    开源

    代码地址: https://github.com/jadepeng/simplepv

    欢迎使用。

  • 相关阅读:
    Java测试用例简介
    Hive使用入门
    Java中的GetOpt操作
    Tomcat的文件列表服务
    Hadoop MapReduce 初步学习总结
    hadoop集群调优-OS和文件系统部分
    02怎么学数据结构?
    01为什么学习数据结构?
    MySQL实战06の锁
    MySQL实战05の索引(下)
  • 原文地址:https://www.cnblogs.com/xiaoqi/p/simplepv.html
Copyright © 2020-2023  润新知