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

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

step0

下载编译:

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

部署 web 程序

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

输出

  1. 2021-12-02 20:25:49.014 INFO 35916 --- [ main] com.jadepeng.simplepv.SimplepvApp : The following profiles are active: prod
  2. 2021-12-02 20:25:53.585 INFO 35916 --- [ main] c.j.simplepv.config.WebConfigurer : Web application configuration, using profiles: prod
  3. 2021-12-02 20:25:53.589 INFO 35916 --- [ main] c.j.simplepv.config.WebConfigurer : Web application fully configured
  4. 2021-12-02 20:26:02.580 INFO 35916 --- [ main] org.jboss.threads : JBoss Threads version 3.1.0.Final
  5. 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)
  6. 2021-12-02 20:26:02.707 INFO 35916 --- [ main] com.jadepeng.simplepv.SimplepvApp :
  7. ----------------------------------------------------------
  8. Application 'simplepv' is running! Access URLs:
  9. Local: http://localhost:58080/
  10. External: http://172.1.1.12:58080/
  11. Profile(s): [prod]
  12. ----------------------------------------------------------

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

step1

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

  1. var bszCaller, bszTag, scriptTag, ready;
  2. var t,
  3. e,
  4. n,
  5. a = !1,
  6. c = [];
  7. // 修复Node同构代码的问题
  8. if (typeof document !== 'undefined') {
  9. (ready = function (t) {
  10. return (
  11. a || 'interactive' === document.readyState || 'complete' === document.readyState
  12. ? t.call(document)
  13. : c.push(function () {
  14. return t.call(this);
  15. }),
  16. this
  17. );
  18. }),
  19. (e = function () {
  20. for (var t = 0, e = c.length; t < e; t++) c[t].apply(document);
  21. c = [];
  22. }),
  23. (n = function () {
  24. a ||
  25. ((a = !0),
  26. e.call(window),
  27. document.removeEventListener
  28. ? document.removeEventListener('DOMContentLoaded', n, !1)
  29. : document.attachEvent &&
  30. (document.detachEvent('onreadystatechange', n), window == window.top && (clearInterval(t), (t = null))));
  31. }),
  32. document.addEventListener
  33. ? document.addEventListener('DOMContentLoaded', n, !1)
  34. : document.attachEvent &&
  35. (document.attachEvent('onreadystatechange', function () {
  36. /loaded|complete/.test(document.readyState) && n();
  37. }),
  38. window == window.top &&
  39. (t = setInterval(function () {
  40. try {
  41. a || document.documentElement.doScroll('left');
  42. } catch (t) {
  43. return;
  44. }
  45. n();
  46. }, 5)));
  47. }
  48. bszCaller = {
  49. fetch: function (t, e) {
  50. var n = 'SimplePVCallback' + Math.floor(1099511627776 * Math.random());
  51. t = t.replace('=SimplePVCallback', '=' + n);
  52. (scriptTag = document.createElement('SCRIPT')),
  53. (scriptTag.type = 'text/javascript'),
  54. (scriptTag.defer = !0),
  55. (scriptTag.src = t),
  56. document.getElementsByTagName('HEAD')[0].appendChild(scriptTag);
  57. window[n] = this.evalCall(e);
  58. },
  59. evalCall: function (e) {
  60. return function (t) {
  61. ready(function () {
  62. try {
  63. e(t),
  64. scriptTag && scriptTag.parentElement && scriptTag.parentElement.removeChild && scriptTag.parentElement.removeChild(scriptTag);
  65. } catch (t) {
  66. console.log(t), bszTag.hides();
  67. }
  68. });
  69. };
  70. },
  71. };
  72. const fetch = siteUrl => {
  73. bszTag && bszTag.hides();
  74. bszCaller.fetch(`${siteUrl}/api/pv/${window.btoa(location.href)}?jsonpCallback=SimplePVCallback`, function (t) {
  75. bszTag.texts(t), bszTag.shows();
  76. });
  77. };
  78. bszTag = {
  79. bszs: ['site_pv', 'page_pv'],
  80. texts: function (n) {
  81. this.bszs.map(function (t) {
  82. var e = document.getElementById('busuanzi_value_' + t);
  83. e && (e.innerHTML = n[t]);
  84. });
  85. },
  86. hides: function () {
  87. this.bszs.map(function (t) {
  88. var e = document.getElementById('busuanzi_container_' + t);
  89. e && (e.style.display = 'none');
  90. });
  91. },
  92. shows: function () {
  93. this.bszs.map(function (t) {
  94. var e = document.getElementById('busuanzi_container_' + t);
  95. e && (e.style.display = 'inline');
  96. });
  97. },
  98. };
  99. if (typeof document !== 'undefined') {
  100. fetch('http://localhost:8080/');
  101. }

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

step2

在需要显示 pv 的地方

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

原理

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

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

  1. public class PV implements Serializable {
  2. @Id
  3. @GeneratedValue(strategy = GenerationType.IDENTITY)
  4. @Column(name = "id")
  5. private Long id;
  6. @Column(name = "url")
  7. private String url;
  8. @Column(name = "pv")
  9. private Integer pv;
  10. }

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

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

  1. @Override
  2. public PVDTO increment(String url) {
  3. Lock lock = null;
  4. // 简单锁一下
  5. try {
  6. URL uri = new URL(url);
  7. lock = this.lock(uri.getHost(), 30000);
  8. if (lock == null) {
  9. throw new RuntimeException("请稍后重试");
  10. }
  11. PV pv = incrementPV(url);
  12. PV sitePv = incrementPV(uri.getHost());
  13. return new PVDTO(pv.getPv(), sitePv.getPv());
  14. } catch (MalformedURLException e) {
  15. throw new RuntimeException("url not support");
  16. } finally {
  17. if (lock != null) {
  18. this.releaseLock(lock);
  19. }
  20. }
  21. }
  22. private PV incrementPV(String url) {
  23. PV pv = this.pVRepository.findFirstByUrl(url).orElse(new PV().url(url).pv(0));
  24. pv.setPv(pv.getPv() + 1);
  25. this.pVRepository.save(pv);
  26. return pv;
  27. }

开源

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

欢迎使用。

实现一个简单的类似不蒜子的PV统计器的更多相关文章

  1. 一个简单的类似Vue的双向绑定

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. 手把手教你从零写一个简单的 VUE

    本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...

  3. 计算机程序的思维逻辑 (60) - 随机读写文件及其应用 - 实现一个简单的KV数据库

    57节介绍了字节流, 58节介绍了字符流,它们都是以流的方式读写文件,流的方式有几个限制: 要么读,要么写,不能同时读和写 不能随机读写,只能从头读到尾,且不能重复读,虽然通过缓冲可以实现部分重读,但 ...

  4. 在iOS中实现一个简单的画板App

    在这个随笔中,我们要为iPhone实现一个简单的画板App. 首先需要指出的是,这个demo中使用QuarzCore进行绘画,而不是OpenGL.这两个都可以实现类似的功能,区别是OpenGL更快,但 ...

  5. 使用MongoDB和JSP实现一个简单的购物车系统

    目录 1 问题描述  2 解决方案  2.1  实现功能  2.2  最终运行效果图  2.3  系统功能框架示意图  2.4  有关MongoDB简介及系统环境配置  2.5  核心功能代码讲解  ...

  6. nodejs实现一个简单的爬虫

    nodejs是js语言,实现一个爬出非常的方便. 步骤 1. 使用nodejs的request模块,获取目标页面的html代码:https://github.com/request/request 2 ...

  7. 3小时搞定一个简单的MIS系统案例Northwind,有视频、有源代码下载、有真相

    一.瞎扯框架.架构 楼主自从1998年从C语言.MASM.Foxbase开始学计算机开始接触这个行当16年以来,2001年干第一份与程序.软件.然后是各种屌的东西开始,差不多干了13年了,这13年来, ...

  8. 自己动手模拟开发一个简单的Web服务器

    开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此事要躬行”,于是我们自己模拟一个简单的W ...

  9. 【转】用C写一个简单病毒

    [摘要]在分析病毒机理的基础上,用C语言写了一个小病毒作为实例,用TURBOC2.0实现. [Abstract] This paper introduce the charateristic of t ...

随机推荐

  1. 通过Envoy实现.NET架构的网关

    什么是Gateway 在微服务体系结构中,如果每个微服务通常都会公开一组精细终结点,这种情况可能会有以下问题 如果没有 API 网关模式,客户端应用将与内部微服务相耦合. 在客户端应用中,单个页面/屏 ...

  2. 小白自制Linux开发板 九. 修改开机Logo

    许久不见啊,今天我们继续来修改我们的系统. 通过前面的几篇文章我们已经能轻松驾驭我们的开发板了,但是现在都是追求个性化的时代,我们在开发板上打上了自己的Logo,那我们是否可以改变开机启动的Logo呢 ...

  3. 数据流中的中位数 牛客网 剑指Offer

    数据流中的中位数 牛客网 剑指Offer 题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就 ...

  4. 干货分享之spring框架源码分析02-(对象创建or生命周期)

    记录并分享一下本人学习spring源码的过程,有什么问题或者补充会持续更新.欢迎大家指正! 环境: spring5.X + idea 之前分析了Spring读取xml文件的所有信息封装成beanDef ...

  5. Arraylist,LinkedList和Vector的异同

    相同: 都是List接口的常用类,List接口:存储有序,可重复的数据 差异: ArrayList: 是作为List接口中的主要实现的类:线程不安全,效率高.底层使用是Object[] element ...

  6. Matlab 中 arburg 函数的理解与实际使用方法

    1. 理解 1.1 Matlab 帮助: a = arburg(x,p)返回与输入数组x的p阶模型相对应的归一化自回归(AR)参数. 如果x是一个向量,则输出数组a是一个行向量. 如果x是矩阵,则参数 ...

  7. 安装RedHat和Centos后做的15件事情

    由于之前的Centos 7不支持无线网络连接,我尝试着将内核升级至4.8还是无效,遂决定换回RedHat 7,目前系统已经安装好,版本是Red Hat Enterprise Linux 7.3,下面是 ...

  8. kubernetes常见命令

    kubernetes命令 kubectl get pod --all-namespaces查看pod节点 kubectl delete -n service/pods/deplay 删除指定内容 ku ...

  9. MySQL基础学习——SQL对数据库进行操作、对数据库的表进行操作

    1.SQL对数据库进行操作: 创建数据库: 语法: create database 数据库名称 [character set 字符集 collate 字符集校对规则];字符集校对规则即所用字符集的数据 ...

  10. 手撸一个IOC容器

    IoC 什么是IoC? IoC是Inversion of Control(控制反转)的简称,注意它是一个技术思想.描述的是对象创建.管理的事情. 传统开发方式:比如类A依赖类B,往往会在类A里面new ...