前言
ByteFisher 博客的评论系统使用 Waline(部署于 Vercel) + LeanCloud(国内版)的组合已运行一年多,积累了 19 条评论和 137 篇文章阅读计数。一切看似稳定,直到某天发现了一个 ETIMEDOUT 报错……
这篇文章记录了从发现问题、方案选型、数据库迁移到最终落地的完整过程,特别整理了迁移过程中遇到的 7 个坑,希望能给同样使用 Waline + LeanCloud 组合的博主一些参考。
一、问题的源头
1.1 突如其来的连接超时
某天更新博客后,发现控制台持续报错:
1 | WalineComment.vue:194 Get comment data failed with 500: |
表象是 Vercel(海外服务器)向 LeanCloud 国内节点(上海)发起的 TCP 连接全部超时。从国内网络实测却能正常连接:
1 | # 从国内网络测试 |
Vercel 的服务器部署在 AWS/GCP 美西区域,而 LeanCloud 上海节点的跨境网络不稳定是常见现象。之前还能用是因为旧的出口 IP 还能连通,但这终究是随时会爆的雷。
1.2 更大的定时炸弹
排查过程中,发现 LeanCloud 官方公告:
LeanCloud 将于 2027 年 1 月 12 日停止对外提供服务。
距离停服只剩不到 8 个月。即使这次勉强修好连接,2027 年 1 月还是要迁移——不如一步到位。
影响范围
| 组件 | 用途 | 影响程度 |
|---|---|---|
| Waline 服务端(Vercel) | 评论读写、阅读计数 | 🔴 核心功能全部中断 |
scripts/generators/hot-articles.js |
Hexo 构建时获取热门文章 | 🔴 侧边栏热门文章空 |
api/hot-articles.js |
Vercel Serverless 热门文章 API | 🟡 降级可用 |
二、方案选型
2.1 核心条件
| 条件 | 原因 |
|---|---|
| 国内无需代理访问 | 博主和多数读者在国内 |
| Waline 原生支持 | 不改 Waline 代码 |
| 免费额度够用 | 个人博客,数据量小 |
| 2027 年后不会停服 | 这次迁移要管很多年 |
2.2 候选方案对比
| 方案 | 国内访问 | 免费额度 | Waline 支持 | 公司背景 | 结论 |
|---|---|---|---|---|---|
| LeanCloud 国际版 | ✅ | 开发版 | ✅ 原生 | 2027 年停服 | ❌ |
| Turso (SQLite) | ⚠️ | 9GB | ❌ 不支持 | Turso (美国) | ❌ |
| MongoDB Atlas | ⚠️ 慢 | 512MB | ✅ 原生 | MongoDB Inc | 🟡 |
| TiDB Cloud | ✅ 流畅 | 5GB | ✅ 原生 | PingCAP (中国) | ✅ |
2.3 选择 TiDB Cloud 的理由
| 因素 | 评价 |
|---|---|
| PingCAP 是北京公司 | 控制台国内访问流畅,不担心被墙 |
| 5GB 免费额度 | 19 条评论 + 137 条计数十年也用不完 |
| Waline 原生支持 | 直接设置 TIDB_* 环境变量即可 |
| MySQL 兼容 | 迁移数据时 SQL 语法完全兼容,工具链丰富 |
| 长期稳定 | PingCAP 是 TiDB 的创始公司,不会突然停服 |
最终决策:Waline + TiDB Cloud(Serverless)。
三、LeanCloud 数据导出
3.1 控制台导出
在 LeanCloud 国内版控制台 → 数据存储 → 分别导出两张表:
| 表名 | 用途 | 记录数 |
|---|---|---|
Comment |
评论内容 | 19 条 |
Counter |
阅读计数 | 137 条 |
3.2 导出格式
LeanCloud 的导出格式是 tar.gz 压缩包,内含 JSONL(JSON Lines) 文件。格式特殊:
1 | // 第 1 行是元数据 |
每个 JSON 行的字段:
| 字段 | 类型 | 说明 |
|---|---|---|
objectId |
String | LeanCloud 唯一 ID(24 位) |
comment |
Text | 评论正文 |
nick / mail / link |
String | 用户信息 |
url |
String | 文章路径(原始中文) |
pid / rid |
String/null | 父子评论引用 ID |
createdAt / updatedAt |
ISO 时间 | 时间戳 |
坑 1:JSONL 不是标准 JSON 数组,不能直接用
JSON.parse()读取。需要用JSON.parse(line)逐行解析。
四、TiDB Cloud 部署
4.1 注册与建库
- 打开 tidbcloud.com → GitHub 账号登录
- 选择 Serverless Tier(免费 5GB)
- 配置参数:
| 参数 | 值 |
|---|---|
| Cluster Name | bytefisher-waline |
| 区域 | AWS Singapore (ap-southeast-1) |
| 数据库 | waline |
选 Singapore 区域的原因:离国内和 Vercel(美东)的延迟都较低。
4.2 获取连接信息
创建后拿到:
1 | HOST: gateway01.ap-southeast-1.prod.aws.tidbcloud.com |
在 TiDB Cloud 的 SQL Editor 中执行建库:
1 | CREATE DATABASE waline; |
五、数据导入 TiDB
5.1 表结构
Waline 的 TiDB 适配器使用 MySQL 兼容模式,需要创建以下表:
| 表名 | 用途 |
|---|---|
wl_Comment |
评论数据 |
wl_Counter |
阅读计数 |
wl_Users |
用户信息(含第三方 OAuth 字段) |
坑 2:
wl_Users表创建时如果缺少github、Unknown column 'github'错误。建表时一定要包含所有社交登录字段。
5.2 数据迁移
从 LeanCloud JSONL → TiDB SQL 的转换注意:
Comment 表字段映射:
| LeanCloud | TiDB | 转换逻辑 |
|---|---|---|
objectId |
id |
String → 按时间排序后分配自增整数 |
comment |
comment |
直接映射 |
pid / rid |
pid / rid |
坑 3:LeanCloud 存的是 objectId 字符串,TiDB 用自增整数,需要用 Map 重映射 |
createdAt |
createdAt |
ISO 时间戳 → TIMESTAMP |
insertedAt 对象 |
insertedAt |
{"__type":"Date","iso":"..."} 需要特殊解析 |
Counter 表字段映射:
| LeanCloud | TiDB | 说明 |
|---|---|---|
url |
url |
文章路径 |
time |
time |
阅读计数 |
5.3 导入脚本
编写 Node.js 脚本,连接 TiDB 后执行批量插入。核心逻辑:
1 | // 1. 读取 JSONL → 解析每行为 JSON 对象 |
六、Vercel 环境变量配置
这是最关键的一步——Waline 通过环境变量确定使用哪个数据库。
6.1 删除旧环境变量
1 | LEAN_ID # 已不需要 |
6.2 新增 TiDB 环境变量
| 变量名 | 值 |
|---|---|
TIDB_HOST |
TiDB 连接地址 |
TIDB_PORT |
4000 |
TIDB_USER |
用户名 |
TIDB_PASSWORD |
密码 |
TIDB_DB |
waline |
TIDB_PREFIX |
wl_ |
JWT_TOKEN |
随机字符串(管理后台登录密钥) |
坑 4:环境变量名不能写错。之前误将
TIDB_USER写成了IDB_USER(少了开头的 T),Waline 连接不上数据库,但也不报错——仅 Function Logs 中有一条No valid storage found警告。排查了很长时间才发现少了个字母。
6.3 配置验证
配置完成后,访问 https://waline.bytefisher.top/ui,登录管理后台确认:
- Comment 表显示 19+ 条旧评论
- Counter 表显示 137+ 条计数
- 新发评论能正常入库
七、迁移后的修复(踩坑合集)
数据迁移完成只是第一步,后面还有几个坑需要填平。
7.1 坑 5:计数器 URL 编码不匹配(四次反复)
这是整个迁移过程中最折磨人的问题,来回改了四次才最终解决。
问题现象:文章页阅读计数显示为 0,但 Counter 表中确实有数据。
根因:URL 编码格式不一致。
Waline 的数据流是:
1 | 博客页面 (data-path 属性) → Waline API 请求 → Waline 服务端解码 → TiDB 查询 |
每个环节都可能引入编码差异:
| 编码格式 | 示例 |
|---|---|
| 原始中文 | /2026/05/14/Hexo-博客图床迁移实战-从iili.io到Cloudflare Workers+工具链改造全记录/ |
| URL 编码 | /2026/05/14/Hexo-%E5%8D%9A%E5%AE%A2%E5%9B%BE%E5%BA%8A.../ |
| 双重编码 | %2F2026%2F05%2F14%2FHexo-%25E5%258D%259A%25E5%25AE%25A2... |
每次格式转换都可能改变路径字符,导致数据库查不到记录。
四次修复过程:
| 版本 | 数据库格式 | Waline 查询格式 | 结果 |
|---|---|---|---|
| 原始导入 | 原始中文 | 原始中文 | ❌ Waline查询时编码不匹配 |
| 第一次修复 | URL 编码 | URL 编码 | ❌ Waline解码后匹配不上 |
| 第二次修复 | 去尾斜杠 | 带尾斜杠 | ❌ Waline用带斜杠的查 |
| 最终 | 原始中文 + 尾斜杠 | 原始中文 + 尾斜杠 | ✅ 匹配 |
最终结论:Counter 表和 Comment 表的 URL 格式必须统一为原始中文 + 尾斜杠(与 Waline 服务端 decodeURI() 后的格式一致)。
另外,前端 JS 中获取计数时,需要正确的编码链:
1 | // 正确的编码链 |
7.2 坑 6:评论刷新后消失
问题:新评论发成功了(管理后台能看到),但刷新页面后不显示。
排查了一通才发现——Waline Vercel 函数实例缓存了旧查询结果,新评论写入数据库但 API 返回的是旧缓存。
修复方法:
- 直接调 API 确认正常:
/api/comment?path=/guestbook/&pageSize=20 - 强制 Vercel 冷启动重新部署(Deployments → Redeploy)
- 清除浏览器缓存/Service Worker 缓存
之后再发新评论,刷新即可正常加载。
7.3 坑 7:Waline.init 选项不被支持
问题:NexT 主题的 waline.swig 中调用了 Waline.init(),传入了几个不被 @waline/client@2 支持的选项:
1 | // ❌ 会有问题的版本 |
Waline v2 遇到不认识的选项会直接抛出错误,导致整个初始化中断——评论不显示、计数不更新。
修复方法:精简 init 配置,只保留被支持的选项:
1 | // ✅ 正常的版本 |
7.4 计数显示修复(前端 JS)
计数器 API 调用成功后,还需要确保前端 DOM 更新。在 waline.swig 中加了独立的计数获取脚本:
1 | (function() { |
八、热门文章生成改造
原有的 hot-articles.js 生成器直连 LeanCloud API 获取阅读计数:
1 | // ❌ 旧的实现(依赖 LeanCloud) |
迁移后改为在 Hexo 构建时直接查询 TiDB:
1 | // ✅ 新的实现(直连 TiDB) |
这样 hexo g 时直接从 TiDB 拉取最新阅读计数,生成 api/hot-articles.json,不依赖外部 API 的稳定性。
九、最终架构
系统拓扑
1 | 用户浏览器 (www.bytefisher.top) |
数据库表
| 表名 | 用途 | 数据量 |
|---|---|---|
wl_Comment |
评论内容、用户信息 | 21 条 |
wl_Counter |
文章阅读计数 | 133 条 |
wl_Users |
管理后台用户 | 1 条 |
Vercel 环境变量(最终版)
| 变量名 | 用途 |
|---|---|
TIDB_HOST / TIDB_PORT / TIDB_USER / TIDB_PASSWORD |
TiDB 连接 |
TIDB_DB / TIDB_PREFIX |
数据库和表前缀 |
JWT_TOKEN |
管理后台登录密钥 |
SITE_NAME / SITE_URL |
站点信息 |
十、总结
踩坑清单
| 编号 | 坑 | 耗时 | 教训 |
|---|---|---|---|
| 1 | JSONL 格式解析 | 0.5h | LeanCloud 导出的不是标准 JSON |
| 2 | wl_Users 缺 OAuth 字段 | 0.5h | 建表需包含全部字段 |
| 3 | pid/rid 引用重映射 | 1h | 自增 ID 需要重新关联 |
| 4 | TIDB_USER 写错变量名 | 1h | 少了 T 字母排查很久 |
| 5 | URL 编码不匹配(改了4次) | 4h | 记录中文 URL 要统一格式 |
| 6 | Vercel 实例缓存 | 0.5h | 冷启动重新部署即可 |
| 7 | Waline.init 不被支持的选项 | 1h | 精简配置,只留有效选项 |
迁移前后对比
| 项目 | 迁移前 | 迁移后 |
|---|---|---|
| 数据库 | LeanCloud 国内版 | TiDB Cloud(新加坡) |
| 免费额度 | 依赖 LeanCloud 开发版 | 5GB Serverless |
| 连接稳定性 | ❌ Vercel 跨境超时 | ✅ 海外→海外低延迟 |
| 停服风险 | 2027-01-12 | 无(PingCAP 持续运营) |
| 构建时的热门文章 | 直连 LeanCloud API | 直连 TiDB SQL |
| 评论前端 JS | 含不被支持的选项 | 精简配置 + 独立计数脚本 |
最终效果
- 评论功能正常(19 条旧评论 + 新评论持久化)
- 阅读计数实时更新(133 条记录)
- 管理后台可用(GitHub 登录 + 评论管理)
- 首页热门文章正常显示(构建时自动获取)
- 管理后台:
https://waline.bytefisher.top/ui
这次迁移虽然踩了不少坑,但结果是可靠的:TiDB Cloud 作为 PingCAP 的核心产品,配合 Waline 的原生支持,可以保证博客评论系统长期稳定运行。