博客系统重构与配置记录

寒山 5 2026-04-18

本文档记录了基于 Halo 2.24 版本的系统重构过程,主要涉及后端服务部署、静态资源分离、访问统计私有化以及前端组件的适配与调优。


一、 后端服务部署 (Halo 2.24)

1. 部署思路与注意事项

最初依赖面板工具的图形化界面部署 Docker 时,存在数据挂载路径不可控的隐患。为了确保数据的绝对安全和跨版本更新的稳定性,改为在终端通过原生 docker-compose 结合绝对路径进行部署。

在网络配置上,放弃了常规的端口映射(如 -p 8090:8090),改用 network_mode: "host"

  • 原因: 主机模式可以跳过 Docker 内部网桥的转发损耗。Halo 容器启动后直接占用宿主机的 8090 端口,这与服务器现有的 Nginx 反向代理规则(代理至 127.0.0.1:8090)完全契合,降低了网络层面的排错成本。

2. 操作步骤

  1. 在服务器创建主目录:/www/wwwroot/halo

  2. 在该目录下新建 docker-compose.yml

version: "3"
services:
  halo:
    image: halohub/halo:2.24
    container_name: halo
    restart: always
    network_mode: "host"
    volumes:
      - ./:/root/.halo2
  1. 在终端执行 cd /www/wwwroot/halo 并运行 docker compose up -d 启动服务。

二、 静态资产分离 (Cloudflare R2)

1. 部署思路与注意事项

为了降低服务器带宽压力并提高图片加载速度,将附件与图片存储剥离到 Cloudflare R2 对象存储中。

  • 注意事项: 在 R2 中生成 API Token 时,权限必须选择 Object Read & Write(对象读写),否则 Halo 后台无法上传图片。绑定的域名(如 cdn.yourdomain.com)需开启 Cloudflare 的 DNS 代理以利用其 CDN 节点。

2. 操作步骤

  1. 在 Cloudflare R2 创建存储桶,并绑定自定义域名。

  2. 在 Halo 后台安装「S3 对象存储」插件。

  3. 填入 R2 提供的 Endpoint、Access Key、Secret Key,并在绑定域名处填入 cdn.yourdomain.com(无需携带 http/https 前缀)。

三、 数据统计部署 (Umami 3.1.0)

1. 部署思路与注意事项

Umami 提供了轻量级的网站访问统计功能,且数据保留在本地服务器。

  • 注意前端代码变化: Umami V3 版本将前端探针的加载方式为 deferdefer 可以保证脚本在后台下载,并在 HTML 文档完全解析完毕后再执行,从而不阻塞页面渲染。

2. 操作步骤

  1. 创建目录 /www/wwwroot/umami

  2. 新建 docker-compose.yml,配置 Umami 与 PostgreSQL 数据库:

version: '3'
services:
  umami:
    image: ghcr.io/umami-software/umami:3.1.0
    ports:
      - "127.0.0.1:3001:3000"
    environment:
      DATABASE_URL: postgresql://umami:umami_password@db:5432/umami
      DATABASE_TYPE: postgresql
      APP_SECRET: <随机生成一段复杂的密钥>
    depends_on:
      - db
    restart: always

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: umami
      POSTGRES_USER: umami
      POSTGRES_PASSWORD: <你的数据库密码>
    volumes:
      - umami-db-data:/var/lib/postgresql/data
    restart: always

volumes:
  umami-db-data:
  1. 终端执行启动,并在宝塔中设置反向代理,登录后台获取 data-website-id

四、 前端组件调优:全局悬浮音乐播放器

这是本次重构中调整步骤最多的环节。以下是问题排查与迭代的完整路径:

1. 方案迭代过程

  • 阶段一:官方 iframe 外链。 最初使用网易云官方的 iframe 代码,但遇到了浏览器的“混合内容拦截”(HTTPS 网站禁止加载 HTTP 脚本)以及版权歌单直接禁播的问题。此外,为了让播放器只在首页显示,加入了基于路由的 JS 拦截代码,但维护成本较高。

  • 阶段二:引入开源方案。 决定改为全站全局播放(切换文章时音乐不断),改用 APlayer + MetingJS 开源方案。初期遇到了组件无法显示的现象,经排查,是因为为其添加了 display: none 导致 APlayer 初始化时无法读取容器宽高而渲染失败。

  • 阶段三:环境适配与修复。 播放器依然无法加载,通过浏览器 F12 控制台查看日志,确认了两个网络环境问题:

    1. Jsdelivr CDN 在国内网络环境下存在加载超时。

    2. MetingJS 的官方解析 API 域名(api.i-meto.com)在国内存在 DNS 解析故障。

2. 最终解决方案与原理

基于上述排查结果,对前端代码进行了以下三项关键处理:

  1. 替换静态资源节点: 将 APlayer 库的加载源替换为国内的 staticfile.net

  2. API 劫持替换: 在引入 MetingJS 脚本之前,声明 meting_api 变量,将音频解析请求强制重定向至国内稳定可用的第三方镜像源(api.injahow.cn)。

  3. CSS 样式隔离: Halo 主题自带的 text-align: center 会导致播放器内部的歌曲列表排版错乱。通过注入优先级(!important)更高的局部 CSS 代码,强制列表内容左对齐。

  4. 参数声明逻辑: 诸如 order="random"(随机播放)等控制命令,直接作为属性写在 <meting-js> 标签内部,以符合声明式语法,避免异步执行带来的参数丢失。

3. 最终代码配置

路径:Halo 后台 -> 设置 -> 代码注入 -> 页脚(Footer)

<script defer src="https://tongji.yourdomain.com/script.js" data-website-id="你的专属UUID请在后台获取"></script>

<link rel="stylesheet" href="https://cdn.staticfile.net/aplayer/1.10.1/APlayer.min.css">
<script src="https://cdn.staticfile.net/aplayer/1.10.1/APlayer.min.js"></script>

<script>
  var meting_api = 'https://api.injahow.cn/meting/?server=:server&type=:type&id=:id&pages=:page&r=:r';
</script>
<script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/Meting.min.js"></script>

<style>
  .aplayer .aplayer-list ol li { text-align: left !important; }
  .aplayer .aplayer-list ol li .aplayer-list-author { float: right !important; }
</style>

<meting-js
  server="netease"
  type="playlist"
  id="替换为你自己的网易云歌单ID"
  fixed="true"          
  autoplay="false"
  theme="#2980b9"
  loop="all"
  order="random"        
  preload="auto"
  list-folded="true">         
</meting-js>

五、 后续修改注意事项

如果后期需要更改音乐设置,请直接修改上述代码第 5 模块中的对应属性:

  • 更换歌单: 修改 id="替换为你自己的网易云歌单ID" 为新歌单编号。请注意,第三方 API 无法解析网易云系统自带的“我喜欢的音乐”歌单。且歌单中若包含 VIP 或版权受限歌曲,API 将只能抓取到 30 秒的试听版本。建议新建一个仅包含免费歌曲的公开歌单。

  • 自动播放: 修改 autoplay="true" 即可开启。但需受限于现代浏览器的防打扰策略,访客在首次打开页面时,可能仍需在页面空白处点击一次鼠标,音频才会正常输出。