惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
N
Netflix TechBlog - Medium
Google Online Security Blog
Google Online Security Blog
月光博客
月光博客
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
V
V2EX
腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
M
MIT News - Artificial intelligence
Vercel News
Vercel News
The GitHub Blog
The GitHub Blog
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
aimingoo的专栏
aimingoo的专栏
博客园 - 三生石上(FineUI控件)
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
MongoDB | Blog
MongoDB | Blog
H
Help Net Security
The Cloudflare Blog
Blog — PlanetScale
Blog — PlanetScale
F
Full Disclosure
G
Google Developers Blog
罗磊的独立博客
Jina AI
Jina AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
A
About on SuperTechFans
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队
GbyAI
GbyAI
雷峰网
雷峰网
T
The Blog of Author Tim Ferriss
The Register - Security
The Register - Security
U
Unit 42
D
Docker
Martin Fowler
Martin Fowler
L
LINUX DO - 热门话题
NISL@THU
NISL@THU
阮一峰的网络日志
阮一峰的网络日志
C
Cybersecurity and Infrastructure Security Agency CISA
博客园_首页
Google DeepMind News
Google DeepMind News

栖童の小站

中兴微ZX296716机顶盒TTL救砖全攻略 | 栖童の小站 闲鱼副业之行:在机顶盒救砖中,窥见人性的温差 | 栖童の小站 CMCC RAX3000QY路由器TTL刷机与OpenWrt解锁全记录 | 栖童の小站 晨星9385芯片设备免拆包自修改教程 | 栖童の小站 小众云服务商深度测评:小兔互联、初七云、星辰云对比 | 栖童の小站 我的2025:在破除幻象、划定边界与坚守内心的一年 | 栖童の小站 闲鱼求职骗局实录:我是如何识破假冒京东HR | 栖童の小站 “大仙”是如何操控你的:亲历东北出马仙骗局与背后的恐惧营销心理学 | 栖童の小站 一次网站性能翻车实录:滥用SWPP插件导致的用户体验灾难与修复 | 栖童の小站 未成年网络暴力观察:从劝诫到被“人肉”的反思 | 栖童の小站 卸任版主后的身份枷锁:虚拟社交中的友谊与边界 | 栖童の小站 Clarity主题深度定制指南 | 栖童の小站 从Hexo到Nuxt:我的小站重构与品牌升级之路 | 栖童の小站 在爱恨之间:我的人际关系修复与挣扎 | 栖童の小站 信仰的见证:当基督徒的行为违背圣经 | 栖童の小站 版主生涯的回忆:在deepin论坛的日子 | 栖童の小站 从耕种到秋收 | 栖童の小站 当田园牧歌遭遇田埂上的贪婪 | 栖童の小站 芜湖散记:江畔的温柔与遗憾 | 栖童の小站 童年的两面:简单的快乐与沉重的烙印 | 栖童の小站 家庭阴影与校园霸凌的自愈 | 栖童の小站 公共澡堂体验:记录一次北方乡下的专业搓澡 | 栖童の小站 如何打造高效的团队 | 栖童の小站 Linux系统Git使用指南:从本地仓库创建到远程仓库推送 | 栖童の小站 Hexo Butterfly主题进阶美化:添加FPS显示、节日弹窗与评论提示 | 栖童の小站 告别手动编译:利用GitHub Actions自动化部署你的Hexo博客 | 栖童の小站 Linux音频修复:解决前置耳机及麦克风插孔无声方案 | 栖童の小站 从零搭建Hexo静态博客:环境配置、主题安装到部署上线完全指南 | 栖童の小站 解决Debian包格式兼容:从zst到xz的手动转换与重打包教程 | 栖童の小站 Debian系统编译Linux内核deb包:从编译到打包安装全流程 | 栖童の小站 老爷机复活指南:Linux Mint Xfce 轻量系统安装与优化全流程 | 栖童の小站
零成本自建网站统计:在Vercel上部署Umami完全指南 | 栖童の小站
栖童, sweetcandymini@foxmail.com · 2025-04-16 · via 栖童の小站

前言

本教程以VercelAiven免费数据库为例,不止可以用Vercel托管,也可以选择Netlify等,数据库也可以选择其他免费数据库服务,详细的请观看官方文档

一、准备

注册GithubVercelAivenCloudflare(如何注册就不写了,注册非常简单)

二、创建数据库

注册好Aiven后创建 MySQL 数据库

创建数据库
创建数据库

设置MySQL数据库,数据库位置选择亚太地区

设置数据库
设置数据库

注册好Github后,Fork Umami仓库

Umami

Umami is a modern, privacy-focused alternative to Google Analytics.

打开Vercel选择Fork的仓库

选择Fork的仓库
选择Fork的仓库

复制 Service URIVercel的环境变量中添加DATABASE_URL

复制Service URI
复制Service URI

设置DATABASE_URL环境变量

可选:非必要环境变量TRACKER_SCRIPT_NAME,设置Umami跟踪器名称(可填写自己喜欢的值),避免默认名称被广告拦截器拦截导致追踪失败。

设置环境变量
设置环境变量

设置自定义域名(由于Vercel自分配域名被墙,国内访问不到)

自定义域名1
自定义域名1
自定义域名2
自定义域名2

四、设置Umami

默认用户名为 admin ,默认密码为 umami,可在登录后自行修改(推荐修改默认密码,防止他人作恶)

4.1 创建新用户

进入设置用户创建新用户并设置密码,设置权限为仅浏览量(防止后续教程默认用户的Token被人作恶)

创建新用户
创建新用户

4.2 创建团队

进入设置团队创建团队

后面是把需要统计的网站添加到团队,切换到团队-设置-网站-添加网站即可,这个比较简单,不过多描述。

创建团队
创建团队

4.3 记录访问代码

进入设置团队点击创建的团队查看详细信息

查看详细信息
查看详细信息

4.4 登陆新用户

退出登陆登陆新用户—输入新创建的用户名密码,之后进入设置团队加入团队输入访问代码

输入访问代码
输入访问代码

五、Cloudflare-Workers搭建Umami数据接口

前往 Hoppscotch获取Token

输入新创建的用户名密码

Hoppscotch1
Hoppscotch1

成功后返回Token信息

Hoppscotch2
Hoppscotch2

前往Cloudflare,创建一个Workers,设置好名称,确认部署并等待

部署完成,点击右上角编辑代码,修改worker.js的内容(部分内容需修改为自己的数据)

js
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event));
});

const API_BASE_URL = 'umami地址';
const TOKEN = '获取到的token';
const WEBSITE_ID = '网站在umami中的website ID';
const CACHE_KEY = 'umami_cache'; // 缓存
const CACHE_TIME = 600; // 缓存时间,单位秒

async function fetchUmamiData(startAt, endAt) {
  const url = `${API_BASE_URL}/api/websites/${WEBSITE_ID}/stats?startAt=${startAt}&endAt=${endAt}`;
  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${TOKEN}`,
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    console.error(`Error fetching data: ${response.statusText}`);
    return null;
  }

  return response.json();
}

async function handleRequest(event) {
  const cache = await caches.open(CACHE_KEY);
  const cachedResponse = await cache.match(event.request);

  if (cachedResponse) {
    return cachedResponse;
  }

  const now = Date.now();
  const todayStart = new Date(now).setHours(0, 0, 0, 0);
  const yesterdayStart = new Date(now - 86400000).setHours(0, 0, 0, 0);
  const lastMonthStart = new Date(now).setMonth(new Date(now).getMonth() - 1);
  const lastYearStart = new Date(now).setFullYear(new Date(now).getFullYear() - 1);

  const [todayData, yesterdayData, lastMonthData, lastYearData] = await Promise.all([
    fetchUmamiData(todayStart, now),
    fetchUmamiData(yesterdayStart, todayStart),
    fetchUmamiData(lastMonthStart, now),
    fetchUmamiData(lastYearStart, now)
  ]);

  const responseData = {
    today_uv: todayData?.visitors?.value ?? null,
    today_pv: todayData?.pageviews?.value ?? null,
    yesterday_uv: yesterdayData?.visitors?.value ?? null,
    yesterday_pv: yesterdayData?.pageviews?.value ?? null,
    last_month_pv: lastMonthData?.pageviews?.value ?? null,
    last_year_pv: lastYearData?.pageviews?.value ?? null
  };

  const jsonResponse = new Response(JSON.stringify(responseData), {
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization'
    }
  });

  event.waitUntil(cache.put(event.request, jsonResponse.clone()));

  return jsonResponse;
}

完成编辑后,保存并部署

可选:绑定自定义域名。在Workers中点击设置,在域和路由项下添加自定义域名(受Cloudflare限制,域名必须已托管至Cloudflare才可绑定)

由于接入的教程十分简单,本文不再详细写明。