Cloudflare workers 云函数部署 Twikoo 评论系统

本文介绍如何使用 Cloudflare Workers 和 D1 数据库部署 Twikoo 评论系统,并集成到 Astro 静态博客中。

周五 6月 19 2026 Development
3539 字 · 25 分钟

部署

克隆

BASH
git clone git@github.com:twikoojs/twikoo-cloudflare.git

安装依赖

BASH
npm install
# 或
pnpm install

登录到 Cloudflare 账户

BASH
npx wrangler login

创建 Cloudflare D1 数据库并设置模式

BASH
npx wrangler d1 create twikoo

从命令输出中复制 database_namedatabase_id ,粘贴到 wrangler.toml 文件中,替换对应的原始值。

设置 Cloudflare D1 架构

BASH
npx wrangler r2 bucket create twikoo

创建 Cloudflare R2 存储(可选)

BASH
npx wrangler r2 bucket create twikoo

创建 R2 存储桶主要用于图片上传功能。请注意,创建 R2 存储桶需要绑定银行卡。如果你不需要图片上传,可以跳过此步骤。

若创建了 R2 存储桶,请将返回的 R2 域名更新到 wrangler.toml 文件中的 R2_PUBLIC_URL 字段。

部署 Workers

BASH
npx wrangler deploy --minify

如果一切顺利,命令行会输出类似 https://twikoo-cloudflare.<your user name>.workers.dev 的地址。

访问该地址,如果配置正确,浏览器中应显示如下 JSON 信息:

{"code":100,"message":"Twikoo 云函数运行正常,请参考 https://twikoo.js.org/frontend.html 完成前端的配置","version":"1.7.11"}

直接访问该地址可能失败,建议绑定自定义域名后使用。

前端配置

以下配置以 Astro 框架为例。

创建组件

src\components 目录下创建 TwikooComments.astro ,内容如下:

ASTRO
---
interface Props {
  envId: string;
  path?: string;
}

const { envId, path } = Astro.props;
---

<!-- 提前加载 Twikoo 的 CSS -->
<link rel="preload" href="https://cdn.jsdelivr.net/npm/twikoo@1.7.11/dist/twikoo.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/twikoo@1.7.11/dist/twikoo.css"></noscript>

<!-- 备用的内联样式,确保在 CSS 加载前不会碎掉 -->
<style is:global>
  /* 确保评论容器在 CSS 加载前有基本样式 */
  #tcomment {
    min-height: 200px;
  }
  #tcomment .tk-comment-container {
    opacity: 1 !important;
    visibility: visible !important;
  }
</style>

<div class="mt-8">
  <h2 class="mb-4 text-lg font-medium" style="color: oklch(var(--bc))">评论</h2>
  <div 
    id="tcomment" 
    class="twikoo-container rounded-lg border p-4 dark:border-zinc-700"
    data-env-id={envId}
    data-path={path || 'auto'}
  >
    <div class="flex items-center justify-center py-8 text-zinc-500 dark:text-zinc-400">
      <div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500 mr-2"></div>
      正在加载评论...
    </div>
  </div>
</div>

<!-- 使用 defer 属性延迟执行,确保 DOM 就绪 -->
<script 
  src="https://cdn.jsdelivr.net/npm/twikoo@1.7.11/dist/twikoo.min.js"
  defer
></script>

<!-- 内联脚本处理初始化 -->
<script is:inline>
  // 全局变量存储重试次数
  window.twikooRetryCount = 0;
  window.maxRetries = 20; // 最大重试次数

  function waitForTwikoo(callback, retryCount = 0) {
    if (typeof window.twikoo !== 'undefined') {
      callback();
    } else if (retryCount < window.maxRetries) {
      setTimeout(() => waitForTwikoo(callback, retryCount + 1), 200);
    } else {
      console.error('Twikoo failed to load after maximum retries');
      showErrorMessage();
    }
  }

  function showErrorMessage() {
    const containers = document.querySelectorAll('#tcomment');
    containers.forEach(container => {
      if (container) {
        container.innerHTML = `
          <div class="text-center py-8 text-red-500">
            <p>评论系统加载失败</p>
            <button onclick="location.reload()" class="mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 text-sm">
              重新加载
            </button>
          </div>
        `;
      }
    });
  }

  function initializeTwikoo() {
    const container = document.getElementById('tcomment');
    
    if (!container) {
      console.warn('Twikoo container (#tcomment) not found');
      return;
    }

    const envId = container.getAttribute('data-env-id');
    const path = container.getAttribute('data-path');
    
    if (!envId) {
      console.error('Twikoo envId not provided');
      container.innerHTML = '<div class="text-center py-8 text-red-500">评论系统配置错误</div>';
      return;
    }

    try {
      // 清理容器
      container.innerHTML = '';
      
      // 初始化 Twikoo
      window.twikoo.init({
        envId: envId,
        el: '#tcomment',
        path: path === 'auto' ? location.pathname : path,
        lang: 'zh-CN'
      });
      
      console.log('Twikoo initialized successfully');
    } catch (error) {
      console.error('Failed to initialize Twikoo:', error);
      container.innerHTML = `
        <div class="text-center py-8 text-red-500">
          <p>评论系统初始化失败</p>
          <p class="text-sm mt-1">${error.message}</p>
          <button onclick="location.reload()" class="mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 text-sm">
            重新加载
          </button>
        </div>
      `;
    }
  }

  function handlePageInit() {
    // 重置重试计数
    window.twikooRetryCount = 0;
    
    // 等待 Twikoo 脚本加载完成后初始化
    waitForTwikoo(initializeTwikoo);
  }

  // 首次加载处理
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', handlePageInit);
  } else {
    // DOM 已经准备就绪,延迟一点执行以确保所有脚本都加载完成
    setTimeout(handlePageInit, 100);
  }

  // Astro ViewTransitions 支持
  document.addEventListener('astro:after-swap', () => {
    console.log('Page swapped, reinitializing Twikoo');
    setTimeout(handlePageInit, 150);
  });

  // 页面可见性变化处理(可选)
  document.addEventListener('visibilitychange', () => {
    if (!document.hidden) {
      const container = document.getElementById('tcomment');
      if (container && container.children.length === 0) {
        console.log('Page became visible, checking Twikoo');
        setTimeout(handlePageInit, 100);
      }
    }
  });
</script>

<style is:global>
  .twikoo-container {
    font-family: inherit;
    min-height: 200px;
  }
  
  .dark .twikoo-container {
    background-color: transparent;
  }
  
  /* 输入框样式 */
  .dark .tk-content textarea,
  .dark .tk-input input {
    background-color: rgb(39 39 42) !important;
    border-color: rgb(63 63 70) !important;
    color: rgb(228 228 231) !important;
  }
  
  .dark .tk-content textarea:focus,
  .dark .tk-input input:focus {
    border-color: rgb(96 165 250) !important;
  }
  
  /* 按钮样式 */
  .dark .tk-submit {
    background-color: rgb(24 24 27) !important;
    border-color: rgb(63 63 70) !important;
    color: rgb(228 228 231) !important;
  }
  
  .dark .tk-submit:hover {
    background-color: rgb(39 39 42) !important;
  }
  
  /* 评论区域样式 */
  .dark .tk-comment,
  .dark .tk-replies-wrap {
    background-color: transparent !important;
    border-color: rgb(63 63 70) !important;
  }
  
  .dark .tk-comment .tk-main {
    color: rgb(228 228 231) !important;
  }
  
  .dark .tk-comment .tk-meta span {
    color: rgb(161 161 170) !important;
  }
  
  /* 链接样式 */
  .dark .tk-comment a {
    color: rgb(96 165 250) !important;
  }
  
  .dark .tk-comment a:hover {
    color: rgb(147 197 253) !important;
  }
  
  /* 表情包容器 */
  .dark .tk-owo-container {
    background-color: rgb(39 39 42) !important;
    border-color: rgb(63 63 70) !important;
  }
  
  /* 标签和额外信息 */
  .dark .tk-tag,
  .dark .tk-extras {
    color: rgb(161 161 170) !important;
  }
  
  /* 字体和边框圆角 */
  .tk-comment,
  .tk-content,
  .tk-input {
    font-family: 'Geist', system-ui, sans-serif !important;
  }
  
  .tk-content textarea,
  .tk-input input,
  .tk-submit,
  .tk-comment,
  .tk-owo-container {
    border-radius: 0.5rem !important;
  }
  
  .tk-comment {
    margin-bottom: 1rem !important;
  }
  
  /* 加载状态 */
  .dark .tk-loading {
    color: rgb(161 161 170) !important;
  }
  
  /* 加载动画 */
  .animate-spin {
    animation: spin 1s linear infinite;
  }
  
  @keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
  }
</style>

使用组件

在博客文章模板(如 [...slug].astro )中引入组件:

ASTRO
<TwikooComments 
  envId="https://example.com/" <!--填你的 R2 的域名没创建 R2 的话请填写你绑定的自定义域名或 Cloudflare 分配的域名-->
  path={`/blog/${slug}`}
/>

在其他独立页面(如友链页)中,可以省略 path 参数:

ASTRO
<TwikooComments 
    envId="https://example.com/" <!--填你的 R2 的域名没创建 R2 的话请填写你绑定的自定义域名或 Cloudflare 分配的域名-->
/>

邮箱提醒配置

注册 Resend 账号

Resend 官网

这一步不用教了吧(。・ω・。)。

添加并验证域名

为了使用自己的域名邮箱(如我网站的 blog@email.fanzhuo.xyz ),需要将域名添加到 Resend 并验证。

  1. 登录 Resend 控制台,点击左侧 Domains
  2. 点击 Add Domain
  3. 输入你的域名(如 email.fanzhuo.xyz),选择区域(推荐选择距离你最近的区域,如 Tokyo ap-northeast-1
  4. 按照提示配置 DNS 记录。如果你的域名托管在 Cloudflare,可以点击 Go to Cloudflare 自动配置

添加记录后,DNS 传播通常需要几分钟到一小时。在 Resend Domains 页面查看状态变为 Verified(绿色) 即表示验证成功 。

创建 API Key

点击 API Keys ,点击 Create API Key

按照提示填写后,复制并保存 API Key。

在 Twikoo 后台配置邮件通知

进入后台,进入配置管理,点击邮件通知。

按以下内容填写:

配置项填写内容(示例)说明
SENDER_EMAILblog@email.fanzhuo.xyz你刚刚在 Resend 添加的域名邮箱(@前面的内容自定义)
SENDER_NAMEFanzhuo's Blog 评论提醒邮件通知标题
SMTP_SERVICEResend邮件通知邮箱服务商
SMTP_USERresend任意非空值即可
SMTP_PASSre_xxxxxxxxxxxx上一步复制的 API Key

点击页面底部的保存按钮。

测试邮件功能

在配置页面底部,点击邮件通知测试,输入你的接收邮箱,点击发送测试邮件。

如果收到邮件,说明配置成功了!

邮件模板自定义

MAIL_TEMPLATE 字段中,可以自定义邮件模板,支持以下变量:

变量说明
${SITE_URL}网站链接
${SITE_NAME}网站名称
${NICK}评论者昵称
${COMMENT}评论内容
${POST_URL}文章链接
${PARENT_NICK}被回复者昵称
${PARENT_COMMENT}被回复的评论内容

这边贴上我的 MAIL_TEMPLATEMAIL_TEMPLATE_ADMIN

MAIL_TEMPLATE

HTML
<div
    style="border-radius: 10px 10px 10px 10px;font-size:14px;color: #555555;width: 666px;font-family:'Century Gothic','Trebuchet MS','Hiragino Sans GB',微软雅黑,'Microsoft Yahei',Tahoma,Helvetica,Arial,'SimSun',sans-serif;margin:50px auto;border:1px solid #eee;max-width:100%;background: #ffffff repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);">
    <div
        style="width:100%;background:#49BDAD;color:#ffffff;border-radius: 10px 10px 0 0;background-image: -moz-linear-gradient(0deg, rgb(67, 198, 184), rgb(255, 209, 244));background-image: -webkit-linear-gradient(0deg, rgb(67, 198, 184), rgb(255, 209, 244));height: 66px;">
        <p
            style="font-size:15px;word-break:break-all;padding: 23px 32px;margin:0;background-color: hsla(0,0%,100%,.4);border-radius: 10px 10px 0 0;">
            您在<a style="text-decoration:none;color: #ffffff;" href="${SITE_URL}"
                target="_blank">${SITE_NAME}</a>上的留言有新回复啦!</p>
    </div>
    <div style="margin:40px auto;width:90%">
        <p>Hi, ${PARENT_NICK} 同学,您曾在文章上发表评论:</p>
        <div
            style="background: #fafafa repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);margin:20px 0px;padding:15px;border-radius:5px;font-size:14px;color:#555555;">
            ${PARENT_COMMENT}</div>
        <p><strong>${NICK}</strong> 给您的回复如下:</p>
        <div
            style="background: #fafafa repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);margin:20px 0px;padding:15px;border-radius:5px;font-size:14px;color:#555555;">
            ${COMMENT}</div>
        <p>您可以点击<a style="text-decoration:none; color:#12addb" href="${POST_URL}" target="_blank">查看回复的完整内容</a>,欢迎再次光临<a
                style="text-decoration:none; color:#12addb" href="${SITE_URL}" target="_blank">${SITE_NAME}</a>。</p>
        <hr />
        <p style="font-size:12px;color:#b7adad">本邮件为系统自动发送,请勿直接回复邮件哦,可到博文内容回复。</p>
    </div>
</div>

MAIL_TEMPLATE_ADMIN

HTML
<div
    style="border-radius: 10px 10px 10px 10px;font-size:14px;color: #555555;width: 666px;font-family:'Century Gothic','Trebuchet MS','Hiragino Sans GB',微软雅黑,'Microsoft Yahei',Tahoma,Helvetica,Arial,'SimSun',sans-serif;margin:50px auto;border:1px solid #eee;max-width:100%;background: #ffffff repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);">
    <div
        style="width:100%;background:#49BDAD;color:#ffffff;border-radius: 10px 10px 0 0;background-image: -moz-linear-gradient(0deg, rgb(67, 198, 184), rgb(255, 209, 244));background-image: -webkit-linear-gradient(0deg, rgb(67, 198, 184), rgb(255, 209, 244));height: 66px;">
        <p
            style="font-size:15px;word-break:break-all;padding: 23px 32px;margin:0;background-color: hsla(0,0%,100%,.4);border-radius: 10px 10px 0 0;">
            (~ ̄▽ ̄)~<a style="text-decoration:none;color: #ffffff;" href="${SITE_URL}"
                target="_blank">${SITE_NAME}</a>上有新评论啦!</p>
    </div>
    <div style="margin:40px auto;width:90%">
        <p>${NICK} 回复说:</p>
        <div
            style="background: #fafafa repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);margin:20px 0px;padding:15px;border-radius:5px;font-size:14px;color:#555555;">
            ${COMMENT}</div>
        <p>您可以点击<a style="text-decoration:none; color:#12addb" href="${POST_URL}" target="_blank">查看回复的完整内容</a>。</p>
        <hr />
    </div>
</div>

自定义表情包

Twikoo 默认只有 3 套基础表情包,你可以通过配置 JSON 文件来添加自定义表情包。

快速上手

如果你不想自己折腾,可以直接使用我整理好的 JSON 文件:

TEXT
https://fanzhuo.xyz/twikoo-emotion.json

这个文件包含了2233娘、小电视、微博、贴吧、雪初音、QQ和小黑盒的表情包。

自己配置

在项目中创建一个 JSON 文件。

进入 Twikoo-Magic

README 中找到你喜欢的表情包,进入该文件夹,复制 JSON 文件里的全部内容到你的 JSON 文件里。

如果需要添加多套表情包,需要将它们合并到一个 JSON 文件中。

合并注意事项:

规则说明
分组之间用逗号分隔每个表情包分组后面加英文逗号 ,
最后一个分组不加逗号末尾分组后面不要加逗号
外层大括号包裹整个文件最外层只有一个 { }
使用双引号JSON 要求键名必须用双引号

格式示例:

JSON
{
  "分组1": {
    "type": "image",
    "container": [...]
  },
  "分组2": {
    "type": "image",
    "container": [...]
  },
  "分组3": {
    "type": "image",
    "container": [...]
  }
}

如何使用

打开 Twikoo 后台,进入配置管理,找到插件。

EMOTION_CDN 中输入表情 CDN (就是刚刚的 https://fanzhuo.xyz/twikoo-emotion.json 或者你自己创建的JSON文件的地址)

点击页面底部的保存按钮。

数据迁移

从 Waline 到 Twikoo

前言

我原本是用的 Waline 评论系统,教程,但是由于我的 Vercel 账户被 paused 了,并且 LeanCloud 官方发布了停服通知,将于 2027 年 1 月 12 日正式关闭所有公众服务。

我尝试迁移到 MongoDB,但是一直出问题,无奈使用了 Twikoo 。

我在 LeanCloud 后台导出了 Waline 的评论数据,那么问题来了,如何将 Waline 的评论数据迁移至 Twikoo 呢?

我找到了这篇教程

程序迁移

把导出来的 jsonl 文件转换为 json 文件

在与评论数据的同个文件夹中创建一个 z.py (起什么名都行)。

代码如下:

PYTHON
import json

lines = []
with open('Comment.0.jsonl', 'r', encoding='utf-8') as f:
    for line in f:
        line = line.strip()
        if not line:
            continue
        if line.startswith('#'):  # ← 跳过注释行
            continue
        lines.append(json.loads(line))

result = {"data": {"Comment": lines}}

with open('waline_data.json', 'w', encoding='utf-8') as f:
    json.dump(result, f, ensure_ascii=False, indent=2)

print("✅ 转换完成!生成 waline_data.json")

你就会得到一个 waline_data.json

把 json 文件转换为符合 Twikoo 格式的文件

WA

Loading repository information...

打开这个项目,下载 main.py 到与评论数据的同个文件夹,或者直接在与评论数据的同个文件夹中创建一个 main.py ,代码如下:

PYTHON
import json
import uuid
import hashlib
import click
from pathlib import Path
from datetime import datetime
from urllib.parse import quote
from markdown import markdown

cidMap = { }
uidMap = { }
res = []


def step_start(prompt):
    click.echo(prompt, nl=False)


def step_complete(another_newline=False):
    if another_newline:
        click.echo("  完成✅\n")
    else:
        click.echo("  完成✅")


def new_uuid():
    return str(uuid.uuid4()).replace("-", "")


def md5_encrypt(data):
    md5 = hashlib.md5()
    md5.update(data.encode("UTF-8"))
    return md5.hexdigest()


def iso2unix(iso):
    dt = datetime.fromisoformat(iso)
    return int(dt.timestamp() * 1000)


def get_converted(item, site_domain, master_mail):
    url = quote(item["url"])
    return {
        "nick": item["nick"],
        "mail": item["mail"],
        "link": item["link"],
        "ua": item["ua"],
        "ip": item["ip"],
        "url": url,
        "comment": markdown(item["comment"].replace("\n", "\n\n")),
        "pid": cidMap[item["pid"]] if "pid" in item else None,
        "rid": cidMap[item["rid"]] if "rid" in item else None,
        "_id": cidMap[item["objectId"]],
        "uid": uidMap[item["mail"]],
        "mailMd5": md5_encrypt(item["mail"]),
        "master": bool(item["mail"] == master_mail),
        "href": "https://" + site_domain + url,
        "isSpam": bool(item["status"] != "approved"),
        "created": iso2unix(item["insertedAt"]),
        "updated": iso2unix(item["updatedAt"]),
        "top": bool(item["sticky"] > 0) if "sticky" in item else False,
    }


def read_waline(read_file):
    step_start("读取 Waline 评论数据中……")
    with open(read_file, "r", encoding="UTF-8") as f:
        waline = json.load(f)["data"]["Comment"]
    total = len(waline)
    cnt = 0
    step_complete()
    return waline, total, cnt


def establish_map(waline):
    step_start("映射建立中……")
    for item in waline:
        cidMap[item["objectId"]] = new_uuid()
        if item["mail"] not in uidMap:
            uidMap[item["mail"]] = new_uuid()
    step_complete(True)


def convert_all(site_domain, master_mail, waline, total, cnt):
    for item in waline:
        cnt += 1
        step_start(f"{cnt}/{total}: 正在转换来自 [{item["nick"]}] 的评论……")
        res.append(get_converted(item, site_domain, master_mail))
        step_complete()


def write_twikoo(write_file):
    step_start("\n写入文件中……")
    output_path = Path(write_file)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    with open(write_file, "w", encoding="UTF-8") as f:
        json.dump(res, f, ensure_ascii=False, indent=2)
    step_complete()


def main(site_domain, master_mail, master_uid, read_file, write_file):
    global uidMap
    if master_uid != "":
        uidMap = { master_mail: master_uid }
    waline, total, cnt = read_waline(read_file)
    establish_map(waline)
    convert_all(site_domain, master_mail, waline, total, cnt)
    write_twikoo(write_file)


def interactive_input():
    site_domain = click.prompt("你的站点域名")
    bcf = click.prompt("你(博主)有在新的Twikoo评论系统上评论过吗?(y/N)", type=bool, default=False, show_default=False)
    if bcf:
        master_mail = click.prompt("你的电子邮件")
        master_uid = click.prompt("你的 Twikoo UID(可在导出 Twikoo 评论数据后看到)")
    else:
        master_mail = master_uid = ""
    read_file = click.prompt("原 Waline 评论数据文件路径(相对路径,JSON文件)")
    write_file = click.prompt("新的 Twikoo 评论数据文件存储路径(相对路径,JSON文件)")
    click.echo()
    main(site_domain, master_mail, master_uid, read_file, write_file)


if __name__ == '__main__':
    click.echo("Program started.\n")
    interactive_input()
    click.echo("\nProgram ended.")

这个程序需提前安装的第三方库 clickMarkdown

BASH
pip install click markdown

然后直接 python main.py ,按着提示输入就行了(原 waline 数据填刚刚的 waline_data.json )。

但是我在使用过程中发现有问题,会报错,原因是脚本在转换 insertedAt 时,直接传入了字典对象 {"__type":"Date","iso":"..."} ,而不是字符串。需要从字典中提取 iso 字段。

所以建议把第 59 到 60 行的:

PYTHON
"created": iso2unix(item["insertedAt"]),
"updated": iso2unix(item["updatedAt"]),

改为:

PYTHON
"created": iso2unix(item["insertedAt"]["iso"] if isinstance(item["insertedAt"], dict) else item["insertedAt"]),
"updated": iso2unix(item["updatedAt"]["iso"] if isinstance(item["updatedAt"], dict) else item["updatedAt"]),

最后你会的到一个转换完的 json 文件,打开看看,格式是这样的:

JSON
[
  {
    "nick": "匿名",
    "mail": "",
    "link": "",
    "ua": "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
    "ip": "203.123.64.219",
    "url": "/blog/dijkstra",
    "comment": "<p>加油!</p>",
    "pid": null,
    "rid": null,
    "_id": "4af68593f00d4fe0bb551b695d195e6e",
    "uid": "a8d5c853aa874040a093076bb81ed749",
    "mailMd5": "d41d8cd98f00b204e9800998ecf8427e",
    "master": false,
    "href": "https://fanzhuo.xyz/blog/dijkstra",
    "isSpam": false,
    "created": 1755935243365,
    "updated": 1755935244038,
    "top": false
  }
]

注意,如果你的文章地址是中文,那么 urlhref 会变成类似 https://fanzhuo.xyz/blog/%E8%AF%B4%E8%AF%B4/ 这样的内容,请把 %E8%AF%B4%E8%AF%B4 改为你的文章地址的明文,如这个应改为 https://fanzhuo.xyz/blog/说说/

好了,打开 Twikoo 评论系统后台,选到导入界面,原系统选 Twikoo ,文件选你转换完的 json 文件,导入!

大功告成。

最后

感谢阅读!

参考文献:Twikoo 文档数据迁移之从 waline 到 twikoo


Thanks for reading!

Cloudflare workers 云函数部署 Twikoo 评论系统

周五 6月 19 2026 Development
3539 字 · 25 分钟

评论

正在加载评论...