当你看到这篇博文的时候,我的博客方案已经从 Halo 换到了 Hugo,接下来我将介绍我迁移的完整心路历程,希望对你有所帮助

关于 Halo

简介

Halo [ˈheɪloʊ],强大易用的开源建站工具。

这是一个基于 Java 的建站工具,在 2.x 版本上使用的技术栈为 Spring Boot + Netty + Thymeleaf

对于我来说,这是我当时选择它的理由,也是我现在放弃它的原因

数年前选择 Halo

我在22年10月建站的时候,主要还是参考网上的教程和建议,便基于 WordPress 搭建了我的第一个博客网站,但是由于服务器性能比较孱弱(2C4G),后台极其卡顿,仅仅更新了两篇文章后就让我忍无可忍,于是直接换上了更加现代的 Halo

当时应该是 Halo 2.x 刚刚 release,各个主题都还没来得及做适配,尤其是我当时相中的主题 Sakura,直到 22年12月才发布 beta 版本支持,同时还有很多功能无法使用,因此我还是选择了 Halo 1.x

image.png

当时跟着 Halo 的使用文档,我第一次安装了 Docker 环境,第一次搭建了 Nginx 反代,第一次使用 Docker Compose 部署了服务,是真的受益良多,同时更换上 Halo 之后,网站的前后台响应速度均有了很大的提升,同时样式也更加美观

在 Sakura 主题正式适配 Halo 2.x 后,我便将 Halo 1.x 升级到了 Halo 2.x 并沿用至今

为什么我要换方案

我必须要说,Halo 是一个非常出色的系统,它有着优秀的架构设计、出色的性能表现和丰富的拓展能力,而且它这几年的变化是翻天覆地的

但随着使用的深入和时间的推移,我愈发觉得现在的它和现在的我存在严重的分歧

痛点一:系统层面的重

如前文所说,这是一个基于 Spring Boot 的项目,同时还需要维护一个 MySQL 实例(当然也可以是 PostgreSQL),它们对于内存的占用都非常高,虽然官网描述最低需要 1G,但就我个人的使用体验来说 2G 是才是使用的门槛,这对于我孱弱的 VPS 来说有点太重了

自从我把服务器从日本(CMI 线路)换到了美国(普线),访问后台的速度和稳定性也出现了明显的下滑:打开后台要加载 1min,每个操作都要转圈等待。这已经严重影响到了我更新内容的热情

痛点二:工作流的割裂

我自己习惯写 Markdown 格式的内容,之前使用过 Typora, VSCode, OneNote, Notion 等等工具,现在完全换成 Obsidian (目前使用下来最舒服,这个有机会在聊),但是 Halo 并没有原生的 Markdown 支持,它默认仅支持富文本格式,我需要使用插件才能正常编辑,例如 plugin-stackedit 和 halo-plugin-vditor

此外同步也是很麻烦的事情,我需要把本地写好的文章复制粘贴到 Halo 后台,还需要手动处理图片,虽然有一些插件或者脚本可以代劳,但是仍然不够丝滑

变化一:统计分析工具

当时换用 Halo 而不是 Hexo 的一大因素是有后端的 Halo 自己就有统计工具,但是在后来的我还是引入了 Umami 作为我的统计工具,作为一个极其轻量的探针,它的能力比 Halo 原生的统计分析要强大很多,以至于 Halo 上还有 Umami 插件方便接入

image.png

而 Umami 本身也补足了静态博客的一大短板,此外根据统计数据,我发现我的访问来自全球各地,单一后端难免对特定地区照顾不周

变化二:图床

在之前我的图片是直接使用 Halo 的附件功能维护的,但由于服务器带宽比较小且网络不够稳定,我尝试将新增的图片使用图床进行维护,期间使用过 自建MinIO, GitHub Repo 甚至阿里云对象存储,但现在我的选择是 Cloudflare R2,它提供了很好的访问速度和慷慨的访问量,现在我的增量数据完全没有依赖 Halo 附件功能

变化三:Halo 定位

在早期使用 Halo 尤其是 Halo 1.x 时,它的定位更偏向个人建站,但是在 Halo 2.x 尤其是今年,它的定位正在向商业建站转移,对标 WordPress 和 Shopify

Halo Commercial

image.png

(虽然在这里看见我司还挺惊讶的)

关于 Hugo

谈到 Hugo 就不可避免谈到同样作为静态网站构建工具链的 Hexo

为什么没用 Hexo

基于 Node.js 开发的 Hexo 提供了更加丰富而花哨的主题,以及非常丰富的插件生态,我们之前研发组的博客/授课文档就是我使用 Hexo 搭建的,使用的主题是 Maupassant

也因此发现了一些问题以及和我需求的不匹配:

  • 但是仅仅只更新了 10 篇文章后,我发现它构建的速度慢得可怕
  • 我使用的 Sakura 主题 4 年没有维护了,不知道在新版本上能否正常使用
  • 我不需要各种花哨的插件,只需要博客的核心功能

当下的选择

Hugo 是一个使用 Golang 编写的静态站点生成 (SSG) 工具链,最大的特点就是快

The world’s fastest framework for building websites

Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.

它同时解决了我之前博客的几乎所有痛点:

  • 契合 Markdown 工作流
  • 对于 Hexo 构建速度快
  • 纯静态网站,不再需要占用大量资源,同时 CDN 友好

阻止我更换 Hugo 的核心原因

我在 25 年年中的时候就有更换 Hugo 的想法了,但是有一个核心原因阻止了我换 Hugo:主题

我使用了 3 年多的 Sakura 是我非常喜欢的主题,但是在 Hugo 上面并没有找到类似样式或者页面布局的主题,而我又不想换成别的主题样式(换上之后总觉得不是我的博客了),此外由于工作繁忙,我也没有更多的时间可以用于主题开发

因此更换博客方案的事情就一拖再拖,直到最近

迁移工作

最近时间空出来了一小段,但是 AI Codeing 工具的额度还没用完,故着手迁移工作

主题开发

说实话,我并不喜欢基于模板的开发工作(也不喜欢看 PHP 的代码),因此我所做的工作是将 WordPress 的 Sakura 主题地址交给 Cursor(Claude 4.5 Opus thinking),让它在我初始化好的 Hugo 主题上,根据我的个人偏好复刻一个

但是一开始对话的效果不尽人意,并没有参考 Sakura 的页面布局,更没有使用相关样式,不过确实是把 Hugo 主题的框架搭起来了

接下来你需要修改hugo theme @themes/Sakura ,migrate from https://github.com/mashirozx/Sakura 按照这个风格绘制hugo主题,文章图片使用metadata中的 cover.image 或者 banner
现在开始实现 Sakura 风格的主题。首先更新基础布局、创建带有 Sakura 风格文章卡片的首页、创建 Sakura 风格的主要 CSS 样式

我已经运行起来了,现在有一点小问题需要解决:

  • @themes/Sakura/content/_index.md 的文本可以删掉,换成头图、站点标题、站点副标题、不需要
  • 社交链接,参考图中样式
  • 搜索功能需要实现
  • 顶部header的链接需要使用站点根路径,现在会添加到当前相对路径后面

我来解决这三个问题

修改完成!我已经完成了以下更改:

1. 首页头图区域 (Hero Section)

  • 使用全屏背景图 + 站点标题 + 副标题

  • 默认使用 /images/banner.webp 作为背景

  • 可通过配置自定义:

# 在 hugo.yaml 的 params 中添加

params:

  hero:

    background: "/images/banner.webp"  # 背景图

    title: "This is Love98"            # 标题(默认用 site.Title)

    subtitle: "Some Simple Techs"       # 副标题

2. 搜索功能

  • 创建了 _default/search.html 搜索页面布局

  • 创建了 _default/index.json 用于生成搜索索引

  • 支持实时搜索标题、内容、标签、分类

  • 支持 Cmd/Ctrl + K 快捷键跳转搜索

3. 导航链接修复

  • 更新了 menu.html 确保菜单链接使用绝对路径

  • 自动为没有 / 前缀的 URL 添加前缀

4. 其他优化

  • Header 在首页 hero 区域透明显示,滚动后变为实色

  • 删除了主题中的示例 _index.md

  • 搜索按钮改为跳转到搜索页面

你可以刷新页面查看效果。如果需要自定义 hero 配置,在 hugo.yaml 中添加上述配置即可。

Post 卡片 tags 和 categories 需要可以点击,分类和标签都只展示第一个

完成!现在分类和标签都可以点击了,并且只展示第一个:

  • 分类 (Categories): 点击跳转到 /categories/{分类名}/

  • 标签 (Tags): 点击跳转到 /tags/{标签名}/

刷新页面即可看到效果。

(这里省略一些对于 Header 和 Footer 的修复)

接下来我通过截图+手工绘制功能分区的方式,让 Cursor 帮我修改页面布局和样式

[文章页截图] 所有页面保持样式统一,你暂时不需要关心色彩,我会在接下来的对话中和你对齐需求,现在只关心切图,参考图中 Sakura 原本的设计

明白了!我需要实现 Sakura 原版的页面结构:

  1. Header - 顶部导航栏

  2. Banner - 页面横幅(带标题)

  3. Content - 内容区域卡片

让我来实现这个统一的布局

正文部分不需要卡片,直接是背景色

完成!已移除所有内容区域的卡片样式,现在直接使用背景色:

  • 移除了 .content-card 的卡片样式

  • 移除了 .single-post 的卡片样式

  • 移除了 .archives-timeline 的卡片样式

  • 移除了 .about-page、.friends-page、.search-page 的卡片样式

  • 移除了 .section-header 的卡片样式

现在内容区域直接显示在背景上,没有额外的卡片包装。刷新页面查看效果。

[首页截图] @themes/Sakura/layouts/_partials/post-card.html 需要按照图中比例和高度切图

完成!已按照图中比例更新了 post-card 样式:

  1. 固定高度: 280px(桌面)/ 260px(平板)/ 自适应(手机)

  2. 图片占比: 50% 宽度

  3. 内容布局: 使用 justify-content: space-between 垂直分布

  4. 交替布局: 奇数卡片内容靠右对齐

  5. 摘要限制: 最多显示 3 行,超出省略

  6. 响应式: 手机端改为垂直布局

刷新页面查看效果。

接下来还让 AI 做了明暗色和主题色的支持,以及手工对齐了大量 CSS 样式

当然相比于原版主题非常丰富的功能,我做了大刀阔斧的删减,仅保留了我会使用到的部分

文章迁移

我在 Halo 上的存量文章最早的一部分是基于 Halo 默认的富文本的,后来的是基于 Markdown 的,通过 内容助手 和 文章导入导出 两个插件,我将所有文章下载下来,然后让 AI 帮我使用 Python 写了一个迁移工具,用以修改 metadata 并将图片迁移到 Cloudflare R2 的图床上

最后将修改好的文章移动到 Obsidian 下统一管理

评论系统

Hugo 原生提供了 Disqus 的支持,但由于我还有历史数据需要迁移,就没有使用这个;同时不想要使用 giscus 之类基于 GitHub Discussion 的项目,最终决定自行实现

后端

项目:comments-worker

对于项目需求和技术选型我早已有了自己的想法

我想要基于 ”Cloudflare worker + D1 数据库“ 为纯静态博客搭建一套评论系统,需要支持包括用户、评论、回复等必要模块,对于新增的评论需要有审核的功能(即需要我作为管理员审核之后才可见,用户评论后需要给我发送一封邮件通知),现在请你设计这样的一个评论系统

参考文档: https://developers.cloudflare.com/d1/
https://developers.cloudflare.com/workers/

将我的想法交给 Gemini 后并充分沟通需求后,我得到了以下的设计文档

这份文档旨在指导你从零开始构建 **comments-worker** —— 一个基于 Cloudflare 生态、高性能、零成本且支持审核功能的静态博客评论系统。

---

# 📚 comments-worker 开发文档

## 1. 项目概述
**comments-worker** 是专为静态博客设计的评论系统后端。
- **核心特性**:支持 OTP 邮件验证码与 OAuth(GitHub)登录、评论回复嵌套、管理员邮件审核提醒、完全基于 Cloudflare 边缘网络。
- **技术栈**:Cloudflare Workers, D1 Database, Hono 框架, Cloudflare Email Routing.

---

## 2. 数据库设计 (D1)

在 Cloudflare 控制台或通过 `wrangler` 执行以下 SQL 初始化 D1 数据库:

```sql
-- 用户表:存储身份信息
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT UNIQUE NOT NULL,
    display_name TEXT NOT NULL,
    avatar_url TEXT,
    auth_provider TEXT NOT NULL, -- 'otp' 或 'github'
    is_admin INTEGER DEFAULT 0,  -- 1 为管理员
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 评论表:支持层级回复
CREATE TABLE comments (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    post_slug TEXT NOT NULL,          -- 文章唯一标识
    user_id INTEGER NOT NULL,         -- 关联用户
    parent_id INTEGER DEFAULT NULL,   -- 父评论ID
    content TEXT NOT NULL,
    status TEXT DEFAULT 'pending',    -- 'pending', 'approved', 'spam'
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (parent_id) REFERENCES comments(id)
);

-- 验证码表:用于 OTP 登录
CREATE TABLE auth_codes (
    email TEXT PRIMARY KEY,
    code TEXT NOT NULL,
    expires_at INTEGER NOT NULL
);
```

---

## 3. 环境配置

### 3.1 `wrangler.toml` 配置
```toml
name = "comments-worker"
main = "src/index.ts"
compatibility_date = "2023-12-01"

# D1 数据库绑定
[[d1_databases]]
binding = "DB"
database_name = "your-d1-db-name"
database_id = "your-d1-db-id"

# 邮件发送绑定
send_email = [
    { name = "EMAIL" }
]

[vars]
DOMAIN = "yourdomain.com"
ADMIN_EMAIL = "[email protected]"
FRONTEND_URL = "https://blog.yourdomain.com"
JWT_SECRET = "your-super-secret-key"
GITHUB_CLIENT_ID = "your-github-id"
```

---

## 4. 核心功能实现

### 4.1 身份验证模块 (Auth)
系统支持两种验证方式,最终均下发 JWT Token。

- **OTP 流程**:
    1. `POST /auth/otp/send`: 生成 6 位验证码,通过 `env.EMAIL.send()` 发送至用户邮箱。
    2. `POST /auth/otp/verify`: 校验验证码,若正确则在 `users` 表创建/更新记录,并生成 JWT。
- **OAuth 流程**:
    1. `GET /auth/github/login`: 重定向至 GitHub 授权页。
    2. `GET /auth/github/callback`: 接收 `code`,换取用户信息,存入 D1 并下发 JWT。

### 4.2 评论模块 (Comments)
- **GET `/comments/:slug`**: 
    - 逻辑:查询 `status = 'approved'` 的评论。
    - 返回:建议返回扁平数组,由前端根据 `parent_id` 构建树状 UI。
- **POST `/comments`**: 
    - 逻辑:需携带 JWT Header。插入评论时 `status` 默认为 `pending`。
    - 自动化:插入成功后,调用 `env.EMAIL.send()` 向 `ADMIN_EMAIL` 发送提醒。

### 4.3 管理模块 (Admin)
- **PATCH `/admin/approve/:id`**: 
    - 权限:校验 JWT 负载中的 `email` 是否等于 `ADMIN_EMAIL` 或 `is_admin` 字段。
    - 逻辑:更新评论状态为 `approved`。

---

## 5. 邮件发送逻辑 (Email Worker)

使用 `mimetext` 库简化原始邮件构造:

```typescript
import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from "mimetext/browser";

async function sendMail(env, to, subject, body) {
  const msg = createMimeMessage();
  msg.setSender({ name: "评论系统", addr: `noreply@${env.DOMAIN}` });
  msg.setRecipient(to);
  msg.setSubject(subject);
  msg.addMessage({ contentType: 'text/html', data: body });

  const email = new EmailMessage(
    `noreply@${env.DOMAIN}`,
    to,
    msg.asRaw()
  );
  await env.EMAIL.send(email);
}
```

---

## 6. 前端集成建议

由于是静态博客,你可以编写一个轻量级的 JS SDK:

1.  **状态持久化**:将 JWT 存储在 `localStorage` 中。
2.  **UI 组件**:
    - 登录框(输入邮箱 -> 获取 OTP -> 输入 OTP)。
    - 评论列表(递归渲染回复)。
    - 评论框(支持 Markdown 预览)。
3.  **审核快捷链接**:在发给管理员的邮件中,可以直接附带一个带有 Token 的审核链接,实现“一键审核”。

---

## 7. 开发与部署步骤

### 第一步:初始化项目
```bash
npm create cloudflare@latest comments-worker -- --framework=hono
cd comments-worker
npm install mimetext
```

### 第二步:创建 D1 数据库
```bash
npx wrangler d1 create comments-db
# 根据输出的 ID 修改 wrangler.toml
npx wrangler d1 execute comments-db --file=./schema.sql
```

### 第三步:配置 GitHub OAuth
1. 在 GitHub Settings -> Developer Settings 创建新 OAuth App。
2. 设置 Callback URL 为 `https://comments-worker.yourname.workers.dev/auth/github/callback`。

### 第四步:部署
```bash
npx wrangler deploy
```

---

## 8. 安全建议
1.  **CORS 策略**:在 Hono 中使用 `cors()` 中间件,限制 `origin` 为你的博客域名。
2.  **速率限制**:针对 OTP 发送接口,利用 Cloudflare Worker 的 `Rate Limiting` 或在 D1 中记录请求频率,防止邮件被刷。
3.  **输入过滤**:在存储评论前进行简单的 HTML 转义,防止 XSS 攻击。

---

**项目维护提示**:
- 监控 D1 的读写额度(免费额度非常慷慨,通常每日 500w 次读,10w 次写)。
- 定期检查 `auth_codes` 表,可以写一个定时任务(Cron Trigger)清理过期验证码。

让 Cursor 基于此文档开发,基本一次成形

在后续的开发过程中,还额外引入了 OpenAPI 支持和 Cloudflare Turnstile 的支持,并做了一些安全相关的处理

前端

让 Agent 工具访问部署在 Cloudflare Worker 上的服务,获取 OpenAPI 文档,然后在博客主题处根据 OpenAPI 文档直接接入

尾声

每每使用 AI 实现之前构思过的需求,就会觉得自己的行动力不够,但是又真的很无力,如果真的从 0 开始纯手写这些东西,并不是 10 天半个月能搞定的事情,而这些所有,在 AI 帮助下只用了 3 天不到(Cursor 额度用完了)

Q.E.D.