引言:从「敢不敢动手」到「必须搞明白」

2025 年 4 月,我第一次真正接触到个人博客这一概念。
彼时的我,对技术始终抱有一种谨慎甚至畏惧的态度——在没有系统学习之前,不敢轻易尝试任何「看起来复杂」的东西。

为了尽快落地博客、减少未知风险,我选择了当时最稳妥、也最常见的一套方案:

Hexo + Butterfly 主题

它易上手、部署快、几乎零成本,看起来是一个「不会出错」的选择。

但也正是从这个看似简单的起点开始,我逐渐意识到:

工具的易用性,往往会掩盖其背后的复杂性,而真正的成长,往往发生在你被折磨得最狠的时候。


一、Hexo 初体验:简单背后的「隐性成本」

1. GitHub Pages 部署的现实问题

最初,我将 Hexo 博客部署在 GitHub Pages 上。
理论上流程很清晰:

  1. 本地修改配置或文章

  2. 执行 hexo generate

  3. hexo deploy 推送到远程仓库

但现实却异常残酷。

由于 GitHub Pages 在国内访问需要「特殊网络环境」,整个部署过程充满了不确定性:

  • DNS 是否能解析,全看运气

  • SSL 证书配置耗费了大量时间

  • 即使开启科学上网,对节点质量要求极高

  • 到后期,甚至只有连接手机热点才能成功推送

hexo deploy 是否成功,完全像在开盲盒。


2. 没有后台的「纯静态」管理体验

Hexo 最大的问题并不是部署,而是它没有后台管理系统

这意味着:

  • 文章创建、发布:命令行

  • 图片、Logo 路径管理:手写配置

  • 页面路由、站点信息、菜单结构:翻配置文件

  • 页面结构与布局调整:直接改主题源码

每一次修改,几乎都要经历以下流程:

  1. 找配置项(可能在上千行文件里)

  2. 修改配置

  3. 清理缓存

  4. 重新生成静态资源

  5. 提交并推送

  6. 访问验证

如果配置错了——全部重来

当时的我并不觉得这是问题,因为:

  • 我还没有见过「真正的后台系统」

  • 甚至连 Spring Boot 是什么都不知道

现在回头看,只能说一句:

一点细糠都没吃过。


二、三次关键改造:真正理解博客「原理」的开始

尽管困难重重,我还是对 Hexo 博客做了三次比较重要的改造。
这三次修改,反而成为我技术认知跃迁的起点


1. 背景渐变:第一次真正「改源码」

Butterfly 主题默认的背景是纯白 / 纯黑,过于单调。我希望页面更有层次感,于是:

  • 为主题引入了外部 CSS

  • 实现了浅色 / 深色模式下的背景渐变

过程中遇到的问题包括:

  • 页脚被原有样式遮挡 → 使用 z-index 调整层级

  • 黑夜模式不适配 → 利用样式覆盖机制解决

最终的方案是:

  • 浅色、深色样式同时渲染

  • 根据模式切换,通过覆盖关系决定最终展示

这是我第一次意识到:

CSS 并不是「试出来的」,而是有明确规则和逻辑的。


2. 欢迎卡片:第一次理解「模板渲染」

Butterfly 提供了侧边栏卡片的扩展能力,但我想要的是:

在博客主页正文区域,新增一个固定欢迎卡片。

这意味着,我必须修改页面渲染模板

在查阅主题源码后,我逐渐理解了博客的核心原理:

渲染模板 + 内容填充

我做了几件事:

  • 新增一个模板文件

  • 在模板中调用 IP、天气接口

  • 根据访问者 IP、时间、天气动态输出欢迎信息

  • 将该模板引入文章列表页模板中,并固定位置

那一刻我突然意识到:

只要你理解了模板结构,理论上你可以在任何位置插入任何功能。


3. 留言板:前后端分离的第一次实战

一个博客,怎么能没有留言系统?

Hexo 官方方案是使用第三方评论插件,但:

  • 与博客本体割裂

  • 与主题耦合度高

  • 可定制性有限

我选择了另一条路:

利用 Hexo 支持 Markdown 中渲染 HTML 的能力
md 文件中直接嵌入前端页面

此时已经是 2025 年 7 月,我完成了 Spring Boot 的基础学习,于是顺理成章地:

  • 前端:原生 HTML / CSS / JS(嵌入 md)

  • 后端:Spring Boot

后端功能包括:

  • JWT 鉴权

  • 基础 CRUD

  • 权限校验

  • 流量控制

  • 异常处理

  • 日志输出

后台管理系统使用 Vue 编写。

这个留言系统稳定运行了两三个月,直到服务器性能成为瓶颈


三、从「折腾工具」到「选择框架」

Hexo 最大的问题始终是:没有后台

我尝试过一些新技术栈的开源博客项目,但它们普遍存在问题:

  • 生态不成熟

  • 维护频率低

  • 多为个人开发,稳定性存疑

我甚至一度产生了:

「自己写一个博客系统」的想法

但很快否定了:

这是在重复造轮子,成本极高,收益极低。

经过调研后,我选择了 Halo


四、Halo:第一次「吃到细糠」

Halo 带来的体验几乎是降维打击

  • 完整后台管理系统

  • 活跃社区与插件生态

  • 专业团队维护

  • Docker 一键部署

部署流程极其简单:

  1. 拉取 Docker 镜像

  2. 使用 Docker Compose 组建

  3. 修改版本号即可升级

两天时间:

  • 第一天理解整体逻辑

  • 第二天完成博客迁移

文章、相册、资源几乎可以一键导入


五、图库系统与 S3:一次「真实工程级」踩坑

在 Halo 稳定运行之后,我开始着手解决图库管理的问题。

通过阅读 Halo 官方文档以及部分开发者博客,我了解到一个成熟、独立的图库管理系统 —— ChronoFrame

它并不与博客本体强耦合,而是作为一个独立服务存在,通过页面嵌入或跳转的方式进行访问,这种设计本身就非常符合工程直觉。

ChronoFrame 的部署过程几乎没有任何阻碍:

  • Docker 镜像拉取

  • 基础配置文件填写

  • 一键启动

真正的挑战,出现在 对象存储(S3)配置阶段


1. 为什么一定要用对象存储?

在图库系统中,图片数量天然是不可控的:

  • 照片体积大

  • 访问频率高

  • 下行流量消耗极快

如果全部使用服务器本地存储并通过服务器带宽直出:

  • 带宽成本不可控

  • 高峰期极易成为瓶颈

  • 一旦迁移或重装,数据风险极高

因此:

将图片资源交给专业的对象存储服务,几乎是唯一理性的选择。

我最终选择的是 腾讯云 COS(S3 兼容协议)


2. 问题现象:一半成功,一半失败

配置完成后测试,结果非常诡异:

  • ✅ PUT:上传图片 正常

  • ✅ DELETE:删除图片 正常

  • ❌ GET:获取图片 失败

  • ❌ POST:部分请求 失败

浏览器控制台统一报错为:

CORS 跨域问题

这一步,几乎是我被卡住时间最长的地方。


3. 第一轮误判:把问题全部归因于 CORS

看到跨域报错,我的第一反应非常「新手」:

一定是跨域规则没配好。

于是反复检查:

  • COS 跨域规则

  • 方法是否包含 GET / PUT / POST / DELETE

  • 是否允许全部来源

  • 是否允许全部 Headers

但问题在于:

PUT 和 DELETE 明明是成功的。

如果跨域规则真的有问题,它不可能只对一半请求生效。


4. 交叉验证失败:阿里云尝试

为了验证是否是腾讯云的问题,我转而使用 阿里云 OSS

结果更加离谱:

  • PUT 直接失败

  • 报错为请求体解析异常

这一步让我意识到:

问题大概率不在云厂商,而在请求本身。


5. 关键转折:观察请求细节,而不是报错描述

在反复失败之后,我做了一件非常关键的事情:

逐条对比成功请求与失败请求。


6. 真正的原因:S3 Endpoint 路径风格错误

(1)两种 S3 访问风格

  • Virtual Hosted–Style
    https://<bucket>.<endpoint>/<object>

  • Path–Style
    https://<endpoint>/<bucket>/<object>

腾讯云 COS 只支持 Virtual Hosted–Style

(2)PUT 与 GET 使用了不同风格

  • PUT 使用:Virtual Hosted–Style(正确)

  • GET 使用:Path–Style(错误)

浏览器手动访问时,腾讯云直接返回 XML 错误,明确提示:

当前访问路径不符合 Virtual Hosted Style。


7. 为什么它会「伪装成跨域问题」?

原因在于:

  1. 请求被直接拒绝

  2. 响应头中 没有 CORS Header

  3. 浏览器统一判定为跨域错误

这是一个「路径错误 → 响应头缺失 → 被误判为 CORS」的经典陷阱。


8. 最终解决方案:统一访问域名

在不修改 ChronoFrame 源码的前提下,最稳妥的方案是:

  • 不要让 CDN 配置为空

  • 在 CDN 字段中填写合法访问域名

可选方案:

  • https://<bucket>.<endpoint>

  • 绑定自定义域名(推荐):https://img.example.com

ChronoFrame 会优先使用该域名生成访问 URL,从而绕过错误路径拼接。


9. 最终效果与收益

  • 云端保存 原图

  • 访问时返回 压缩图

  • 下行流量成本大幅降低

当前图库:

  • 图片数量:100+

  • 原始体积:约 700MB

如果原图直出,成本会非常可怕;开启压缩后,几乎可以忽略。


结语:博客不是目的,而是放大器

回头看这一路:

  • Hexo 教会我阅读源码

  • 模板修改让我理解渲染逻辑

  • 留言系统让我第一次完整跑通前后端

  • Halo 让我真正理解「框架的价值」

博客本身不是目的,它只是一个放大器

放大你的短板
放大你的好奇心
也最终放大你的成长

这也是我写下这篇复盘的原因。