轻量级静态网站访问统计(类似“不蒜子”,但可以私有化部署,更稳定、更安全)。后端基于 TypeScript + Express + SQLite,前端提供一行代码即用的 JS SDK。
- 站点总访问量(PV)、站点总访客数(UV)、页面阅读量(Page PV)
- 访客识别:IP + UserAgent 哈希(同时保存原始 ip / ua)
- 防刷:IP 级频控、同访客短期重复访问去重、异常流量检测
- 性能:SQLite WAL、必要索引、响应压缩、异步队列写库
- 安全:CORS 白名单、Helmet、安全响应头、输入验证、预编译SQL
server/ # TypeScript + Express + SQLite 服务
sdk/ # 轻量JS SDK,打包为 IIFE(全局 Statlite)
public/ # 可用于部署时托管 SDK 文件
- Node.js >= 18.17(推荐 18 LTS 或 20 LTS)
- 服务器 GLIBC >= 2.27(CentOS 8+、Ubuntu 18.04+、Debian 10+)
- 如系统较旧(如 CentOS 7),推荐使用 Docker 部署
# 1) 安装依赖(在仓库根目录)
npm install
# 2) 构建(可选,SDK 会在 dist 下产物)
npm run build
# 3) 启动开发服务(端口默认 8787)
npm run dev健康检查:GET http://localhost:8787/health 返回 { ok: true }。
STATLITE_DATA: 数据库存放目录(默认<cwd>/data)。STATLITE_CORS_ORIGINS: 逗号分隔的 CORS 白名单,如:https://a.com,https://b.com。为空则放通任意来源。PORT/STATLITE_PORT: 服务端口(默认8787)。也可通过--port=xxxx指定。
端口指定优先级:--port > PORT/STATLITE_PORT > 默认 8787。
示例:
# 通过环境变量指定
PORT=9000 npm run dev
STATLITE_PORT=9000 npm run dev
# 通过启动参数(dev)
npm -w @statlite/server run dev -- --port=9000
# 生产运行
npm run build
node server/dist/index.js --port=9000将 SDK(IIFE)部署到站点可访问路径(示例使用本地路径):
<script src="/statlite/index.global.js" defer data-statlite-site="your-site-id" data-statlite-endpoint="https://your-api"></script>
<!-- 在需要显示统计的位置添加标签 -->
<span data-statlite="pv" data-site="your-site-id" data-endpoint="https://your-api"></span>
<span data-statlite="uv" data-site="your-site-id" data-endpoint="https://your-api"></span>
<span data-statlite="pagepv" data-site="your-site-id" data-endpoint="https://your-api"></span>data-statlite-site: 站点唯一 ID(可用域名或自定义字符串)data-statlite-endpoint: 后端 API 根地址(如https://api.example.com)。
默认行为(可省略 data-site / data-endpoint):
data-site省略时,默认取引用 SDK 的站点域名(<script src>的 hostname)。data-endpoint省略时,默认取引用 SDK 的站点 origin +/statlite,如https://example.com/statlite。
data-statlite-site(挂在<script>上):全局默认 site。页面内统计元素未设置data-site时继承此值。data-site(挂在统计元素上):元素级覆盖 site。若设置则优先于data-statlite-site。data-statlite-endpoint(挂在<script>上):全局默认 endpoint。元素未设置data-endpoint时继承此值。data-endpoint(挂在统计元素上):元素级覆盖 endpoint。若设置则优先于data-statlite-endpoint。
优先级(高 → 低):
- 元素级:
data-site/data-endpoint - 全局级:
data-statlite-site/data-statlite-endpoint - 缺省值:脚本 src 的 hostname / origin +
/statlite
SDK 会在 DOMContentLoaded 后自动:
- 上报访问
POST /stats/track - 扫描页面中带有
data-statlite的元素并填充对应数值(GET /stats/summary)
构建后的 SDK 文件位于:sdk/dist/index.global.js;你可以复制到你的静态资源目录(如 public/statlite/index.global.js)进行托管。
POST /stats/track- Body:
{ site: string; page: string; title?: string } - 返回:
{ ok: true, site, page, title?, totalPv, totalUv, pagePv }
- Body:
GET /stats/summary?site=...&page=...- 返回:
{ ok: true, site, totalPv, totalUv, pagePv? }
- 返回:
访客识别:使用 IP + UserAgent 的 SHA-256 哈希(同时记录原始 IP/UA)。
- IP 级频控:默认每分钟 60 次(内存桶)。
- 同访客短期重复访问去重:同一访客同一页面 30 秒内不重复计数。
- 异常流量检测:在窗口内超出常规 5x 的访问视为异常,返回 429。
- SQLite
WAL、foreign_keys开启。 - 写入采用异步队列批处理事务,降低锁竞争。
- 必要索引:
(site_id),(site_id, page_path),(visitor_id, created_at)。 - 响应压缩(
compression)。
- CORS 白名单可配置;默认允许所有来源。
- Helmet + 自定义安全响应头。
- Zod 入参校验;SQL 使用预编译语句。
# 健康检查
curl -s http://localhost:8787/health
# 上报一次访问
curl -s -X POST http://localhost:8787/stats/track \
-H 'Content-Type: application/json' \
-d '{"site":"demo","page":"/hello","title":"Hello"}'
# 汇总查询(站点 + 页面)
curl -s 'http://localhost:8787/stats/summary?site=demo&page=%2Fhello'- 反向代理保留
X-Forwarded-For或X-Real-IP,以便准确识别访客 IP。 - 设置
STATLITE_DATA到持久化挂载目录。 - 仅放行受信任的前端 Origin 到
STATLITE_CORS_ORIGINS。 - 生产运行:
npm run build && node server/dist/index.js。
# 生成发布产物(在仓库根目录)
npm run release
# 生成目录:./release
# 包含:server/(已将 dist 平铺到 server 根目录,可直接 node server/index.js)、
# public(含 /statlite/index.global.js)、README.md、start.sh
# 将 release 整个目录拷贝到服务器,进入后:
# 可选:自定义数据目录与端口
export STATLITE_DATA=/var/lib/statlite
export PORT=9000 # 或 STATLITE_PORT=9000
# 启动
./start.sh说明:
start.sh会读取PORT或STATLITE_PORT,也可默认端口 8787。STATLITE_DATA未设置时,默认使用release/data目录。
- 本项目使用
better-sqlite3原生模块,需在“目标服务器”上安装生产依赖以匹配其系统与 Node 版本。 - 发布脚本默认不会在本地为
release/server安装依赖,避免将本地构建的二进制带到服务器引发invalid ELF header等错误。 - 正确做法:将
release/上传至服务器后,执行:cd /path/to/release/server npm install --omit=dev cd .. && ./start.sh --port=8787
- 如需在本地就安装(不推荐,除非目标与本地平台/Node 完全一致),可在本地运行:
RELEASE_INSTALL_DEPS=true npm run release
- 数据库文件位置:由
STATLITE_DATA指定目录中的statlite.sqlite(默认在release/data)。 - 初始化:首次运行若不存在数据库文件,会自动创建并执行迁移,无需手工建表。
- 迁移策略:使用
CREATE TABLE/INDEX IF NOT EXISTS等幂等迁移,不会覆盖或清空已存在的数据。 - 建议:
- 生产环境将
STATLITE_DATA挂载到独立持久化目录(或云盘/卷)。 - 备份时只需备份该目录即可保留全部统计数据。
- 生产环境将
仓库已提供 Dockerfile(基于 node:18-alpine)。推荐先在本地生成 release/,再构建镜像:
npm run release
docker build -t statlite:latest .
# 运行(将数据目录挂载到宿主机)
docker run -d \
-p 8787:8787 \
-e PORT=8787 \
-e STATLITE_DATA=/data \
-v /var/lib/statlite:/data \
--name statlite statlite:latest已提供 systemd/statlite.service:
# 将 release/ 拷贝到 /opt/statlite
sudo mkdir -p /opt/statlite
sudo cp -r release/* /opt/statlite/
# 安装服务单元
sudo cp systemd/statlite.service /etc/systemd/system/statlite.service
sudo systemctl daemon-reload
sudo systemctl enable --now statlite
# 查看状态
systemctl status statlite仓库提供交互式部署脚本 scripts/deploy.mjs,自动完成:构建、上传、安装依赖、启动服务。
# 首次运行会进入交互式配置
npm run deploy
# 或手动编写配置文件 deploy.config.json(参考 deploy.config.json.example)
# 再次运行时会自动使用已保存配置
npm run deploy{
"host": "192.168.1.100", // 服务器 IP/域名
"port": 22, // SSH 端口
"username": "root", // SSH 用户名
"password": "", // SSH 密码(如使用密钥则留空)
"keyFile": "~/.ssh/id_rsa", // SSH 私钥路径(如使用密码则留空)
"remoteDir": "/opt/statlite", // 远程部署目录
"runMode": "systemd", // 运行方式:systemd 或 command
"useNvm": false, // 服务器是否使用 nvm 管理 Node
"nvmNodeVersion": "18" // nvm 环境的 Node 版本
}- Server IP/hostname:服务器地址
- SSH port:SSH 端口(默认 22)
- SSH username:登录用户名
- Auth mode:认证方式(password 或 keyfile)
- Path to SSH private key:私钥文件路径(选择 keyfile 时)
- SSH password:密码(选择 password 时)
- Remote directory:部署目录(默认 /opt/statlite)
- Run mode:启动方式(systemd 或 command)
- Use nvm?:服务器是否使用 nvm(yes/no)
- nvm node version:Node 版本号(如 18、lts、node 等)
- 密码认证时,SSH/SCP 会交互式提示输入密码(无需额外工具)。
- 推荐使用 SSH 密钥认证(免密登录)以提升体验。
- systemd 模式需要服务器 sudo 权限。
- 服务器需要预装 Node.js(或 nvm)与 npm;脚本会自动在服务器上安装生产依赖。
- 配置文件
deploy.config.json已加入.gitignore,避免敏感信息泄露。 - 重要:服务器系统需满足 Node.js 18+ 的 GLIBC 要求(>= 2.27)。CentOS 7 等旧系统建议改用 Docker 部署。
MIT
