前言
给网站拓展个统计分析功能,采用免费开源的umaimi
参考一些博文写的不是很详尽(参考资料3博主遇到同样问题),官方文档写的非常简单,不了解程序(摸熟)完全看不懂,就如之前提到过的,好的程序也不一定有好的文档,全凭开发者用心程度
总体搭建过程花费1小时多
不想重复造轮子,但踩了些小坑,写篇总结文,方便以后查阅,有笔记的话,搭建估计15分钟左右就能完成
应该算是这个架构(Hexo
+Supabase
+Vercel
)最详尽的教程了吧….
另外,虽然说是用于Hexo,但其实博客程序(Hugo
、Typecho
、WordPress
)也是通用的
毕竟本质只是引入了一个 js
用于追踪,外加提供给用户一个网页用于查看
追踪的说明
追踪符合 GDPR 规定,不会收集任何个人身份信息,并对收集的所有数据进行匿名处理。
用户无法被识别,也不会跨网站被跟踪。
另外不会在跟踪代码中使用任何 cookie。
详细见:umami-faq
流程与架构
简单介绍下实现的流程和架构
实现流程包括
- 创建数据库:用于存储网站访问的数据(什么位置、什么平台、什么系统),数据全部存储于数据库
- 创建站点:创建个网页程序,用于查询数据,将数据图示化呈现给用户(游客、自己)观看
- Hexo配置:给博客注入追踪脚本,用于统计访客数据
架构
- 博客:Hexo
- 托管程序(创建站点):
Vercel
- 托管数据库(创建数据库):
Supabase
以下为托管的其他可选项,选择对应可用的即可
个人采用以上架构,如用其他需自行尝试摸索
另外数据库不要采用Heroku
,好像没有免费计划了,详见参考资料3
托管详细完整介绍也可参考官方文档
- 应用程序托管(创建站点)
- 数据库托管(创建数据库)
参考资料
- 部署独立统计分析服务Umami:写的比较详尽,但初始化数据库会踩坑,本篇在此基础上,结合参考其他文档完善
- 使用 Umami 统计个人网站访问数据:与博主采用不同架构(PlanetScale + vercel),仅作为了解使用
- 如何不花一分钱搭建 Umami 统计工具:介绍了一些坑
- 在 Vercel 部署 umami 网站统计及报错解决:介绍了2个数据库报错解决方案
- 使用 vercel+supabase 免费部署 umami_:不错的教程, 但还不够详尽
- 给Hexo静态博客添加Umami数据分析 :与博主采用不同架构(Heroku + vercle),仅作为了解使用
创建数据库
打开Supabase官网,使用Github登录,没有也可以注册一个帐号后登录
推荐使用
Github
,下面建立站点的时候也要使用到
登陆成功后,创建一个新的项目New project->personal
我这里为事后总结,所以已经有1个项目,总之就是找到New project
按钮后,点击新建一个项目
进入创建页面后,填写相应信息
Name
:项目的名称Database Password
:数据库密码(可以点击Generate a password
自动生成,输入后记住后面要用到)Region
:采用默认West US(North California)
Pricing Plan
:采用默认Free
创建数据库后,点击设置图标,找到Database
,再找到Connection string
,点击URL
将这个URL
复制下来,将其中[YOUR-PASSWORD]
改为上面输入的数据库密码,不要中括号[]
例如我的密码是123456
默认:postgresql://postgres:[YOUR-PASSWORD]@db.jxlqbxdxbujxopupqsnd.supabase.co:5432/postgres
修改后:postgresql://postgres:123456@db.jxlqbxdxbujxopupqsnd.supabase.co:5432/postgres
至此数据库建立完毕
站点
创建站点
先登录Github,点击这里,Fork Umami项目(拷贝一份到自己仓库)
或者也可以进入Umami仓库,点击右上角的Fork
,拉取一份到自己的仓库
登录Vercel,点击这里,找到上面Github Fork Umami的项目
例如个人是umami-repository
,点击Import
之后开始输环境变量
DATABASE_URL
:粘贴创建数据库步骤中获得的数据库URLHASH_SALT
:任意字符串,可以点击这里生成一串UUIDTRACKER_SCRIPT_NAME
:任意字符串,可以点击这里生成一串UUID
环境变量填写后点击Deploy
开始部署
部署需要两分钟多,正常部署成功后会有一个彩纸的喜庆页面。
由于这里个人踩坑部署失败,所以没有对应的截图,懒的新建一个部署,这里采用参考资料1博主的喜庆页面图,用于演示
这里说明部署失败后怎么重新部署,如果部署成功可忽略这里
如果部署失败,解决问题后(一般是数据库问题),就进入这个项目(程序),重新部署即可
先进入指示板,可以点击这里进入,点击部署失败的程序
找到部署选项,重新部署即可
配置站点
上面部署成功后,需要登录vercel
分配的网站,进行配置
不知道怎么看的话,点击这里,找到你创建的项目(程序),点击进去
点击图示位置的超链接进去
再点击Visit
访问即可
进入登录界面,默认用户名admin
和密码umami
为确保安全,先修改密码
点击Setting
-Profile
,再点击Change password
进行修改密码
之后,添加要统计分析的网站
点击Settings
-Websites
,再点击Add website
进行添加
输入站点名称和域名(不用加协议)
完成后,目前虽然可以访问,但是博客没有追踪代码,所以是不会数据的,所以要拿到js
代码用于博客中去统计数据
点击Edit
进行编辑
点击Tracking code
,将其中的js
代码复制记录下来,待会Hexo配置用到
至此站点配置完毕(修改密码、添加统计分析站点)
Hexo配置与测试
获取的js
可以插入到主题代码中去,也可以用注入器
个人主推后者,就如同插件一样,不想用了,直接删除文件即可
所以这里采用的是注入器的方式
注入器是Hexo 5 版本自身加入的一项新功能
所以使用的Hexo版本需要>=5
使用文档可参考:官方文档
在博客根目录下新建一个scripts
文件夹
在scripts
文件夹下面添加一个新js
文件,例如个人新建一个umami-statistical.js
将下面的代码复制粘贴进去,将xxx
替换为配置站点步骤获得的js
代码即可
hexo.extend.injector.register('head_end', xxx);
配置完毕后,Hexo
重新生成部署后,可以自己访问下自己的博客
之后在Vercel
搭建的站点点击查看,检查是否开始统计
后话
说下踩的坑,个人最开始是参考资料1进行搭建,其中数据库有个初始化(个人也认为很正常,数据库要建立对应表和列)
但其中提供的初始化代码链接已经失效
没办法,个人翻阅 umami 仓库,搜索以 .sql
结尾的文件,找到可能是初始化数据库的代码进行初始化
-- CreateExtension
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- CreateTable
CREATE TABLE "user" (
"user_id" UUID NOT NULL,
"username" VARCHAR(255) NOT NULL,
"password" VARCHAR(60) NOT NULL,
"role" VARCHAR(50) NOT NULL,
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6),
"deleted_at" TIMESTAMPTZ(6),
CONSTRAINT "user_pkey" PRIMARY KEY ("user_id")
);
-- CreateTable
CREATE TABLE "session" (
"session_id" UUID NOT NULL,
"website_id" UUID NOT NULL,
"hostname" VARCHAR(100),
"browser" VARCHAR(20),
"os" VARCHAR(20),
"device" VARCHAR(20),
"screen" VARCHAR(11),
"language" VARCHAR(35),
"country" CHAR(2),
"subdivision1" VARCHAR(20),
"subdivision2" VARCHAR(50),
"city" VARCHAR(50),
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "session_pkey" PRIMARY KEY ("session_id")
);
-- CreateTable
CREATE TABLE "website" (
"website_id" UUID NOT NULL,
"name" VARCHAR(100) NOT NULL,
"domain" VARCHAR(500),
"share_id" VARCHAR(50),
"reset_at" TIMESTAMPTZ(6),
"user_id" UUID,
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6),
"deleted_at" TIMESTAMPTZ(6),
CONSTRAINT "website_pkey" PRIMARY KEY ("website_id")
);
-- CreateTable
CREATE TABLE "website_event" (
"event_id" UUID NOT NULL,
"website_id" UUID NOT NULL,
"session_id" UUID NOT NULL,
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
"url_path" VARCHAR(500) NOT NULL,
"url_query" VARCHAR(500),
"referrer_path" VARCHAR(500),
"referrer_query" VARCHAR(500),
"referrer_domain" VARCHAR(500),
"page_title" VARCHAR(500),
"event_type" INTEGER NOT NULL DEFAULT 1,
"event_name" VARCHAR(50),
CONSTRAINT "website_event_pkey" PRIMARY KEY ("event_id")
);
-- CreateTable
CREATE TABLE "event_data" (
"event_id" UUID NOT NULL,
"website_id" UUID NOT NULL,
"website_event_id" UUID NOT NULL,
"event_key" VARCHAR(500) NOT NULL,
"event_string_value" VARCHAR(500),
"event_numeric_value" DECIMAL(19,4),
"event_date_value" TIMESTAMPTZ(6),
"event_data_type" INTEGER NOT NULL,
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "event_data_pkey" PRIMARY KEY ("event_id")
);
-- CreateTable
CREATE TABLE "team" (
"team_id" UUID NOT NULL,
"name" VARCHAR(50) NOT NULL,
"access_code" VARCHAR(50),
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6),
CONSTRAINT "team_pkey" PRIMARY KEY ("team_id")
);
-- CreateTable
CREATE TABLE "team_user" (
"team_user_id" UUID NOT NULL,
"team_id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"role" VARCHAR(50) NOT NULL,
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6),
CONSTRAINT "team_user_pkey" PRIMARY KEY ("team_user_id")
);
-- CreateTable
CREATE TABLE "team_website" (
"team_website_id" UUID NOT NULL,
"team_id" UUID NOT NULL,
"website_id" UUID NOT NULL,
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "team_website_pkey" PRIMARY KEY ("team_website_id")
);
-- CreateIndex
CREATE UNIQUE INDEX "user_user_id_key" ON "user"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "user_username_key" ON "user"("username");
-- CreateIndex
CREATE UNIQUE INDEX "session_session_id_key" ON "session"("session_id");
-- CreateIndex
CREATE INDEX "session_created_at_idx" ON "session"("created_at");
-- CreateIndex
CREATE INDEX "session_website_id_idx" ON "session"("website_id");
-- CreateIndex
CREATE UNIQUE INDEX "website_website_id_key" ON "website"("website_id");
-- CreateIndex
CREATE UNIQUE INDEX "website_share_id_key" ON "website"("share_id");
-- CreateIndex
CREATE INDEX "website_user_id_idx" ON "website"("user_id");
-- CreateIndex
CREATE INDEX "website_created_at_idx" ON "website"("created_at");
-- CreateIndex
CREATE INDEX "website_share_id_idx" ON "website"("share_id");
-- CreateIndex
CREATE INDEX "website_event_created_at_idx" ON "website_event"("created_at");
-- CreateIndex
CREATE INDEX "website_event_session_id_idx" ON "website_event"("session_id");
-- CreateIndex
CREATE INDEX "website_event_website_id_idx" ON "website_event"("website_id");
-- CreateIndex
CREATE INDEX "website_event_website_id_created_at_idx" ON "website_event"("website_id", "created_at");
-- CreateIndex
CREATE INDEX "website_event_website_id_session_id_created_at_idx" ON "website_event"("website_id", "session_id", "created_at");
-- CreateIndex
CREATE INDEX "event_data_created_at_idx" ON "event_data"("created_at");
-- CreateIndex
CREATE INDEX "event_data_website_id_idx" ON "event_data"("website_id");
-- CreateIndex
CREATE INDEX "event_data_website_event_id_idx" ON "event_data"("website_event_id");
-- CreateIndex
CREATE UNIQUE INDEX "team_team_id_key" ON "team"("team_id");
-- CreateIndex
CREATE UNIQUE INDEX "team_access_code_key" ON "team"("access_code");
-- CreateIndex
CREATE INDEX "team_access_code_idx" ON "team"("access_code");
-- CreateIndex
CREATE UNIQUE INDEX "team_user_team_user_id_key" ON "team_user"("team_user_id");
-- CreateIndex
CREATE INDEX "team_user_team_id_idx" ON "team_user"("team_id");
-- CreateIndex
CREATE INDEX "team_user_user_id_idx" ON "team_user"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "team_website_team_website_id_key" ON "team_website"("team_website_id");
-- CreateIndex
CREATE INDEX "team_website_team_id_idx" ON "team_website"("team_id");
-- CreateIndex
CREATE INDEX "team_website_website_id_idx" ON "team_website"("website_id");
-- AddSystemUser
INSERT INTO "user" (user_id, username, role, password) VALUES ('41e2b680-648e-4b09-bcd7-3e2b10c06264' , 'admin', 'admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa');
之后确实初始化成功,如图所示(前后对比),建立了对应的表
但Vercel
部署过程报错了,报错信息如下(仅截取错误部分)
$ node scripts/check-db.js
✓ DATABASE_URL is defined.
✓ Database connection successful.
✓ Database version check successful.
Error: P3005
The database schema is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline
✗ Command failed: prisma migrate deploy
Error: P3005
The database schema is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
ERROR: "check-db" exited with 1.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Error: Command "yarn run build" exited with 1
数据库链接是成功的,但数据库架构不为空,
报错P3005
,意思是数据库已经有数据(初始化导致的错误)
报错后,将上述数据库的表全部清空,再重新部署即可
所以,数据库初始化这一步骤非必须的,推测可能是版本差异问题吧