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

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

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

step0#

下载编译:

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

部署 web 程序

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

输出

Copy
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, 也可以直接放入到网页中

Copy
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 的地方

Copy
<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存储一条记录

Copy
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,防止并发出错

Copy
@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

欢迎使用。

关注作者

欢迎关注作者微信公众号, 一起交流软件开发:欢迎关注作者微信公众号

posted @   JadePeng  阅读(620)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示
CONTENTS