打造极简网站流量统计小工具

2023-02-02 共1868字 约4分钟 -次阅读

背景

博客网站搭建完后,就可以开始博客写作了。当我们认真写完并发表一篇文章后,当然也希望知道有多少人阅读过,这就需要用到网站流量统计功能。

目前实现网站流量统计功能的平台和第三方产品,可选择范围非常多。比如Google和百度都是广泛使用的成熟平台,可以提供非常全面的数据统计功能。其他的,比如不蒜子也是使用率很高的第三方产品。

在分析了各平台和产品的优缺点后,我发现自己其实想要的很简单,其实就两个要求:一是数据安全可控;二是功能简单,易于接入。基于这两方面的考虑,最终决定自己去构建一款轻量级的网站流量统计小工具。

选型思路

确定好走自研方向后,开始考虑一些技术选型方面的事项。

既然是自研,首先要考虑开发成本,其次是部署和运维成本。基于这些成本考虑,首先排除购买云主机进行独立部署的选项。那么剩下的只有提供无服务器服务的平台了。目前各大云平台几乎都提供Serverless无服务计算产品,比如AWS Lambda,Azure Functions,Cloudflare Workers等。

由于我之前一直在用Cloudflare,对它的各个产品也比较熟悉,所以比较倾向于选择Workers来实现。另外,Workers还提供Wrangler Action,可以在提交代码到GitHub代码仓库时自动触发部署,整个过程完全实现自动化,极大的节省后续的部署和运维成本。

基于以上这些优点,最终确定了Node.js + Cloudflare Workers + GitHub Action的选型组合。

功能实现

确定好技术选型后,再分析产品功能的实现思路。

整个流量统计工具包括前端JavaScript脚本和后端API服务,前端脚本命名为rain.js,后端服务则由worker.js实现。

前端实现

首先是前端JS脚本实现逻辑,流程如下:

  1. 页面加载脚本后触发执行统计函数。
  2. 在函数中,首先获取当前页面中所有样式为.page_pv的元素列表。
  3. 根据元素列表的长度进行判断并构建对应的请求报文,并请求后端API服务。
    1. 如果长度等于1,则默认是文章页的请求,站点PV加1,页面PV加1。
    2. 如果长度大于1,则默认是文章列表页的请求,站点PV加1。
    3. 除以上两种情况,则默认是其他页面的请求,站点PV加1。
  4. 根据请求响应结果,将统计数值添加到页面中。

核心实现代码如下:

//页面流量统计入口
async function count() {
    const pagePVElems = document.querySelectorAll('.page_pv');
    if(!pagePVElems) {
        return;
    }

    const reqData = buildReqData(pagePVElems);
    if(!reqData) {
        return;
    }
    const reqDataStr = JSON.stringify(reqData);

    const resData = await req(reqDataStr);
    if(!resData) {
        return;
    }
    const resDataStr = JSON.stringify(resData);

    if(resData.data && resData.data.sitePV) {
        const sitePVEl = document.querySelector('.site_pv');
        sitePVEl.innerHTML = resData.data.sitePV;
    }
    if(resData.data && resData.data.pages) {
        for(let i = 0; i < pagePVElems.length; i++) {
            pagePVElems[i].innerHTML = resData.data.pages[i].pagePV;
        }
    }
}

后端实现

然后是后端API实现逻辑,流程如下:

  1. 接收到计数请求后,获取User-AgentRefererOrigin等信息。
  2. 先计算站点PV。根据Origin从KV中获取当前站点PV数值,如果存在则加1,如果不存在则赋值1。
  3. 再计算页面PV,这里要分两种情况:
    1. 如果URL数量等于1,根据URL从KV中获取页面PV数值,如果存在则加1,如果不存在则赋值1。
    2. 如果URL数量大于1,根据URL从KV中获取页面PV数值,如果存在则不做操作,如果不存在则赋值0。
  4. 组装计算结果并返回。

核心实现代码如下:

//计数
async function count(request) {
    let code = '200';
    let message = 'OK';
    let data = {};

    const reqBody = await readReqBody(request);
    if(!reqBody) {
        return buildCountRes('400', 'request body is not exist', null);
    }
    const countType = reqBody.countType;
    const urls = reqBody.urls;
    if(!countType || !urls) {
        return buildCountRes('400', 'count type or url array is not exist', null);
    }
    if(countType === '01') {
        return buildCountRes(code, 'count type is 01, no count', null);
    }
    if(countType === '00') {
        return buildCountRes(code, 'count type is 00, no count', null);
    }

    const ua = request.headers.get('User-Agent');
    const ip = request.headers.get('CF-Connecting-IP');
    const rf = request.headers.get('Referer');
    const or = request.headers.get('Origin');

    let sitePVKeyPrefix = 'sitePV:';
    let siteUVKeyPrefix = 'siteUV:';
    let pagePVKeyPrefix = 'pagePV:';
    let pageUVKeyPrefix = 'pageUV:';

    let sitePVKey = sitePVKeyPrefix + or.normalize();
    let sitePVNum = await env.VIEWS.get(sitePVKey);
    if(sitePVNum) {
        sitePVNum = Number(sitePVNum) + 1;
    } else {
        sitePVNum = 1;
    }
    env.VIEWS.put(sitePVKey, sitePVNum.toString());
    data.sitePV = sitePVNum;

    if(countType === '10') {
        if(urls) {
            let tmpArr = [];
            for(const el of urls) {
                let pagePVKey = pagePVKeyPrefix + el.normalize();
                let pagePVNum = await env.VIEWS.get(pagePVKey);
                if(!pagePVNum) {
                    pagePVNum = 0;
                }
                let tmpObj = {
                    url: el,
                    pagePV: pagePVNum
                }
                tmpArr.push(tmpObj);
            };
            data.pages = tmpArr;
        }
    }
    if(countType === '11') {
        if(urls) {
            let tmpArr = [];
            let pagePVKey = pagePVKeyPrefix + urls[0].normalize();
            let pagePVNum = await env.VIEWS.get(pagePVKey);
            if(pagePVNum) {
                pagePVNum = Number(pagePVNum) + 1;
            } else {
                pagePVNum = 1;
            }
            env.VIEWS.put(pagePVKey, pagePVNum.toString());
            let tmpObj = {
                url: urls[0],
                pagePV: pagePVNum
            }
            tmpArr.push(tmpObj);
            data.pages = tmpArr;
        }
    }
    return buildCountRes(code, message, data);
}

以上就是前端脚本和后端服务的核心实现代码,如果想了解更多代码细节,请访问Gentle Rain了解更多。

效果展示

目前,这款轻量级网页流量统计工具已正式发布,最新版本为1.0.0。我把它名命为Gentle Rain,其实也没有特别的原因,主要是在创建Workers服务的时候,系统自动生成了这个名字。我觉得还挺不错的,那就笑纳了。

说了这么多,让我们看看下面的效果图吧。当然,你也可以直接访问我的个人博客CRUDMAN进行阅读和体验。

图1. 页面访问量统计截图

图2. 站点访问量统计截图

看到这里,如果你也想给自己的独立博客网站加上专属的网页流量统计功能,欢迎访问Gentle Rain中文介绍,这里有更详细的应用接入指南。

最后,如果你觉得这个小工具还是有点用的话,欢迎StarForkWatch一键三连。

注:当前版本为1.0.0,只实现了PV统计功能,UV统计计划下一个版本实现,敬请期待。

 

建站  
[流量统计]   [静态网站]   [Cloudflare Workers]   [Serverless]