本文主要是采用开源项目analytics_with_cloudflare

analytics_with_cloudflare服务部署

下载项目文件

git clone https://github.com/yestool/analytics_with_cloudflare

安装依赖

cd analytics_with_cloudflare
npm install -g wrangler
npm install hono

登录

跳转cloudflare网页授权

npx wrangler login

创建D1数据库:[web_analytics]

数据库名称为web_analytics,与package.json内保持一致

npx wrangler d1 create web_analytics

成功后显示:

✅ Successfully created DB web_analytics

[[d1_databases]]
binding = "DB" # available in your Worker on env.DB
database_name = "web_analytics"
database_id = "<unique-ID-for-your-database>"

配置worker和D1数据库绑定

将上个步骤返回的unique-ID-for-your-database写进wrangler.toml

name = "analytics_with_cloudflare"
main = "src/index.ts"
compatibility_date = "2024-06-14"

[[d1_databases]]
binding = "DB" # available in your Worker on env.DB
database_name = "web_analytics"
database_id = "<unique-ID-for-your-database>"

初始化D1数据库的表结构

npm run initSql

修改src/index.ts 内容

    const body = await c.req.json()
    const hostname = body.hostname
    const url_path = body.url
    const referrer = body.referrer
    const pv = body.pv
    const uv = body.uv
    // 添加以下两行
    const spv = body.spv
    const suv = body.suv 
    // 中间代码忽略
    // 修改下面代码内容
    const resData:{pv?: number, uv?: number, spv?: number, suv?: number} = {}
    // 中间代码忽略
    if (uv){
      const total = await c.env.DB.prepare('SELECT COUNT(*) AS total from (select DISTINCT visitor_ip from t_web_visitor where website_id = ? and url_path = ?) t').bind(websiteId, url_path).first('total');
      resData['uv'] = Number(total)
    }
    // 添加以下两段代码
    if (spv){
      const total = await c.env.DB.prepare('SELECT COUNT(*) AS total from t_web_visitor where website_id = ?').bind(websiteId).first('total');
      resData['spv'] = Number(total)
    }
    if (suv){
      const total = await c.env.DB.prepare('SELECT COUNT(*) AS total from (select DISTINCT visitor_ip from t_web_visitor where website_id = ?) t').bind(websiteId).first('total');
      resData['suv'] = Number(total)
    }
    // 修改完成
    

完整代码内容,可以下载index.ts
原来的代码只支撑返回单个页面的访问数据,不支持返回整个网站的访问数据,增加spvsuv两个返回值,分别是全站访问人次全站访问人数

发布

npm run deploy

成功后显示:

> [email protected] deploy
> wrangler deploy

Proxy environment variables detected. We'll use your proxy for fetch requests.
 ⛅️ wrangler 3.18.0
-------------------
Your worker has access to the following bindings:
- D1 Databases:
  - DB: web_analytics (<unique-ID-for-your-database>)
Total Upload: 50.28 KiB / gzip: 12.23 KiB
Uploaded analytics_with_cloudflare (1.29 sec)
Published analytics_with_cloudflare (4.03 sec)
  https://analytics_with_cloudflare.xxxxx.workers.dev
Current Deployment ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxx

添加自定义域名

可能cloudflare的workers.dev域名在有些区域里不能显示,可以在cloudflare配置自定义域名。
进入Workers 和 Pages->analytics_with_cloudflare(默认,改成你修改后名称)->设置->域和路由->添加,选择自定义域,输入二级域名即可,Cloudflare会自动在响应的主域名下添加一个对应二级域名的worker类型的DNS记录。

Hugo集成配置

博客目录参考如下:

bytejog
├── archetypes       # 站点文章模板
│   └── default.md
├── assets           # 需要被处理的资源 (SCSS、TypeScrpt 等)
│   └── js
├── content          # 站点文章
├── data             # 配置文件
├── hugo.yaml        # 站点配置文件
├── i18n             # 国际化文件
├── layouts          # 站点样式
│   └── partials
├── static           # 静态资源 (图片、CSS、JavaScript 等)
└── themes           # 主题

analytics_with_cloudflare前端部署修改

将analytics_with_cloudflare的front/dist目录下的index.js文件拷贝到hugo目录的assets/js目录下,修改名字为analytics.js
修改js文件的内容,以支撑前面后端服务增加返回的全站访问人次全站访问人数数据。
修改内容如下:

(function(){

  console.info(
    'welcome to WebViso/yestool,author:YesTool,author url: https://webviso.yestool.org'
  );

  setTimeout(function () {
    var addHeadStr = '<meta property="og:site_counter_author" content="yestool"></meta>'
      + '<meta property="og:site_counter_author_url" content="https://webviso.yestool.org"></meta>';
    
    if (document.head){
      document.head.innerHTML += addHeadStr;
    }
  }, 500);

  const script = document.currentScript;
  let dataBaseUrl = script.getAttribute('data-base-url');
  let dataPagePvId = script.getAttribute('data-page-pv-id');
  let dataPageUvId = script.getAttribute('data-page-uv-id');
  let dataSitePvId = script.getAttribute('data-site-pv-id');
  let dataSiteUvId = script.getAttribute('data-site-uv-id');

  const WebViso = {};
  WebViso.version = '0.0.0';
  let BASE_API_PATH = 'https://webviso.yestool.org';

  WebViso.page_pv_id = "page_pv"; 
  WebViso.page_uv_id = "page_uv";
  WebViso.site_pv_id = "site_pv"; 
  WebViso.site_uv_id = "site_uv";
  if(dataBaseUrl) {
    BASE_API_PATH = dataBaseUrl;
  }
  if(dataPagePvId) {
    WebViso.page_pv_id = dataPagePvId;
  }
  if(dataPageUvId) {
    WebViso.page_uv_id = dataPageUvId;
  }
  if(dataSitePvId) {
    WebViso.site_pv_id = dataSitePvId;
  }
  if(dataSiteUvId) {
    WebViso.site_uv_id = dataSiteUvId;
  }

  /**
   * @description: init Fetch json from api
   * @return {Object}
   */
  WebViso.init = async function () {
    const thisPage = getLocation(window.location.href);
    const pagePvEle = document.getElementById(WebViso.page_pv_id);
    const pageUvEle = document.getElementById(WebViso.page_uv_id);
    const sitePvEle = document.getElementById(WebViso.site_pv_id);
    const siteUvEle = document.getElementById(WebViso.site_uv_id);
    const queryData = {
      url: thisPage.pathname,
      hostname: thisPage.hostname,
      referrer: document.referrer
    }
    if (pagePvEle) {
      queryData.pv = true;
    }
    if (pageUvEle) {
      queryData.uv = true;
    }
    if (sitePvEle) {
      queryData.spv = true;
    }
    if (siteUvEle) {
      queryData.suv = true;
    }
    await fetchJson(`${BASE_API_PATH}/api/visit`, queryData)
      .then((res) => {
        if (res.ret != 'OK') {
          console.error('WebViso.init error', res.message);
          return;
        }
        const resData = res.data;
        if (pagePvEle) {
          pagePvEle.innerText = resData.pv;
        }
        if (pageUvEle) {
          pageUvEle.innerText = resData.uv;
        }
        if (sitePvEle) {
          sitePvEle.innerText = resData.spv;
        }
        if (siteUvEle) {
          siteUvEle.innerText = resData.suv;
        }
      })
      .catch((err) => {
        console.log("WebViso.init fetch error", err);
      });
  };

  /**
   * @description: Fetch json from api
   * @param {String} url response from url
   * @return {Object}
   */
  function fetchJson(url, data) {
    return new Promise((resolve) => {
      fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(data),
      })
      .then(res => {
        return res.json();
      })
      .then(function(data) {
        resolve(data);
      })
      .catch(err => {
        console.error(err);
      });
    });
  }

  const getLocation = function(href) {
    const l = document.createElement("a");
    l.href = href;
    return l;
  };
  
  if (typeof window !== 'undefined') {
    WebViso.init();
    window.WebViso = WebViso;
  }
})();

header扩展修改

hugo目录的layouts/partials目录下新建extend_head.html,文件内容为:

{{- /* Head custom content area start */ -}}
{{- /*     Insert any custom code (web-analytics, resources, etc.) - it will appear in the <head></head> section of every page. */ -}}
{{- /*     Can be overwritten by partial with the same name in the global layouts. */ -}}
{{- /* Head custom content area end */ -}}
{{ $analyticsJs := resources.Get "js/analytics.js" | js.Build "analytics.js" | minify | fingerprint }}
<script defer src="{{ $analyticsJs.Permalink }}" data-base-url="https://xxxxxxxxxxxxxxxxxxxxxxxxxxxx" data-page-pv-id="page_pv" data-page-uv-id="page_uv" data-site-pv-id="site_pv" data-site-uv-id="site_uv"></script>

url的内容替换为二级域名,或者worker的域名https://analytics_with_cloudflare.xxxxx.workers.dev

footer扩展修改

hugo目录的layouts/partials目录下新建extend_footer.html,文件内容为:

{{- /* Footer custom content area start */ -}}
{{- /*     Insert any custom code web-analytics, resources, etc. here */ -}}
{{- /* Footer custom content area end */ -}}
<div class="footer" style="padding-top: 0px;margin-top: -18px;">
	本站总访问量<span id="site_pv"></span></div>

加入id为page_pvpage_uvsite_pvsite_uv的标签,即可显示 访问人次(pv) 或、访问人数(uv)总访问人次(spv)总访问人数(suv)

本页访问人次:<span id="page_pv"></span>
本页访问人数:<span id="page_uv"></span>
本站总访问人次:<span id="site_pv"></span>
本站总访问人数:<span id="site_uv"></span>