• 简体中文
  • 数据格式

    评论数据存放在 COMMENTS_PATH(默认 comments/)目录下。

    comments/
      index.json
      hello-world.json
      posts/another-post.json

    每篇文章一个聚合文件,而不是一条评论一个文件。原因:代理读下 N+1 请求成本太高,聚合成一个文件后读取成本与评论数无关。

    文章文件

    comments/<slug>.json:

    {
      "version": 1,
      "post": "hello-world",
      "updatedAt": "2026-10-30T05:00:00.000Z",
      "comments": [
        {
          "id": "8c8f3690-7bb7-4add-901d-c829b337417e",
          "parentId": null,
          "name": "Alice",
          "content": "写得好。",
          "createdAt": "2026-10-30T04:12:00.000Z",
          "emailEncrypted": "<base64>",
          "emailKeyFingerprint": "a1b2c3d4"
        }
      ]
    }
    字段API 返回说明
    id是UUID v4,提交时生成,批准不变
    parentId是只指向同文件内已存在的评论;V1 最多两级
    name是纯文本
    content是纯文本,展示时转义,不解析 HTML
    createdAt是提交时刻(UTC),不是批准时刻
    emailEncrypted否AES-256-GCM,钥匙由 COMMENTS_SECRET 经 HKDF 派生
    emailKeyFingerprint否便于密钥轮换后识别旧密文

    未填写邮箱时,三个邮箱相关字段全部省略。

    索引文件

    comments/index.json:

    {
      "version": 1,
      "updatedAt": "2026-10-30T05:00:00.000Z",
      "posts": {
        "hello-world": { "count": 12, "updatedAt": "2026-10-30T05:00:00.000Z" }
      }
    }

    索引只提供站点级计数,不参与评论读取。极端情况下它可能短暂落后于文章文件,以文章文件为准。

    slug 规则

    • 允许字符:小写字母、数字、-、_、/
    • 以字母或数字开头
    • 最长 179 字符
    • 禁止 ..、禁止绝对路径
    • 结尾的 .json 会被剥离

    widget 默认取 location.pathname 并按同一套规则规范化。

    并发

    写入使用文件 sha 做乐观锁。同一篇文章两条评论同时批准时,后者会遇到 sha 不匹配,服务端重读后重试(有限次)。

    索引文件是独立写入,失败只记日志,不会让批准本身失败。

    Git 历史永久保留

    删除评论只删当前版本,Git 历史仍然存在。表单必须写明这一点,任何抓取仓库历史的人都能看到已删除内容。