这份精心整理的 【自建不蒜子(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: znet

busuanzi的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">
    &copy; <?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('&laquo; ' . _t('前一页'), _t('后一页') . ' &raquo;'); ?>
</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

标签: none

添加新评论