自建不蒜子Busuanzi轻量级访客计数器
这份精心整理的 【自建不蒜子(Busuanzi)轻量级访客计数器完美部署与集成笔记】 涵盖了从架构设计、Podman 部署、Caddy 反向代理到 Typecho 博客集成的全流程,采用模块化结构,方便你日后随时查阅。
自建不蒜子(Busuanzi)访客计数器全面部署笔记
一、 系统架构设计
本方案采用跨国混合架构,兼顾了静态/国内自建博客的高速加载与海外服务器低成本自建统计的需求。
[ 读者浏览器 (国内) ]
│
├─► 1. 访问博客文章 ──► [ 国内云服务器 (Typecho/WordPress/Hexo) ]
│
└─► 2. 高速直连请求 ──► [ 谷歌云服务器 (Caddy) ] ──► [ Podman 容器 (Busuanzi + Redis) ]
- 博客服务器:位于国内,负责极速渲染博客主体内容。
- 统计服务器:海外谷歌云(1核1G),运行
xcsoft/busuanzi容器和Redis。 - 网络解析:使用 Cloudflare 解析二级域名(如
counter.yourdomain.com),必须关闭小黄云(仅限 DNS),确保国内访客直连谷歌云,实现计数数字秒弹出的极致体验,同时让 Caddy 直接获取真实 IP。
二、 服务端部署(基于 podman-compose)
新版 Go 语言编写的 xcsoft/busuanzi 极其轻量,运行时内存仅约 10MB~30MB,平时 CPU 占用几乎为 0%。
1. 创建项目目录
在谷歌云服务器上创建所需的数据持久化目录:
mkdir -p ~/busuanzi/data/redis
cd ~/busuanzi
2. 编写 docker-compose.yaml
创建并编辑配置文件:
nano docker-compose.yaml
粘贴以下完整配置,请务必根据注释修改你的自定义参数:
version: "3.8"
services:
redis:
image: "docker.io/library/redis:alpine"
container_name: bsz-redis
restart: always
volumes:
- ./data/redis:/data
# 提示:无需向公网暴露 6379 端口,仅供不蒜子容器内网互通,极度安全
bsz:
image: "docker.io/xcsoft/busuanzi:latest"
container_name: bsz-app
restart: always
ports:
- "127.0.0.1:8080:8080" # 仅允许本地本地环回地址访问,配合 Caddy 阻断公网直连漏洞
depends_on:
- redis
environment:
WEB_LOG: "true" # 开启访问日志
WEB_DEBUG: "false" # 关闭 Debug
WEB_CORS: "*" # 【核心】允许跨域,从而可供你名下的任意多个网站同时使用
BSZ_EXPIRE: 0 # 统计数据永不过期
BSZ_PATHSTYLE: "true" # 开启现代化路径匹配样式
# 【必须修改 1】:你准备在域名解析商那里绑定的不蒜子二级域名(不要带末尾斜杠)
API_SERVER: "https://counter.yourdomain.com"
# 【必须修改 2】:自定义数据签名密钥,填写任意随机安全字符串
BSZ_SECRET: "my_super_safe_secret_string_123456"
# 内部通信地址,直接指向 redis 服务名
REDIS_ADDRESS: "redis:6379"
3. 启动容器群
podman-compose up -d
注:若提示找不到命令,可用 podman compose up -d 或通过 pip 安装 podman-compose。
三、 网关反向代理配置(Caddyfile)
相较于复杂的 Nginx,Caddy 会自动申请并续期 SSL 证书,且配置文件极其精简。由于关闭了 Cloudflare 小黄云,Caddy 会直接承接读者的真实公网 IP。
打开并编辑 Caddy 配置文件(通常位于 /etc/caddy/Caddyfile):
sudo nano /etc/caddy/Caddyfile
修改为如下精简配置:
counter.yourdomain.com {
# 反向代理到 podman-compose 映射出的本地 8080 端口
# Caddy 默认会自动透传 X-Forwarded-For 和 X-Real-IP 等真实访客 IP 报头
reverse_proxy 127.0.0.1:8080
# 可选:记录访问日志以便排查问题
log {
output file /var/log/caddy/busuanzi_access.log
}
}
重启 Caddy 使其生效:
sudo systemctl restart caddy
配置如下
redis的podman-compose
services:
redis:
image: "docker.io/library/redis:alpine"
container_name: redis
volumes:
- ./redisdata:/data
networks:
- podman-network
security_opt:
- no-new-privileges:true
environment:
- TZ=Asia/Shanghai
networks:
podman-network:
external: true
name: znetbusuanzi的podman-compose
services:
bsz:
image: "docker.io/xcsoft/busuanzi:latest"
container_name: busuanzi
restart: always
environment:
WEB_LOG: "true"
WEB_DEBUG: "false"
WEB_CORS: "*"
BSZ_EXPIRE: 0
BSZ_PATHSTYLE: "true"
API_SERVER: "https://zcoun.755855.xyz"
BSZ_SECRET: "ba9e16-198-40c-84fc-531255bba9"
REDIS_ADDRESS: "redis:6379"
TZ: Asia/Shanghai
networks:
- podman-network
security_opt:
- no-new-privileges:true
networks:
podman-network:
external: true
name: znet博客的footer.php
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
</div><!-- end .row -->
</div>
</div><!-- end #body -->
<footer id="footer" role="contentinfo">
© <?php echo date('Y'); ?> <a href="<?php $this->options->siteUrl(); ?>"><?php $this->options->title(); ?></a>.
<?php _e('由 <a href="https://typecho.org">Typecho</a> 强力驱动'); ?>.
</footer><!-- end #footer -->
<?php $this->footer(); ?>
<!-- 1. 引入你自建的不蒜子核心 JS 脚本 -->
<script async src="https://zcoun.755855.xyz/js/lite"></script>
<!-- 2. 美化版的页脚显示挂件 -->
<div class="site-busuanzi-inner" style="font-size: 13px; color: #888; text-align: center; margin: 10px 0;">
<span>
<i class="fa fa-user"></i> 本站总访客数:
<span id="busuanzi_site_pv" style="font-weight: bold; color: #333;">...</span> 人
</span>
<span style="margin: 0 8px;">|</span>
<span>
<i class="fa fa-eye"></i> 本页总访问量:
<span id="busuanzi_page_pv" style="font-weight: bold; color: #333;">...</span> 次
</span>
</div>
</body>
</html>博客的header.php
<?php
/**
* Default theme for Typecho
*
* @package Typecho Replica Theme
* @author Typecho Team
* @version 1.2
* @link http://typecho.org
*/
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
$this->need('header.php');
?>
<div class="col-mb-12 col-8" id="main" role="main">
<?php if ($this->is('index')): ?>
<div style="text-align: center; margin: 15px 0; padding: 10px; background: #f9f9f9; border-radius: 4px;">
<script async src="https://zcoun.755855.xyz/js/lite"></script>
<p style="margin: 0; color: #666; font-size: 14px;">
欢迎阁下!您是第 <span id="busuanzi_site_uv" style="font-weight: bold; color: #ea6f5a;">...</span> 位读者。
</p>
</div>
<?php endif; ?>
<?php if (!($this->is('index')) && !($this->is('post'))): ?>
<h3 class="archive-title"><?php $this->archiveTitle([
'category' => _t('分类 %s 下的文章'),
'search' => _t('包含关键字 %s 的文章'),
'tag' => _t('标签 %s 下的文章'),
'author' => _t('%s 发布的文章')
], '', ''); ?></h3>
<?php endif; ?>
<?php if ($this->have()): ?>
<?php while ($this->next()): ?>
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
<?php postMeta($this); ?>
<?php if ($this->is('single')): ?>
<!-- 文章详情页:显示全文 -->
<div class="entry-content fmt" itemprop="articleBody">
<?php $this->content(_t('阅读全文')); ?>
</div>
<?php else: ?>
<!-- 列表页(首页、分类页、归档页等):显示摘要 -->
<div class="post-excerpt" itemprop="description">
<?php $this->excerpt(200, '...'); ?>
<p><a href="<?php $this->permalink(); ?>" class="more-link"><?php _e('阅读全文'); ?></a></p>
</div>
<?php endif; ?>
</article>
<?php endwhile; ?>
<?php else: ?>
<article class="post">
<h2 class="post-title"><?php _e('没有找到内容'); ?></h2>
</article>
<?php endif; ?>
<?php $this->pageNav('« ' . _t('前一页'), _t('后一页') . ' »'); ?>
</div><!-- end #main-->
<?php $this->need('sidebar.php'); ?>
<?php $this->need('footer.php'); ?>
四、 核心原理与特征释疑
1. 为什么直接访问根域名显示作者的链接?
/js/lite(核心脚本):访问该路径会正确读取你配置的API_SERVER变量,生成独属于你自建环境的纯净脚本。/(根目录首页):那是作者打包在镜像里的“产品说明书”静态 HTML,里面引用的公用域名9420.ltd是写死的展示代码,对你网站的实际统计毫无影响,属于正常现象。
2. 它支持多网站同时统计吗?
完全支持。 只要设置了 WEB_CORS: "*",这台服务器就变成了一个通用的多租户统计平台。不蒜子会自动提取前端浏览器发送请求时的实际“域名”,并在 Redis 中为不同域名开辟独立的空间,绝不混淆。
五、 前端集成与 Typecho 博客实战
新版 xcsoft/busuanzi 前端脚本经过了精简,去掉了老版冗余的 _value_ 字段。
纯净版标签命名对照表
busuanzi_site_uv:全站总独立访客数(基于 IP + 浏览器指纹去重)。busuanzi_site_pv:全站总页面浏览量(每次刷新都会递增)。busuanzi_page_uv:不清楚,估计跟uv一个意思busuanzi_page_pv:当前单篇文章/单页面的总独立阅读量。
🛠️ 实战:仅在 Typecho 博客首页标题下显示
如果你的主题结构中网站大标题在 header.php,而文章列表在 index.php,最佳实践是直接将代码包裹在 Typecho 的首页条件判断中,置于 index.php 列表的最上方容器内。
打开你的主题 index.php,在 <div class="col-mb-12 col-8" id="main" role="main"> 的紧下方插入以下代码:
<!-- 仅在博客首页渲染自建不蒜子组件 -->
<?php if ($this->is('index')): ?>
<div style="text-align: center; margin: 15px 0; padding: 10px; background: #f9f9f9; border-radius: 4px;">
<!-- 异步加载你自建的高速脚本路径 -->
<script async src="https://zcoun.755855.xyz/js/lite"></script>
<!-- 精美的前端展示,支持 CSS 样式自由微调 -->
<p style="margin: 0; color: #666; font-size: 14px;">
欢迎阁下!您是第 <span id="busuanzi_site_uv" style="font-weight: bold; color: #ea6f5a; font-size: 16px;">...</span> 位读者。
</p>
</div>
<?php endif; ?>
<!-- 统计代码结束 -->
六、 数据库高级维护(Redis 骚操作)
所有访客数据都牢牢掌控在你自己的谷歌云 Redis 容器内存中,并实时持久化在宿主机的 ~/busuanzi/data/redis/dump.rdb 文件里。
1. 连入 Redis 终端
podman exec -it redis redis-cli
2. 探索加密的域名 Key
为了隐私和安全,新版不蒜子将你的明文域名转化为了 MD5 哈希值的前 16 位。在 Redis 终端中查询:
127.0.0.1:6379> keys *site_pv*
1) "bsz:site_pv:bab7427ffab8015c"
2) "bsz:site_pv:3df2a04b3ac978f6"
提示:可退出 Redis 在服务器终端执行以下 Python 命令来肉眼识别哪串哈希对应哪个域名:
python3 -c "import hashlib; print(hashlib.md5(b'yourblog.com').hexdigest()[:16])"
3. 手动修改/注入访客数值(改大数字)
如果你需要为博客“初始化”或者人工调整一个好看的基数,可直接在 Redis 终端内使用 SET 命令:
# 查看当前真实数值
127.0.0.1:6379> GET bsz:site_pv:bab7427ffab8015c
"12"
# 强制修改该数值为 88888
127.0.0.1:6379> SET bsz:site_pv:bab7427ffab8015c 88888
OK
4. 强制生效同步
修改完 Redis 后,由于容器内存中可能有数值缓存,请在谷歌云终端执行重启命令刷新容器,并在浏览器端按下 Ctrl + F5 强制刷新,大功告成:
podman-compose restart bsz