實現一個簡單的類似不蒜子的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

歡迎使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM