<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>随笔 on 孤筝の温暖小家</title><link>https://www.guzhengsvt.cn/zh-cn/tags/%E9%9A%8F%E7%AC%94/</link><description>Recent content from 孤筝の温暖小家</description><generator>Hugo</generator><language>zh-CN</language><managingEditor>lvbowen040427@163.com (孤筝)</managingEditor><webMaster>lvbowen040427@163.com (孤筝)</webMaster><copyright>本博客所有文章除特别声明外，均采用 BY-NC-SA 许可协议。转载请注明出处！</copyright><lastBuildDate>Tue, 18 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://www.guzhengsvt.cn/zh-cn/tags/%E9%9A%8F%E7%AC%94/index.xml" rel="self" type="application/rss+xml"/><item><title>LeanCloud要停了，我把Waline评论搬到了MongoDB Atlas</title><link>https://www.guzhengsvt.cn/zh-cn/post/code/waline-leancloud-to-mongodb-atlas/</link><pubDate>Tue, 18 Aug 2026 00:00:00 +0800</pubDate><author>lvbowen040427@163.com (孤筝)</author><guid>https://www.guzhengsvt.cn/zh-cn/post/code/waline-leancloud-to-mongodb-atlas/</guid><description>
<![CDATA[<h1>LeanCloud要停了，我把Waline评论搬到了MongoDB Atlas</h1><p>作者：孤筝（lvbowen040427@163.com）</p>
        
          <p>我的博客评论系统是 Hugo + Waline，数据一直托管在 LeanCloud。几个月前，LeanCloud发邮件告诉我“快要停服了”，但我转头就把这事忘了。最近翻阅友链，辛未羊在评论区说waline托管平台要关了，我才想起来这码事。</p>
<p>LeanCloud、Vercel托管、Waline评论管理，只有建站的时候才配置过。有些东西平时感觉不到，真要搬起来还挺麻烦。</p>
<h2 id="起因">
<a class="header-anchor" href="#%e8%b5%b7%e5%9b%a0"></a>
起因
</h2><p>Waline 本身只是一个评论服务端，存储后端其实可以换。LeanCloud 只是其中一个选项。</p>
<p>官方支持的后端大概有：</p>
<ul>
<li>MongoDB</li>
<li>MySQL / TiDB</li>
<li>PostgreSQL</li>
<li>SQLite</li>
<li>腾讯云 CloudBase</li>
<li>GitHub</li>
</ul>
<p>一开始我也纠结过要不要趁机换个评论系统，比如 Giscus、Twikoo、Artalk 什么的。但想了想，换评论系统意味着历史评论、回复关系、用户数据都要重新处理，迁移成本太高了。而且我只需要把数据从 LeanCloud 挪走，没必要连 Waline 一起换掉。</p>
<p>所以最后决定：<strong>继续用 Waline，只换数据库。</strong></p>
<h2 id="为什么选-mongodb">
<a class="header-anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e9%80%89-mongodb"></a>
为什么选 MongoDB
</h2><p>MongoDB 最大的优势是：<strong>LeanCloud 的 BSON 备份可以直接恢复进 MongoDB。</strong></p>
<p>也就是说，不用把评论一条条转成 SQL，不用重新设计表结构，<code>Comment</code>、<code>Users</code>、<code>Counter</code> 这些数据基本能原样搬过去。</p>
<p>而如果选 MySQL/PostgreSQL，虽然 Waline 官方提供了转换工具，但主要处理 <code>Comment</code>，<code>Users</code>、<code>Counter</code>、<code>Tag</code> 这些附加数据往往还得自己写脚本。</p>
<p>所以我的选择很简单：</p>
<blockquote>
<p>哪个最省事、最不容易丢数据，就选哪个。</p>
</blockquote>
<h2 id="导出数据">
<a class="header-anchor" href="#%e5%af%bc%e5%87%ba%e6%95%b0%e6%8d%ae"></a>
导出数据
</h2><p>LeanCloud 控制台里可以导出两种东西：</p>
<ul>
<li>JSON 格式：按 Class 导出，适合转换后导入 SQL 系数据库</li>
<li>BSON 格式：数据库备份，适合直接导入 MongoDB</li>
</ul>
<p>我一开始导了 JSONL，后来发现还能导 BSON，果断又导了一份 BSON。官方说BSON导出更快，但实际上官网下载有问题，等半天还显示导出中，邮件发来的倒是可以直接下载了。</p>
<p>最后拿到手的文件是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Comment.bson.gz
</span></span><span class="line"><span class="cl">Users.bson.gz
</span></span><span class="line"><span class="cl">Counter.bson.gz
</span></span></code></pre></div><p>评论 139 条，访问量 351 条，用户 7 个。不多，但也不少。</p>
<h2 id="导入过程并没有想象中顺利">
<a class="header-anchor" href="#%e5%af%bc%e5%85%a5%e8%bf%87%e7%a8%8b%e5%b9%b6%e6%b2%a1%e6%9c%89%e6%83%b3%e8%b1%a1%e4%b8%ad%e9%a1%ba%e5%88%a9"></a>
导入过程：并没有想象中顺利
</h2><p>我本来以为 BSON 导出来之后，直接 <code>mongorestore</code> 就完事了。</p>
<p>结果发现一个坑：</p>
<p><strong>Comment 的 <code>_id</code> 不全是 ObjectId。</strong></p>
<p>有一部分是 <code>&quot;187&quot;</code>、<code>&quot;186&quot;</code> 这种普通字符串，另一部分才是 MongoDB 的 ObjectId。而评论之间的 <code>pid</code>、<code>rid</code> 又互相引用这些旧 ID。</p>
<p>如果直接导入，Waline 按 <code>objectId</code> 查评论时，字符串 ID 不是合法 ObjectId，回复层级就会坏掉。</p>
<p>所以最后写了个小脚本：</p>
<ol>
<li>给旧的字符串 ID 生成新的 ObjectId；</li>
<li>建立旧 ID → 新 ID 的映射；</li>
<li>同步改写每条评论的 <code>pid</code>、<code>rid</code>；</li>
<li>再导入 MongoDB。</li>
</ol>
<p>导入完成后还检查了一遍：</p>
<ul>
<li>所有 <code>pid</code> / <code>rid</code> 都能找到对应评论；</li>
<li>所有 <code>user_id</code> 都能找到对应用户；</li>
<li>评论、用户、访问量数量都一致。</li>
</ul>
<h2 id="vercel-的连环坑">
<a class="header-anchor" href="#vercel-%e7%9a%84%e8%bf%9e%e7%8e%af%e5%9d%91"></a>
Vercel 的连环坑
</h2><h3 id="第一个坑err_require_esm">
<a class="header-anchor" href="#%e7%ac%ac%e4%b8%80%e4%b8%aa%e5%9d%91err_require_esm"></a>
第一个坑：ERR_REQUIRE_ESM
</h3><p>部署到 Vercel 后，一访问就 500：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Error [ERR_REQUIRE_ESM]: require() of ES Module
</span></span><span class="line"><span class="cl">/var/task/node_modules/@exodus/bytes/encoding-lite.js
</span></span></code></pre></div><p>原因是 Waline 依赖的 <code>jsdom@29</code> 底层用了 ESM-only 的 <code>@exodus/bytes</code>，而 Vercel 的 Node 运行时不支持直接 <code>require()</code> 它。</p>
<p>一开始我以为升级 Node.js 版本到 24.x 就行，结果没用。</p>
<p>只覆盖 <code>html-encoding-sniffer</code> 也不行，因为 <code>whatwg-url</code> 也 require 了同一个 ESM 包。</p>
<p>最后最有效的办法是：<strong>把 <code>jsdom</code> 降到 26.1.0</strong>，整个依赖链就回到 CommonJS 了。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;overrides&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;jsdom&#34;</span><span class="p">:</span> <span class="s2">&#34;26.1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;html-encoding-sniffer&#34;</span><span class="p">:</span> <span class="s2">&#34;4.0.0&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="第二个坑mathjax-字体文件丢失">
<a class="header-anchor" href="#%e7%ac%ac%e4%ba%8c%e4%b8%aa%e5%9d%91mathjax-%e5%ad%97%e4%bd%93%e6%96%87%e4%bb%b6%e4%b8%a2%e5%a4%b1"></a>
第二个坑：MathJax 字体文件丢失
</h3><p>ESM 问题解决后，又变成：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cannot find module
</span></span><span class="line"><span class="cl">&#39;/var/task/node_modules/@mathjax/mathjax-newcm-font/mjs/chtml/default.js&#39;
</span></span></code></pre></div><p>原因是 Vercel 打包 Serverless 函数时，自动文件追踪漏掉了 MathJax 的字体文件。</p>
<p>解决方法是在 <code>vercel.json</code> 里显式把字体文件包含进去：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;config&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;includeFiles&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;node_modules/@mathjax/mathjax-newcm-font/**/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;node_modules/mhchemparser/**/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;node_modules/ip2region/data/**&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="第三个坑登录管理后台显示账号密码错误">
<a class="header-anchor" href="#%e7%ac%ac%e4%b8%89%e4%b8%aa%e5%9d%91%e7%99%bb%e5%bd%95%e7%ae%a1%e7%90%86%e5%90%8e%e5%8f%b0%e6%98%be%e7%a4%ba%e8%b4%a6%e5%8f%b7%e5%af%86%e7%a0%81%e9%94%99%e8%af%af"></a>
第三个坑：登录管理后台显示账号密码错误
</h3><p>服务终于能打开了，但登录管理后台时一直提示“账号密码错误”。</p>
<p>我一度以为迁移把密码弄坏了，甚至准备直接重置密码。</p>
<p>后来我用接口测了一下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">POST /token
</span></span></code></pre></div><p>返回值并不是密码错误，而是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="nt">&#34;errno&#34;</span><span class="p">:</span><span class="mi">500</span><span class="p">,</span><span class="nt">&#34;errmsg&#34;</span><span class="p">:</span><span class="s2">&#34;ResourceRequest timed out&#34;</span><span class="p">}</span>
</span></span></code></pre></div><p>这是 <strong>MongoDB 连接超时</strong>。</p>
<p>原因很简单：MongoDB Atlas 的白名单里只加了我本机的 IP，但 Waline 跑在 Vercel 的 Serverless 环境里，Vercel 的服务器 IP 是动态的，根本连不上数据库。</p>
<p>解决办法也简单：在 Atlas Network Access 里加上：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0.0.0.0/0
</span></span></code></pre></div><p>允许所有 IP 访问。数据库本身有用户名密码保护，个人博客这样用问题不大。</p>
<h2 id="最终结果">
<a class="header-anchor" href="#%e6%9c%80%e7%bb%88%e7%bb%93%e6%9e%9c"></a>
最终结果
</h2><p>折腾了一晚上，终于跑通了。</p>
<p>现在的架构是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Hugo 博客
</span></span><span class="line"><span class="cl">   ↓
</span></span><span class="line"><span class="cl">Waline（Vercel Serverless）
</span></span><span class="line"><span class="cl">   ↓
</span></span><span class="line"><span class="cl">MongoDB Atlas
</span></span></code></pre></div><p>历史评论、回复层级、访问量、用户数据都还在。</p>
<p>Waline 环境变量大概是这样的：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-env" data-lang="env"><span class="line"><span class="cl"><span class="nv">MONGO_DB</span><span class="o">=</span>waline
</span></span><span class="line"><span class="cl"><span class="nv">MONGO_USER</span><span class="o">=</span>xxx
</span></span><span class="line"><span class="cl"><span class="nv">MONGO_PASSWORD</span><span class="o">=</span>xxx
</span></span><span class="line"><span class="cl"><span class="nv">MONGO_HOST</span><span class="o">=[</span><span class="s2">&#34;cluster0-shard-00-00.xxx.mongodb.net&#34;</span>,<span class="s2">&#34;cluster0-shard-00-01.xxx.mongodb.net&#34;</span>,<span class="s2">&#34;cluster0-shard-00-02.xxx.mongodb.net&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">MONGO_PORT</span><span class="o">=[</span>27017,27017,27017<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">MONGO_REPLICASET</span><span class="o">=</span>atlas-xxx-shard-0
</span></span><span class="line"><span class="cl"><span class="nv">MONGO_AUTHSOURCE</span><span class="o">=</span>admin
</span></span><span class="line"><span class="cl"><span class="nv">MONGO_OPT_SSL</span><span class="o">=</span><span class="nb">true</span>
</span></span></code></pre></div><h2 id="一些感受">
<a class="header-anchor" href="#%e4%b8%80%e4%ba%9b%e6%84%9f%e5%8f%97"></a>
一些感受
</h2><p>这次迁移本身不难，难的是那些“看起来毫不相关”的连锁坑：</p>
<ul>
<li>Node 运行时版本；</li>
<li>依赖包从 CJS 变 ESM；</li>
<li>Vercel 打包漏文件；</li>
<li>数据库白名单没放行 Serverless。</li>
</ul>
<p>总结：</p>
<ol>
<li><strong>先备份，再折腾</strong>；</li>
<li><strong>能用 BSON 直导就不要手动转 CSV</strong>；</li>
<li><strong>Serverless 连数据库，第一时间检查 IP 白名单</strong>；</li>
<li><strong>遇到登录类报错，先看接口真实返回，别被前端提示误导</strong>。</li>
</ol>
<p>记录一下，下次再遇到类似迁移，至少知道该先看哪里。</p>
        
        <hr><p>本文2026-08-18首发于<a href='https://www.guzhengsvt.cn/'>孤筝の温暖小家</a>，最后修改于2026-08-18</p><p>本博客所有文章除特别声明外，均采用 BY-NC-SA 许可协议。转载请注明出处！</p>]]></description><category>Code</category></item></channel></rss>