🌐 中文 · English
零框架 · 零构建 · 零依赖 —— 双击 index.html 就能用的个人博客
Qingyu'Blog(轻语博客)是一个纯原生 JavaScript 编写的个人博客系统,不依赖任何前端框架(React / Vue / Svelte)和构建工具(Webpack / Vite),也没有任何运行时第三方依赖。
它支持两种运行模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 静态模式 | 双击 public/index.html 即可使用,数据存浏览器 localStorage |
本地写作、临时预览 |
| 云端模式 | 部署到 Cloudflare Workers + D1,数据存云端数据库 | 正式发布、多人访问 |
整个博客本体就在 public/ 目录:前台 index.html + style.css + app.js + posts.js + music-player.js + bg-anim.js,后台 admin.js + admin.css,国际化 i18n.js + locales/。
🆕 当前版本
v2.10.100。除写作 / 评论 / 统计等基础能力外,还内置:文章加密(AES-GCM 纯前端)、文章级 SEO(标题 / 描述 / canonical / noindex)、草稿预览分享链接(HMAC 签名)、一键导出静态站、打印 / 导出 PDF、Webmention、多作者与作者页、Mermaid 图表 + KaTeX 公式(本地化按需加载)、订阅分组群发、前端错误日志、评论反机器人、访问国家 / 设备识别,以及后台「功能开关」等。
完整变更历史见仓库 提交记录。
💡 仓库根目录的
index.html只是一个跳转页,会自动打开public/index.html(Workers / Pages 的部署目录)。本地双击public/index.html同样可用。
🆕 第一次部署 Cloudflare? 请直接看 Cloudflare 配置完全指南(新手版) —— 从注册账号、创建 D1/KV、申请 API Token,到 R2 桶与 CORS、绑定域名、首次初始化管理员,每一步都写了在控制台的哪个位置点什么。
| 优点 | 说明 |
|---|---|
| 零门槛 | 不需要 Node.js、不需要 npm、不需要构建,双击即可运行 |
| 零成本 | Cloudflare Workers + D1 + KV + R2 的免费额度完全够个人博客使用 |
| 零依赖 | 核心(前台 / 后台 / i18n)不引入任何第三方运行时库;表情 / 图表 / 公式等重量级库按需加载且已本地化在 public/libs/,页面用不到就一个字节都不下载 |
| 零锁定 | 文章是 Markdown 文本,数据在标准 SQLite(D1),随时可以迁走 |
| 双通道 | 静态导出 + 云端 API,同一份代码两种部署方式 |
| 视觉细节 | 清理了全站标题下方、底部导航、页脚等处的装饰性小圆点分隔符;评论区与底部导航之间留出间距 |
| 响应式 | 前台 + 后台均支持手机 / 平板 / 桌面全适配 |
| 多语言 | 内置中文 / English / 日本語 / 한국어 / हिन्दी 五语界面,自动识别浏览器语言 |
| 书卷美学 | 宋体正文 + 仿宋引用装饰,零 webfont 下载;四季背景动画(春樱 / 夏光 / 秋叶 / 冬雪) |
| 安全 | 密码 PBKDF2-SHA256 加盐哈希(10 万次迭代)、会话令牌鉴权、登录失败限流锁定、CSP 等安全响应头 |
| AI 增强 | Workers AI 提供文章摘要 / 写作助手 / 评论汇总 / 垃圾检测;未配置或关闭时自动隐藏入口、对博客零影响 |
| 内容工具链 | 文章加密、SEO 覆盖、草稿自动保存与崩溃恢复、预览分享链接、静态站导出、打印 / PDF、Webmention 引用、多作者页——写作与分发闭环 |
| 可观测 | 后台仪表盘(趋势 / 来源 / 国家 / 设备 / 品牌)、站点健康检查、操作审计日志、前端错误日志(自动捕获访客端异常并按相同错误聚合) |
git clone https://github.com/kejiland/qingyu-blog.git
cd qingyu-blog双击 public/index.html,或启动本地服务器:
# Python
python -m http.server 8080 -d public
# Node.js
npx serve public打开 http://localhost:8080/admin,设置密码即可开始写作。
只需要四件事:创建 D1 数据库 → 创建 KV 命名空间 → 申请 API Token → 填进 GitHub Secrets,剩下的交给 GitHub Actions。
# 登录 Cloudflare
npx wrangler login
# 创建 D1 数据库(主存储:文章 / 评论 / 统计 / 密码 / 设置)
npx wrangler d1 create blog
# 记下输出的 database_id(是 UUID,不是数据库名,也不是 KV 的 id)
# 创建 KV 命名空间(限流 / 去重 / AI 缓存)
npx wrangler kv namespace create BLOG
# 记下输出的 id(32 位十六进制)在仓库 Settings → Secrets and variables → Actions → Secrets 中添加:
必填(缺一个部署就停):
| Secret | 说明 |
|---|---|
CLOUDFLARE_API_TOKEN |
Cloudflare Account API Token(需 Workers Scripts / D1 / KV 的 Edit 权限) |
CLOUDFLARE_ACCOUNT_ID |
Cloudflare 账户 ID(控制台右侧可见,或看地址栏) |
BLOG_D1_ID |
D1 数据库 ID(上一步创建获得,UUID 格式) |
BLOG_KV_ID |
KV 命名空间 ID(上一步创建获得,32 位十六进制) |
推荐:
| Secret | 说明 |
|---|---|
BLOG_ADMIN_SETUP_KEY |
安装密钥。配置后只有拿着这串密钥的人才能初始化管理员密码(防抢注,强烈建议);未配置则退化为"首次登录自动生成随机默认密码",存在先到先得竞态 |
SITE_URL |
站点对外域名,如 https://blog.example.com(用于收紧 CORS / RSS / Sitemap,末尾不要加 /) |
可选(按功能分组):
📧 邮件订阅与通知(Resend,三项齐全才启用)
| Secret | 说明 |
|---|---|
RESEND_API_KEY |
Resend API Key |
BLOG_MAIL_FROM |
发件人地址(域名需在 Resend 验证),如 blog@yourdomain.com |
BLOG_MAIL_REPLY_TO |
可选:回信地址 |
BLOG_ADMIN_EMAIL |
可选:新评论通知收件邮箱;不填回退到「个人资料 → 邮箱」 |
🔐 文章加密与预览链接
| Secret | 说明 |
|---|---|
BLOG_PREVIEW_SECRET |
草稿预览分享链接的 HMAC 签名密钥。不填则由管理员密码哈希派生 —— 改密码会让所有已发出的预览链接立即失效 |
💬 评论
| Secret | 说明 |
|---|---|
COMMENT_BLOCKLIST |
评论敏感词(换行 / 逗号分隔),优先级高于后台词库 |
BLOG_RATE_LIMIT_BINDING |
启用 Worker 内的边缘登录限流(值为正整数命名空间,如 1001);会让部署改用 wrangler 4.x。账户不支持时删掉即可 |
🤖 AI(Workers AI)
| Secret | 说明 |
|---|---|
BLOG_AI_ENABLED |
设为 0 / false / off 可整体关闭 AI;不填 = 有绑定就开启 |
BLOG_AI_PUBLIC |
设为 0 / false / off 可禁止匿名访客生成摘要(登录后仍可用,只读缓存与 ping 不受影响) |
🗂️ R2 对象存储(音乐 / 媒体 / 备份)
| Secret | 说明 |
|---|---|
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY / R2_ENDPOINT |
R2 S3 兼容凭据(音乐、媒体、备份共用同一对) |
R2_BUCKET / R2_PUBLIC_BASE |
音乐桶:桶名 + 公开域名(不能为空,否则整组 R2 配置不写入) |
R2_MEDIA_BUCKET / R2_MEDIA_PUBLIC_BASE |
媒体桶:桶名 + 公开域名 |
R2_BACKUP_BUCKET |
私有备份桶:保存自动 / 手动备份 JSON,建议不要绑定公开域名;不填则备份与恢复功能停用 |
⚙️ 其他
| Secret | 说明 |
|---|---|
CF_ZONE_ID |
自定义域名的 Zone ID;配合 Token 的 Cache Purge 权限 → 发布即清边缘缓存(运行时变量 CF_API_TOKEN 由工作流自动写入) |
PAGES_PROJECT_NAME |
名字有误导性:实际作用是覆盖 Worker 名称。不填则用 wrangler.workers.toml 里的 kejiland。新手建议不填 |
BLOG_WRITE_TOKEN |
旧式写入令牌,新部署不需要 |
代码实际读取的全部变量;标 自动 的由上面的 GitHub Secret 自动写入 Worker。
| 变量 | 类型 | 未配置时的行为 |
|---|---|---|
DB |
D1 绑定 | 所有接口返回「数据库未配置」 |
ASSETS |
静态资源绑定 | 静态页面 404(Workers 部署自动注入) |
BLOG |
KV 绑定 | 评论 / 点赞 / 浏览的限流与去重失效(功能仍可用) |
SITE_URL |
变量 | 跨域请求一律被拒;RSS / Sitemap 改用请求来源 |
CF_ZONE_ID + CF_API_TOKEN |
变量 + Secret(自动) | 不做边缘缓存清除(新内容延迟 1~5 分钟生效) |
BLOG_ADMIN_SETUP_KEY |
Secret | 首次登录自动生成随机默认密码(有先到先得竞态);仍是登录限流的应急通道 |
BLOG_WRITE_TOKEN |
Secret | 只能走会话登录 |
BLOG_RATE_LIMIT_BINDING → LOGIN_LIMITER |
Secret → 绑定 | 不注入边缘限流,登录限流回退到 D1 计数 |
AI |
Workers AI 绑定 | /api/ai/* 全部 404,前端自动隐藏 AI 入口 |
BLOG_AI_ENABLED |
Secret / 变量 | 有 AI + DB 时即视为开启 |
BLOG_AI_PUBLIC |
Secret / 变量 | 允许匿名生成 AI 摘要 |
RESEND_API_KEY / BLOG_MAIL_FROM / SITE_URL |
Secret(自动) | 邮件订阅、新文章通知、评论通知、订阅者群发全部停用 |
BLOG_MAIL_REPLY_TO |
Secret(自动) | 不影响发送,仅无 reply-to |
BLOG_ADMIN_EMAIL |
Secret(自动) | 评论通知回退到「个人资料 → 邮箱」 |
COMMENT_BLOCKLIST |
Secret(自动) | 只使用后台维护的词库 |
BLOG_PREVIEW_SECRET |
Secret(自动) | 预览链接密钥由管理员密码哈希派生(改密码即失效) |
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY / R2_ENDPOINT |
Secret(自动) | 图片 / 音乐上传与备份接口返回 503 |
R2_BUCKET / R2_PUBLIC_BASE |
Secret(自动) | 音乐上传不可用(不影响图片) |
R2_MEDIA_BUCKET / R2_MEDIA_PUBLIC_BASE |
Secret(自动) | 图片上传不可用(不影响音乐) |
R2_BACKUP_BUCKET |
Secret(自动) | 备份与恢复停用(后台备份页显示未配置) |
⚠️ 添加或修改 Secret 后一定要重新跑一次部署(Deploy 工作流),新值才会写入 Worker。可选 Secret 留空不影响部署——工作流只写入非空的那些。
推送代码或手动运行 Actions,工作流会自动:
- ✅ 安装 Wrangler CLI
- ✅ 运行三套测试(
smoke-test.js178 例 /gb-verify.js18 例 /search-verify.js25 例,失败即中止不部署) - ✅ 校验必要 Secrets 与 ID 格式
- ✅ 执行 D1 迁移(
schema_migrations记账表 + 列预检 + 报错兜底,三层幂等) - ✅ 部署 Worker 到 Cloudflare
- ✅ 写入运行时 Secret(安装密钥 / R2 凭据 / 清缓存凭证,配置了才写)
部署完成后访问 https://你的域名/admin:配置了安装密钥就点「首次部署?使用安装密钥初始化」;没配置就用任意密码登录一次、拿页面上弹出的随机默认密码登录,然后立刻改密。
详细的每一步(含控制台截图级别的路径说明、权限勾选表、R2 CORS 配置、故障排查速查表)见 CLOUDFLARE_SETUP_GUIDE.md。 只想搞清 Secrets 与 R2 令牌的区别,看 DEPLOYMENT_SECRETS_GUIDE.md。
若你此前使用 KV 单 key 存储,可把线上数据搬到 D1:
# 本地执行(需 CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN / BLOG_KV_ID / BLOG_D1_ID)
node scripts/migrate-kv-to-d1.mjs --dry-run # 仅预览 SQL
node scripts/migrate-kv-to-d1.mjs # 正式写入 D1或在仓库 Actions 标签页手动触发 Migrate KV to D1 工作流(支持 dry-run / migrate 两种模式)。
| 功能 | 说明 |
|---|---|
| 真实路径路由 | 无 hash:/、/archive、/tags、/about、/guestbook、/posts/<别名>/、/admin、/write,刷新不 404 |
| Markdown 渲染 | 标题 / 表格 / 引用 / 列表 / 代码块(js、ts、python、bash、css 语法高亮)/ 行内代码 / 加粗斜体删除线 / 图片 / 链接;输入先转义,不执行原始 HTML |
| 正文目录 TOC | 自动编号(1 / 1.1 / 1.2 …)、标题锚点、可折叠,点击平滑滚动 |
| 阅读体验 | 阅读时长估算、浏览数、点赞(含本机去重)、置顶徽章、一键分享(复制链接 / 系统分享 / 微博 / X / Facebook / Telegram / 邮件)、阅读位置记忆(下次打开自动回到上次位置)、正文划线高亮(选中即高亮,可加备注,本机保存;支持备注、汇总浮层一键跳转、导出 / 导入 JSON、一键清除)、返回顶部 |
| 阅读字号与浮动目录 | 文章页可一键调整正文字号(A− / A / A+,本地记忆);移动端右下角提供浮动目录按钮,抽屉式展开全部章节 |
| 沉浸式阅读 | 正文图片点击打开全屏灯箱,支持左右切换与键盘浏览;文章顶部显示阅读进度条,目录自动高亮当前章节 |
| 站点公告栏 | 后台「站点信息」开启后,在顶栏下方显示公告与可选链接;访客可一键关闭,本地记住状态 |
| 热门文章排行 | /popular 按浏览×1 + 点赞×3 + 评论×5 展示综合热度,云端支持全部 / 近 7 天 / 近 30 天范围切换 |
| 评论系统 | 云端 D1 全局评论 + 审核模式;评论列表服务端分页(每页 8 条顶层评论,含各自的回复),支持「最热 / 最新」两种排序;最多 3 层嵌套回复;孤儿回复自动提升;删除文章级联清理评论/点赞/浏览量;重复发送拦截(同分区同昵称同内容 409)与频率限制(每 IP 5 条/分钟);评论区与底部导航之间保留留白,评论不再紧贴页脚 |
| 评论互动 | 评论支持点赞、精选和置顶;前台可在「最热 / 最新」间切换,切换即刻高亮反馈并显示骨架占位(不再出现空白或「无数据」闪烁后才出内容),列表连贯不跳动;按顶层分页(每页 8 条,可加载更多),后台可一键切换精选与置顶 |
| 留言板 | 导航直达 /guestbook,「留言 / 优化方案」双分区,云端存储,复用评论安全管道;支持 Ctrl/⌘ + Enter 发送 |
| 全文搜索 | 云端基于 D1 FTS5(trigram)索引标题 / 标签 / 摘要 / 正文,支持中英文子串、相关度排序、关键字高亮和“加载更多”分页;短关键词自动回退 LIKE,静态模式仍有本地搜索 |
| 友情链接页 | 独立的 /links 页面展示后台配置的友链(卡片式、外链新窗口打开);页脚默认新增「友链」入口 |
| 阅读历史 / 稍后读 | 自动记录本机阅读历史(最多 100 篇),文章页可收藏稍后读;历史页汇总两个列表 |
| 分类 | 前台分类页(含每类文章数);首页支持按分类筛选,卡片区显示当前分类并可一键清除;编辑器可填写分类、自动汇总已有分类作候选 |
| 标签与归档 | 首页标签筛选(?tag=,支持回退清除)、标签云(含计数)、归档按「年 → 月」分组 |
| 精选文章 | 评论区下方自动推荐(点赞×3 + 浏览×1 + 评论×5,取前 2 篇,排除当前文章与草稿) |
| 双向链接与相关文章 | 正文可用 [[文章标题]] 建立双向链接;详情页显示“引用本文”,并按共同标签、同系列和发布时间推荐相关文章 |
| RSS / Sitemap | /feed.xml、/sitemap.xml 云端动态生成,随文章增删改查自动更新;草稿与加密文章自动排除 |
| 上一篇 / 下一篇 | 只有一条时自动隐藏空位;点击后新文章一律从开头开始阅读(不会被上一页的滚动位置带过去,仅手动刷新时才恢复上次阅读位置);两篇文章之间交叉淡入淡出过渡,不做整页翻书式切换 |
| 相关文章位置 | 相关文章统一排在上一篇 / 下一篇之下,随正文滚动顺序阅读,不再插在正文中间 |
| 卡片式列表 | 封面缩略图(自动取 cover 或正文首图)、置顶徽章、标签贴底、加载骨架屏、分页(?page=) |
| 深色 / 浅色主题 | 一键切换,跟随系统偏好,无首屏闪白;顶栏会随滚动加深阴影 |
| 主题色切换 | 4 种强调色:赭橙(terra)/ 黛蓝(indigo)/ 竹青(bamboo)/ 凝夜紫(dusk)。桌面为图标按钮 + 色板弹层,手机端为原生下拉;每种配色会连带调整背景与边框色 |
| 多语言界面 | 中文 / English / 日本語 / 한국어 / हिन्दी(各 993 个语言键),自动识别 + 手动切换,桌面为 🌐 弹层 + SVG 国旗,手机端为原生下拉 |
| PWA 离线阅读与写作 | 可安装到桌面或手机;首页、核心资源与已访问文章离线可打开;云端编辑断网时先保存到本地队列,网络恢复后自动同步 |
| 背景动画 | canvas 手绘四季粒子(春日樱花瓣 / 夏日嫩绿叶片 / 秋叶 / 六向分叉雪花),仅首页运行、页面隐藏时暂停;桌面默认开、触屏默认关,顶栏可一键开关,尊重 prefers-reduced-motion。预览:/?season=spring|summer|autumn|winter&bg=1 |
| 图表 / 公式 | 正文支持 Mermaid 图表(```mermaid 代码块)与 KaTeX 数学公式($…$ / `$$…$$`);两个库都本地化在 `public/libs/`(离线可用、无需放开 CDN),且只在页面真的用到时才加载,普通页面零额外请求;可在「功能开关」整体关闭 |
| Smoji 表情 | 评论、留言板、编辑器三处都内置表情选择器,按需懒加载,支持正文内联渲染 |
| AI 文章摘要 | 文章页一键生成内容摘要(单篇缓存 30 天);AI 不可用时入口自动隐藏 |
| 全站音乐播放器 | 右下角悬浮音符按钮,平时缩进窗口外只露出一点圆弧、悬停 / 点击滑出;面板含曲目信息 · 可拖动进度条 · 上一首 / 播放暂停 / 下一首 · 音量 · 播放列表(当前项高亮 + 均衡动画)。自动连播、记忆上次曲目与进度、音量持久化,刷新后恢复但不自动出声;无音乐时完全隐藏,后台路由自动收起;样式与脚本不在首屏关键路径上 |
| 书卷风衬线排版 | 正文 / 标题 / 大标题统一走系统宋体栈,引用装饰走系统仿宋;iOS 走原生宋体 / 仿宋;不加载任何 webfont,无首屏字体下载 |
| 首屏弱网兜底 | 关键资源(config / posts / i18n / app / style)显式 preload,不再靠探测器猜;云端接口超时改为 8 秒截止(不再无限等待),文章正文另设 10 秒超时保护;失败时渲染可手动重试的失败页,不再自动循环拉取 |
| 云端快照兜底 | 每次成功拉到云端文章 / 设置后写一份快照到本地(最长用于 12 小时的首屏);下次打开先用快照立即出内容,后台并行刷新真实数据(拿到后覆盖重渲染)。接口慢或丢包时不再一直转圈,快照不影响数据准确性 |
| 无障碍与细节 | 弹层带 role / aria-*;图标按钮有 title / aria-label;图片 loading="lazy" + 加载淡入;CSP 等安全响应头 |
后台是一个独立切片(admin.js + admin.css),只在进入后台路由时懒加载,前台首屏零成本。
| 功能 | 说明 |
|---|---|
| 路由 | /admin(仪表盘)、/admin/posts、/admin/posts/new、/admin/posts/:id/edit、/admin/tags、/admin/comments、/admin/comments/pending、/admin/media、/admin/music、/admin/settings;未知 /admin/* 回退到仪表盘 |
| 登录门禁 | 云端:密码登录,或「首次部署?使用安装密钥初始化」(安装密钥走 X-Setup-Key);触发登录限流时页面会自动展开「被限流?用安装密钥登录」应急入口(只跳过限流,不跳过密码校验)。静态:本地门禁。任何接口返回 401 会提示「登录已过期」并自动跳回登录页 |
| 仪表盘 | 7 张统计卡(文章总数 / 已发布 / 定时发布 / 草稿 / 置顶 / 评论总数 / 待审核)+ 近 30 天访问与评论两张折线图(内联 SVG,支持悬停预览与点击固定数值)+ 最新发布 / 最新评论(自动滚动、悬停暂停)+ 存储与订阅概览(媒体数量与占用空间 / 音乐 / 订阅者与活跃数 / 备份份数与最近备份时间) |
| 文章数据 | 独立的文章分析页展示每篇内容的浏览、点赞、评论和综合得分,支持全部 / 近 7 天 / 近 30 天范围、每篇趋势图与 CSV 导出,并包含草稿 |
| 文章管理 | 关键字搜索(标题 + 标签,250ms 防抖)、状态筛选(全部 / 已发布 / 定时发布 / 草稿)、每页 10 篇分页、置顶切换(乐观更新)、多选批量置顶 / 取消置顶 / 删除(带二次确认)、删除无感刷新(行级淡出 + 服务端级联清理评论/统计) |
| 定时发布 | 云端模式可指定未来发布时间,Worker Cron 每 5 分钟检查一次,到点自动发布;定时文章不会提前出现在首页、RSS 或 Sitemap |
| 版本历史 | 每次保存自动保留最多 50 个文章快照;可查看版本列表、正文差异,并一键恢复到任意历史版本 |
| 备份与恢复 | 云端可手动备份,Worker Cron 每天北京时间 03:00 自动备份文章、版本、评论、媒体元数据、音乐、设置和统计到私有 R2;保留最近 30 份,列表分页(每页 10)并显示每份备份的内容摘要(文章 / 评论 / 媒体 / 音乐 / 订阅数量),支持下载、删除和一键恢复(恢复前自动生成安全快照) |
| 系列 / 专栏 | 文章可设置系列名称和系列序号;前台 /series 汇总全部系列,系列详情按序号排列并显示系列内上一篇/下一篇;后台支持系列重命名和移除 |
| 邮件订阅 | 前台 /subscribe 邮箱订阅;双重确认后,新文章发布自动进入发件箱并由 Cron 异步发送;后台支持邮箱搜索 + 状态筛选 + 分页(每页 20),可按当前筛选导出 CSV、删除订阅者 |
| 订阅分组与群发 | 订阅者可标注分组(多标签);后台可按分组筛选,并向指定分组(或全部已确认订阅者)群发自定义主题/正文邮件,写入发件箱后由 Cron 异步发送,邮件底部自动带退订入口 |
| 自动分享图 | 编辑器可根据标题、日期、系列和标签自动生成 1200×630 PNG 分享图,上传到 R2;前台自动输出 og:image 与 twitter:image,无分享图时回退封面 |
| 图片压缩与粘贴上传 | 媒体库上传时自动压缩、转为 WebP 并生成缩略图;编辑器支持直接粘贴截图,压缩上传后自动插入 Markdown 图片 |
| 编辑器 | 标题 / 发布时间(精确到分钟,编辑时保留原时间)/ 标签 / 封面(可从媒体库选)/ 置顶 / Markdown 正文;本地草稿自动保存(输入停顿 1.5s / 每 10s 一次,误关标签页或崩溃后重进可一键恢复;加密文章不落本地明文);右侧实时预览、实时字数与预计阅读时长、快捷键(Ctrl/⌘ + B / I / K / S)、输入框自动增高、工具栏(加粗、斜体、标题、引用、代码、列表、链接、图片、表情、表格、任务列表、分割线、代码块(含语言选择));存草稿或直接发布 |
| 多作者 / 作者页 | 每篇文章可指定作者(编辑器有作者输入 + 已有作者候选);文章页显示作者署名;前台 /authors 汇总全部作者及其文章数,/authors/<作者名> 是该作者的文章列表;作者字段随文章一起备份 / 导出(历史版本恢复不会丢失) |
| Webmention | 支持 W3C Webmention:页面 <link rel="webmention"> 声明端点;外站引用你的文章并通知本站后,会抓取来源页校验确实链接到该文章再收录,在文章底部展示来源标题 / 摘要 / 作者;来源页撤链不会自动消失,可在后台「引用管理」删除 |
| 打印 / 导出 PDF | 文章页一键「打印 / PDF」:专门的打印样式会隐藏导航、评论、广告、播放器、目录浮层等一切干扰,只留标题 + 正文;代码块 / 表格 / 图片 / 公式 / 图表自动避免跨页断开,外链自动附上完整网址,页脚附站点名与原文链接,浏览器「另存为 PDF」即可存档 |
| 草稿预览分享链接 | 编辑器一键为未发布文章生成带 HMAC 签名的预览链接(默认 7 天有效,可复制/直接打开);拿到链接的人无需登录即可查看,页面标记 noindex、不统计、不加载评论;修改站点密码会让所有已发出的链接立即失效 |
| 文章级 SEO | 每篇文章可单独覆盖 SEO 标题 / 描述 / Canonical / noindex(留空自动生成);生效于 <title>、meta description、Open Graph、Twitter Card 与 JSON-LD;恢复历史版本不会丢失 SEO 设置 |
| 文章加密 | 编辑器可一键开启正文加密:AES-GCM-256 + PBKDF2-SHA256(10 万次迭代)纯前端加密;明文不会上传,服务器只保存密文 enc;读者打开详情页需输入密码解锁,服务器无法解密。后台编辑器会把密码记住在本机浏览器,可点「显示」查看(仅本机、换设备或清缓存后不可见) |
| AI 写作助手 | 编辑器内一键:标题建议 / 润色 / 翻译(5 种目标语言),结果可应用到标题、替换原文、插入正文末尾或复制;AI 不可用时整条栏位不渲染 |
| 评论敏感词 | 后台可维护敏感词表(每行一个);命中昵称或内容的评论直接拒绝,不进入数据库 |
| 评论反机器人 | 隐藏蜜罐字段 + 表单时间戳校验:蜜罐被填写视为机器人静默丢弃(返回成功但不入库),表单填入不足 2 秒即提交会被拒绝;对正常访客无感,可在「功能开关」关闭 |
| 评论批量操作 | 评论列表支持多选(含全选本页)后批量通过 / 转待审 / 批量删除,服务端一次请求完成并写入操作日志 |
| 评论管理 | 全局评论列表(评论人 / 内容 / 所属文章 / 时间 / 状态 / 操作)、关键字搜索、状态筛选、通过审核(就地更新徽章,不整表重载)、删除(行级淡出);侧栏「待审核评论」带实时数量徽章 |
| AI 评论工具 | 一键汇总近期评论要点(1 小时缓存)+ 单条评论垃圾检测(红 / 绿判定 + 原因) |
| 评论邮件通知 | 新评论和回复自动进入发件箱,由 Cron 异步发送到站长邮箱;收件地址优先取 BLOG_ADMIN_EMAIL,其次为个人资料邮箱 |
| 标签管理 | 标签列表由文章实时统计得出;支持重命名 / 删除(批量更新所有相关文章) |
| 媒体资源库 | 图片上传(浏览器直传 R2 预签名 URL,元数据存 D1)、网格预览、点击缩略图看大图(左右切换 / Esc 关闭)、文件名搜索 + 分页(每页 24)、多选批量删除、复制 URL / 复制 Markdown 图片语法、删除(先删 R2 对象再删 D1 行);静态模式与非云环境给出提示 |
| 音乐管理 | 音频上传(直传 R2,带百分比进度条,支持拖拽);Workers 与 Pages 两种部署形态均可用;**文件名自动识别「歌曲名-歌手」**预填;歌名 / 歌手搜索 + 分页(每页 15);列表行内试听(播放 / 暂停 / 拖动进度 / 显示当前时间与总时长)、重命名、删除(与 R2 对象同步);曲目多时列表卡片内滚动 + 表头吸顶 |
| 功能开关 | 后台「设置 → 功能开关」把原本改 config.js 才能生效的开关搬到网页端:首页每页文章数(0 = 不分页)、首页显示的标签(自选哪些标签出现在首页)、新增导航项显示开关、广告位总开关 + AdSense 客户端 ID + 三个广告位代码(列表上方 / 列表间隔 + 间隔篇数 / 文章底部)、阅读字号(隐藏 A− / A / A+ 按钮)、划线高亮工具(一起管住「高亮汇总 / 清除 / 导出 / 导入」)、收藏(稍后读)按钮、内容渲染(图表公式)、评论反机器人、前端错误上报、站点健康诊断;关闭只是隐藏界面,已保存的高亮与收藏记录不会被删除;保存即时生效,无需改代码重新部署 |
| 博客设置 | 6 个标签页:站点基础信息(站点名称 / 简介 / 头像 Logo / 关于页内容 / 页脚版权署名 / 页脚声明 / 新评论默认需审核)、功能开关(首页分页 / 首页标签 / 新增导航项 / 广告位 / 阅读字号 / 划线高亮 / 收藏 / 内容渲染 / 前端错误上报 / 评论反机器人 / 图表公式 / 站点健康诊断)、个人资料(昵称 / 简介 / 头像 / 邮箱)、导航菜单(可视化增删 + 二级子项 + 一键重置)、底部导航、友情链接 |
| 前端错误日志 | 自动捕获访客浏览器里的未处理异常 / Promise 拒绝,匿名上报到后台 /admin/errors;按相同错误聚合计数(含源码位置、页面、UA),可搜索、一键清空;默认开启,可在「功能开关」关闭 |
| 站点健康检查 | 后台 /admin/health 一键检查 D1 数据表可读性(含各表行数)、KV 读写、R2 媒体桶 / 备份桶、AI 与邮件(Resend)绑定状态 |
| 访问来源 / 设备 | 记录每次浏览的来源域名、国家/地区(Cloudflare 边缘按 IP 地理识别,只存两位国家码、不落原始 IP)、设备类型(桌面 / 手机 / 平板 / 爬虫)、系统(iOS / Android / HarmonyOS / Windows / macOS / Linux)与设备品牌(Apple / Samsung / Xiaomi / Huawei / OPPO / vivo …),按天聚合;仪表盘卡片用国旗 + 占比展示近 30 天国家、来源、设备、系统与品牌 Top 榜 |
| 操作审计日志 | 记录关键后台操作(创建 / 恢复 / 删除备份、删除文章、删除媒体、更新设置),支持类型筛选 + 日期范围筛选 + 分页(每页 20)+ 导出 CSV + 一键清空;记录来源 IP 与时间,出问题可追溯 |
| 顶栏 | 侧栏折叠按钮、面包屑、预览站点、🌐 语言切换、账户菜单(个人资料 / 修改密码 / 退出登录) |
| 导入 / 导出 | 后台支持单篇 Markdown、批量 ZIP、全部导出和 JSON 完整备份;可导入单个文件、多个文件或整个文件夹,静态与云端模式均兼容 |
| 导出静态站 | 「导入/导出」页一键把整站打包成纯静态 ZIP(点击后先弹出系统的「另存为」对话框,由你自己选择保存位置,确认后才开始导出):预渲染首页 / 每篇文章 / 归档 / 标签 / 关于 / 404 页,每个路由都有独立 index.html(任意静态托管都不需要 SPA 重写规则);附带全部静态资源、只含已发布文章的数据、RSS/Sitemap 与部署说明;草稿与定时文章不会导出 |
| 一键导出 | 静态模式特有:编辑器里的「一键导出全部」同时导出 posts.js + feed.xml + sitemap.xml,覆盖 public/ 即发布(云端模式由服务端动态生成 RSS/Sitemap,因此没有导出入口) |
| 响应式 | PC 固定侧栏(可折叠为 72px 图标栏)/ 移动端抽屉导航;断点 1100 / 991 / 640 / 420px |
| 首页(浅色 · 含标签筛选、置顶徽章、封面缩略图) | 文章详情(TOC、表格、代码块) | 站内搜索(关键字高亮 + 句子上下文) |
|---|---|---|
| 标签云 | 归档(按年月分组) | 留言板 |
|---|---|---|
| 首页(深色) | 文章(深色) | 移动端 |
|---|---|---|
| 全站音乐播放器(点击右下角按钮弹出的面板) |
|---|
| 登录门禁(首次部署可用安装密钥初始化) | 仪表盘(6 张统计卡 + 30 天趋势图) | 深色模式 |
|---|---|---|
| 文章管理 | 编辑器(Markdown 实时预览 + AI 写作助手) | 评论管理 |
|---|---|---|
| 媒体资源库 | 音乐管理(行内试听 + 表头吸顶) | 博客设置 | 标签管理 |
|---|---|---|---|
| 首页(浅色) | 文章(浅色) | 文章(深色) |
|---|---|---|
🔧 截图由
scripts/screenshots/capture.mjs用无头 Chrome 打开本地演示服务器(内置示例文章 / 评论 / 音乐数据)自动生成,界面是public/的真实代码,数据为演示内容。 重新生成:npm i -D puppeteer-core && node scripts/screenshots/capture.mjs
├── public/ # 站点本体(静态资源,部署目录)
│ ├── index.html # 页面入口(双击 / 部署起点)
│ ├── config.js / config.min.js # 全站配置(模式 / 站点地址 / 页脚 / 广告)
│ ├── style.css / style.min.css # 前台样式(明暗主题 + 4 种强调色 + 响应式 + 衬线字体栈)
│ ├── app.js / app.min.js # 前台逻辑(路由 / Markdown / 搜索 / 评论 / 留言板 / 统计 / i18n / AI 摘要)
│ ├── manifest.webmanifest # PWA 安装清单
│ ├── sw.js # 离线缓存 Service Worker
│ ├── icons/ # PWA 桌面 / 手机图标
│ ├── admin.js / admin.min.js # 后台管理 SPA(按需懒加载)
│ ├── admin.css / admin.min.css # 后台样式(玻璃拟态,响应式)
│ ├── music-player.js / .min.js # 全站音乐播放器(悬浮按钮 + 面板 + 播放列表 + 进度记忆)
│ ├── music-player.css / .min.css # 播放器样式(不在首屏关键路径,空闲时加载)
│ ├── bg-anim.js / bg-anim.min.js # 四季 canvas 背景动画
│ ├── i18n.js / i18n.min.js # 国际化模块(中/英/日/韩/印地,内置中文兜底)
│ ├── posts.js / posts.min.js # 静态模式文章数据(由「导出 posts.js」生成)
│ ├── static-export.json # 「导出静态站」的资源清单(列出需打包的静态文件)
│ ├── locales/ # 语言包(zh-CN / en / ja / ko / hi,各 993 键)
│ ├── flags/ # 语言切换用的 SVG 国旗(cn / gb / jp / kr / in)
│ ├── libs/smoji/ # Smoji 表情选择器(按需加载)
│ ├── libs/katex/ # KaTeX 数学公式(本地化,按需加载)
│ ├── libs/mermaid/ # Mermaid 图表(本地化,按需加载)
│ ├── robots.txt # 爬虫规则(禁止抓取后台,声明 Sitemap)
│ ├── llms.txt # 面向 LLM / 代理的站点说明
│ ├── ads.txt # 广告声明(可选,配合 config.js ads)
│ ├── _headers # Cloudflare 响应头(CSP / 安全头 / 各类资源缓存策略)
│ ├── _redirects # Cloudflare 路由(SPA 回退 + /public 前缀 301)
│ ├── .well-known/ # ard.json(ARD v0.91)与 ai-catalog.json(兼容入口)
│ # feed.xml / sitemap.xml 由云端动态生成(见 functions/)
├── functions/ # Cloudflare API(Pages Functions / Workers 共用)
│ ├── api/
│ │ ├── posts.js # 文章列表 / 创建(?all=1 含草稿、?full=1 含正文、?page 服务端分页)
│ │ ├── posts/[id].js # 单篇文章 GET / PUT / DELETE(删除级联清理评论与统计)
│ │ ├── posts/[id]/comments.js # 文章评论 GET / POST(3 层嵌套 + 服务端分页)
│ │ ├── posts/[id]/comments/[cid].js # 单条评论删除
│ │ ├── posts/[id]/stats.js # 阅读 / 点赞(同时记录来源、国家、设备、系统、品牌)
│ │ ├── posts/[id]/relations.js # 双向链接 + 相关文章推荐
│ │ ├── posts/[id]/revisions*.js # 文章版本历史:列表 / 单版本 / 恢复
│ │ ├── comments.js # 全局评论列表(后台,可按状态过滤)
│ │ ├── comments/[id].js # 评论审核(通过 / 待审 / 精选 / 置顶)/ 删除
│ │ ├── comments/[id]/like.js # 评论点赞
│ │ ├── ai/{ping,summary,assist,comments}.js # AI 探测 / 摘要 / 写作助手 / 评论汇总与垃圾检测
│ │ ├── media.js · media/upload-url.js · media/[id].js # 媒体库:列表 / R2 直传签名 / 删除
│ │ ├── music.js · music/upload-url.js · music/[id].js # 音乐:列表 / 直传签名 / 重命名 / 删除
│ │ ├── subscribe.js · subscribe/{confirm,unsubscribe}.js # 订阅:申请 / 双重确认 / 退订
│ │ ├── settings.js # 站点设置 GET / PUT(含「功能开关」features)
│ │ ├── search.js # 全文搜索(FTS5 trigram,短词回退 LIKE)
│ │ ├── popular.js # 热门文章排行(全部 / 近 7 天 / 近 30 天)
│ │ ├── stats/trend.js # 近 N 天访问 / 点赞 / 评论趋势
│ │ ├── errors.js # 前端错误上报(公开、限流、按指纹聚合)
│ │ ├── webmention.js # Webmention:接收通知 / 查询某篇文章的引用
│ │ ├── preview.js # 草稿预览(凭 HMAC 签名 token 读取未发布文章)
│ │ ├── site-files/index.js · site-files/[name].js # 站点产物(feed / sitemap):列出 / 下载
│ │ ├── admin/{setup,login,logout,password}.js # 初始化 / 登录 / 登出 / 改密
│ │ ├── admin/{health,audit-log,tags,comments/bulk,post-analytics,og-upload-url}.js # 健康检查 / 审计 / 标签批处理 / 评论批处理 / 文章数据 / 分享图
│ │ ├── admin/{stats/sources,errors,preview-link,webmentions}.js # 来源统计 / 错误日志 / 预览链接 / 引用管理
│ │ ├── admin/{subscribers,subscribers/[id],subscribers/broadcast}.js # 订阅者列表 / 分组 / 群发
│ │ ├── admin/{backups,backups/[id],backups/[id]/restore}.js # 备份列表 / 下载删除 / 恢复
│ │ ├── feed.xml.js · sitemap.xml.js # /api/feed.xml · /api/sitemap.xml
│ │ └── [[path]].js # /api/* 兜底:未知接口一律返回 JSON 404(绝不回退 HTML)
│ ├── feed.xml.js · sitemap.xml.js # 根路径 /feed.xml · /sitemap.xml
│ └── _lib/
│ ├── api-core.js # API 核心:文章 / 评论 / 统计 / 设置 / 鉴权 / 安全头 / 限流 / RSS / Sitemap
│ ├── search.js · popular.js · relations.js · analytics.js # 全文搜索 / 热门 / 关联 / 文章数据
│ ├── subscribe.js # 邮件订阅、订阅者分组、群发与发件箱投递
│ ├── backup.js # R2 备份与恢复(含预恢复快照)
│ ├── media.js · music.js · og.js # R2 直传 / 音乐元数据 / 分享图生成
│ └── ai.js # Workers AI 封装(模型 / 提示词 / 限流 / 缓存 / 降级)
├── worker.js # Cloudflare Workers 入口(路由分发 + 静态资源 + SPA 回退 + 缓存头)
├── migrations/ # D1 迁移(CI 自动执行,记账表幂等)
│ ├── 0001_init.sql # 基础表(posts / comments / stats / admin_auth / admin_se…
│ ├── 0002_site_files.sql # 站点生成产物(feed.xml / sitemap.xml):随文章发布/删除一同写入云端,供下载与备份
│ ├── 0003_cover_column.sql # 封面图列:为已存在部署的 posts 表补列(新部署已含于 0001_init.sql,此脚本幂等)
│ ├── 0004_post_meta.sql # 文章补充字段:分类(category) 与 发布状态(status)
│ ├── 0005_comment_status.sql # 评论补充字段:审核状态(status)
│ ├── 0006_media.sql # 媒体资源库(图片上传库):后台「媒体库」页面使用
│ ├── 0007_settings.sql # 站点设置(键值对):后台「博客设置」页面持久化配置
│ ├── 0008_stats_daily.sql # 每日阅读/点赞聚合:用于后台「近 N 天访问趋势 / 点赞趋势」图表
│ ├── 0009_comment_status_index.sql # 评论审核状态索引(幂等:CREATE INDEX IF NOT EXISTS 可安全重复执行)。
│ ├── 0010_admin_must_change.sql # 管理员认证:新增 must_change 字段
│ ├── 0011_comment_reply.sql # 评论回复功能:添加 parent_id 字段
│ ├── 0012_clear_orphaned_nav.sql # 清理被移除的「导航菜单」配置模块遗留在 site_settings 的 nav 键值。
│ ├── 0013_music.sql # 音乐播放列表(元数据存 D1;音频文件本体在 Cloudflare R2,url 为公开可读地址)
│ ├── 0014_purge_base64_media.sql # 清理历史遗留的「图片存 D1」记录:早期媒体库以 data URL(base64)把图片内容直接存进
│ ├── 0015_hot_path_indexes.sql # 热点路径索引(评论 / 音乐 / 媒体 / 会话)
│ ├── 0016_scheduled_publishing.sql # 定时发布:在 status='scheduled' 时保存预计发布时间(UTC 毫秒时间戳)
│ ├── 0017_post_revisions.sql # 文章版本历史:每篇文章最多保留 50 个快照
│ ├── 0018_backups.sql # R2 备份元数据:备份文件本体存私有 R2_BACKUP_BUCKET
│ ├── 0019_post_series.sql # 文章系列 / 专栏:一篇文章归属一个系列,series_order 控制系列内顺序
│ ├── 0020_subscribers.sql # 邮件订阅与通知发件箱(双重确认 + 异步发送)
│ ├── 0021_post_og_image.sql # 自动分享图(OG Image):R2 公开地址
│ ├── 0022_media_thumb.sql # 媒体缩略图:浏览器端压缩上传时同时生成 WebP 缩略图
│ ├── 0023_post_fts.sql # 文章 FTS5 全文索引(trigram,自动同步 posts)
│ ├── 0024_comment_notifications.sql # 邮件发件箱:区分文章订阅通知与站长评论通知
│ ├── 0025_comment_interactions.sql # 评论互动:点赞、精选、置顶
│ ├── 0026_audit_log.sql # 后台操作审计日志:记录备份/恢复/删除/设置变更等关键操作
│ ├── 0027_stats_sources.sql # 访问来源 / 设备统计:按天聚合
│ ├── 0028_subscriber_groups.sql # 订阅者分组:单列存 JSON 数组字符串(例如 ["newsletter","vip"]),用于后台分组…
│ ├── 0029_error_logs.sql # 前端错误日志(浏览器运行时异常上报,按 fingerprint 聚合去重)
│ ├── 0030_post_seo.sql # 文章级 SEO 覆盖(JSON:{title,desc,canonical,noindex}),空对象表…
│ ├── 0031_webmentions.sql # Webmention:外站引用你的文章时,来源页主动通知本端点,校验后展示
│ ├── 0032_post_author.sql # 文章作者(可留空,前台回退到站点署名 / 个人昵称)
├── scripts/
│ ├── migrate-kv-to-d1.mjs # 一次性迁移:KV 数据 → D1
│ ├── minify.mjs # 用 terser / clean-css 生成 public/ 下的 *.min.*
│ └── screenshots/ # README 截图生成工具(可选,需 puppeteer-core)
│ ├── demo-content.mjs # 截图用示例数据(文章 / 评论 / 音乐 / 媒体 / 设置)
│ ├── demo-server.mjs # 本地演示服务器(静态资源 + mock /api)
│ └── capture.mjs # 无头 Chrome 截图脚本
├── .github/workflows/
│ ├── deploy.yml # GitHub Actions 自动部署到 Workers
│ └── migrate-kv-to-d1.yml # 手动触发 KV → D1 迁移
├── seed.js # 导入示例文章到云端 API
├── index.html # 根跳转页(自动跳 public/index.html)
├── wrangler.toml # Cloudflare Pages 配置
├── wrangler.workers.toml # Cloudflare Workers 配置(部署使用)
├── smoke-test.js # 冒烟测试(178 例)
├── gb-verify.js # 留言板专项验证(18 例)
├── search-verify.js # 搜索专项验证(25 例)
├── docs/ # 项目说明文档(中英双语)
│ ├── README.md # 中文说明(本文件)
│ ├── README_EN.md # 英文说明
│ ├── DEVELOPMENT.md # 开发与架构指南(中文)
│ ├── DEVELOPMENT_EN.md # 开发与架构指南(英文)
│ ├── CLOUDFLARE_SETUP_GUIDE.md # Cloudflare 配置完全指南(新手版·中文)
│ ├── CLOUDFLARE_SETUP_GUIDE_EN.md # 同上 · 英文
│ ├── DEPLOYMENT_SECRETS_GUIDE.md # GitHub Secrets 与 R2 令牌详解·中文
│ ├── DEPLOYMENT_SECRETS_GUIDE_EN.md # 同上 · 英文
│ ├── SECURITY.md / SECURITY_EN.md # 安全策略(中 / 英)
│ ├── CODE_OF_CONDUCT.md # 行为准则 · 中文
│ ├── CODE_OF_CONDUCT_EN.md # 行为准则 · 英文
│ ├── ABOUT.md / ABOUT_EN.md # 项目简介(中 / 英)
│ └── CONTRIBUTING.md # 贡献指南(单文件内中英对照)
└── LICENSE
完整的控制台操作步骤见 CLOUDFLARE_SETUP_GUIDE.md,本节只讲"代码里怎么用"。
- 入口:
worker.js(路由分发);functions/下的文件同时可被 Pages Functions 复用 - 静态资源:
public/通过[assets]绑定提供,SPA 回退 + 缓存头都在 Worker 里处理 - 兼容性日期:
2025-02-01
wrangler.workers.toml 关键配置:
name = "kejiland"
main = "worker.js"
[assets]
directory = "./public"
binding = "ASSETS"
not_found_handling = "single-page-application" # 无扩展名路径回退 index.html
html_handling = "auto-trailing-slash"
[[kv_namespaces]]
binding = "BLOG"
id = "{env.BLOG_KV_ID}" # KV 的 id 不支持 {env.} 内插,CI 部署前用脚本替换
[[d1_databases]]
binding = "DB"
database_name = "blog"
database_id = "{env.BLOG_D1_ID}"
[ai] # Workers AI(绑定名必须是 AI)
binding = "AI"
[vars]
SITE_URL = "{env.SITE_URL}"
CF_ZONE_ID = "{env.CF_ZONE_ID}"静态资源的缓存策略(Worker 里统一设置):
| 资源 | Cache-Control |
|---|---|
带 ?v= 版本号,或 /fonts/、/flags/、/libs/ 下的文件 |
public, max-age=31536000, immutable |
| 其他带扩展名的静态文件 | public, max-age=3600, stale-while-revalidate=86400 |
| 无扩展名的 HTML 入口 | no-cache(靠 ETag 命中 304) |
API 边界:未知 /api/* 一律返回 JSON 404,绝不回退到 index.html;非 GET/HEAD 请求也不会被 SPA 回退。
KV 不是文章存储,而是限流与去重设施:
| Key 模式 | 用途 | TTL |
|---|---|---|
rate:cmt:<ip>:<分钟窗口> |
评论频率限制(5 条/分钟) | 120s |
rate:like:<ip>:<分钟窗口> |
点赞频率限制(10 次/分钟) | 120s |
rate:view:<ip>:<分钟窗口> |
浏览频率限制(30 次/分钟) | 120s |
liked:<ip>:<postId> |
同一 IP 对同一文章只计一次赞 | 30 天 |
viewed:<ip>:<postId> |
同一 IP 对同一文章每小时只计一次浏览 | 1 小时 |
ai:sum:ip:<ip> / ai:sum:day:g |
AI 摘要用量(8 次/时·IP;300 次/天·全站) | 1 小时 / 1 天 |
ai:assist:day:<ip> / ai:cmt:day:<ip> |
AI 写作助手 / 评论工具每日用量 | 1 天 |
ai:sum:<slug>:<lang> / ai:cmt:sum |
AI 摘要(30 天)与评论汇总(1 小时)缓存 | 见左 |
⚠️ KV 是最终一致性存储(全球传播有延迟),只适合计数与缓存;需要强一致性的数据都在 D1。⚠️ KV 未绑定时,上面的限流与去重会静默失效(日志里有一条 warning),所以建议配置。
| 表 | 说明 | 关键字段 |
|---|---|---|
posts |
文章 | id, title, date, excerpt, content, cover, og_image, pinned, protected, enc, tags(JSON), category, series, series_order, author, status, publish_at, seo(JSON) |
comments |
评论 | id, post_id, author, content, date, status(approved/pending), parent_id, likes, featured, pinned |
stats |
阅读 / 点赞 | post_id, likes, views |
stats_daily |
每日聚合(趋势图) | post_id, date, views, likes(PRIMARY KEY(post_id,date)) |
admin_auth |
管理员密码 | k, salt, hash, iter, must_change |
admin_sessions |
登录会话 | token, exp(7 天,绝对时间戳) |
admin_fails |
登录失败限流 | ip, n, until |
media |
媒体元数据 | id, name, url, type, size, created_at |
site_settings |
站点设置 | k, v |
site_files |
站点产物 | name, content, updated_at |
music |
音乐元数据 | id, title, artist, url, cover, size, duration, sort, created_at |
post_revisions |
文章版本历史(每篇最多 50 个快照) | id, post_id, title, content, tags, status, reason, created_at |
subscribers |
邮件订阅者 | id, email, status(pending/active/unsubscribed), token, locale, groups(JSON), confirmed_at |
mail_outbox |
邮件发件箱(文章通知 / 评论通知 / 群发,由 Cron 异步发送) | post_id, to_email, status, kind, payload, sent_at |
backups |
R2 备份元数据 | id, object_key, size, reason, created_at, counts |
audit_log |
后台操作审计 | id, action, target, detail, ip, created_at |
stats_sources |
访问来源聚合(按天) | post_id, date, kind(ref/device/country/platform/vendor), name, views |
error_logs |
前端错误日志(按 fingerprint 聚合去重) | fingerprint, kind, message, source, stack, url, ua, hits, last_at |
webmentions |
外站引用(Webmention) | id, source, target, post_id, author_name, title, excerpt, status, created_at |
posts_fts |
D1 FTS5 全文索引(external-content,自动同步 posts) | id, title, excerpt, content, tags |
schema_migrations |
CI 记账表 | name, applied_at |
迁移按文件名升序由 CI 幂等执行(schema_migrations 记账 + 加列类迁移预检,重复执行安全)。0015_hot_path_indexes.sql 补充了 5 个热点索引:idx_comments_post_id、idx_comments_status_date、idx_music_sort、idx_media_created_id、idx_admin_sessions_exp;0023_post_fts.sql 建 posts_fts 全文索引;0027~0032 陆续加入来源统计、订阅分组、错误日志、文章 SEO、Webmention 与文章作者字段。
文件本体存 R2,D1 只存元数据;上传走浏览器直传,文件不经过 Worker。
| 能力 | 说明 |
|---|---|
| 浏览器直传 | 后端签发 SigV4 预签名 PUT URL(签名含 content-type;host,使用 UNSIGNED-PAYLOAD,有效期 3600 秒),文件直传 R2,前端显示进度 |
| 公开读取 | 绑定 R2 自定义域名(如 music.example.com / media.example.com),前台直接拉流 / 显示图片 |
| 同步删除 | 删除时 Worker 侧用签名发起 R2 DELETE(签名头 host;x-amz-content-sha256;x-amz-date),成功后再删 D1 行 |
| 桶选择 | 图片固定写 R2_MEDIA_BUCKET;音乐优先写 R2_BUCKET,仅当 R2_BUCKET 或 R2_PUBLIC_BASE 为空时回退 R2_MEDIA_BUCKET |
| 音乐白名单 | mp3 / m4a / ogg / oga / wav / aac / opus / flac,单文件 ≤ 30MB |
| 图片白名单 | png / jpg / jpeg / webp / gif / svg / avif / bmp / ico,单文件 ≤ 10MB |
| 降级 | 未配置 R2 凭据时上传接口返回 503;读取与其余功能不受影响 |
| 凭据 | 只有一组:音乐与图片共用 R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY(必须同时有权限访问两个桶) |
💡 缩略图 / 封面 / 音频首次加载偏慢? 浏览器直传的 R2 对象默认不带长缓存头,首次加载需要回源。 建议在 Cloudflare 控制台 → 你的域名 → 缓存 → Cache Rules 新建一条规则: Hostname 匹配
media.你的域与music.你的域→ Cache Everything / Eligible for cache → Edge TTL 与 Browser TTL 都设 1 个月。 保存后第一次访问仍回源一次,之后浏览器与边缘都直接命中。 前端侧也已配合优化:首屏前 2 张缩略图fetchpriority="high"、全部图片loading="lazy" decoding="async"并做加载淡入。
默认模型 @cf/meta/llama-3.2-3b-instruct,按 Neurons 计费,免费额度约 10,000 Neurons/天:
| 接口 | 用途 | 限制 |
|---|---|---|
GET /api/ai/ping |
可用性探测(前端据此显示 / 隐藏全部 AI 入口) | — |
GET/POST /api/ai/summary |
文章摘要(单篇缓存 30 天;force 重新生成需登录,且「重新生成」按钮只有服务端确认的管理员才看得到) |
IP 8 次/时;全站 300 次/天 |
POST /api/ai/assist |
写作助手:标题建议 / 润色 / 翻译(需登录) | 200 次/天 |
POST /api/ai/comments |
评论汇总(缓存 1 小时)与单条垃圾检测(需登录) | 100 次/天 |
- 配置:
wrangler.workers.toml里的[ai] binding = "AI"(部署时自动创建绑定);环境变量BLOG_AI_ENABLED设为0/false/off可整体关闭,BLOG_AI_PUBLIC设为同样值可禁止匿名生成摘要 - 优雅降级:未绑定 AI、未配置 D1 或开关关闭时,相关接口返回 404,前端(
aiProbe)自动隐藏全部 AI 入口,博客其余功能完全不受影响 - 前端记忆策略:探测到「可用」缓存 10 分钟、「不可用」只缓存 30 秒,AI 上线 / 修复后刷新页面即恢复
- 隐私提示:AI 摘要 / 写作助手 / 评论汇总会把相应明文内容发送给 Cloudflare Workers AI 做推理;涉及敏感内容请关闭相关入口
⚠️ [ai]是 Workers 专属绑定。wrangler.toml(Pages 配置)里没有这一项,所以在 Pages 部署下 AI 接口恒为 404。
window.BLOG_CONFIG = {
// ====== 基础配置 ======
mode: 'auto', // 'auto' | 'static' | 'api'
apiBase: '', // API 基础地址,留空 = 同源
siteUrl: 'https://www.example.com', // 站点对外地址(RSS / Sitemap / canonical)
writeToken: '', // 旧版静态令牌(建议用登录替代)
pageSize: 5, // 首页每页文章数(0 = 不分页;非数字时回退 8)
adminPwd: '', // 静态模式本地密码(云端模式请留空)
// ====== 页脚(云端模式下会被 D1 设置覆盖)======
footer: {
text: '',
icp: '', // 备案号
contact: [], // 底部导航(云端由「博客设置 → 底部导航」覆盖)
links: [], // 友情链接(云端由「博客设置 → 友情链接」覆盖)
decl: '', // 站点声明(云端由「页脚声明」覆盖)
email: '', // 联系邮箱(云端由「个人资料 → 邮箱」覆盖)
startYear: 2019, // 版权起始年
copyrightName: "Qingyu'Blog" // 版权署名(云端由「页脚版权署名」覆盖)
},
// ====== 广告位(默认关闭)======
ads: {
enabled: false, // 总开关
client: '', // AdSense 发布商 ID(ca-pub-xxxx),启用时自动加载 adsbygoogle.js
belowSearch: '', // 首页列表上方
between: '', // 列表间隔插入
betweenEvery: 3, // 每 N 篇插入
content: '' // 文章详情底部
}
};提示:「首页每页文章数」(
pageSize)与全部广告位(ads)都可以在后台 设置 → 功能开关 里直接改(写入 D1site_settings.features覆盖这里的默认值,无需改代码、无需重新部署);同一个页面还可以隐藏新增导航项、关闭前端错误上报、评论反机器人、图表 / 公式渲染。
导航项的优先级:云端「博客设置 → 导航菜单」(存 D1 site_settings.nav_menu)> app.js 里的 NAV 兜底数组。底部导航与友情链接同理(D1 优先,config.js 兜底)。
| 值 | 行为 |
|---|---|
'auto' |
推荐。自动检测:请求 /api/posts 成功 → 云端;失败 → 静态 |
'static' |
强制静态模式,只用 posts.js |
'api' |
强制云端模式,需要后端 API |
i18n.js 内置 5 种语言(中文 / English / 日本語 / 한국어 / हिन्दी),每种 993 个语言键。默认按 localStorage('blog.locale') → navigator.language 的顺序识别,并提供手动切换。语言包放在 public/locales/<lang>.json,中文同时内嵌兜底(确保 file:// 本地预览时核心文字始终可读)。
| 层 | 机制 |
|---|---|
| 密码存储 | PBKDF2-SHA256 加盐哈希(100,000 次迭代,16 字节随机盐),永不存明文 |
| 首次部署 | 配置了 BLOG_ADMIN_SETUP_KEY:必须用 X-Setup-Key 显式初始化,未初始化时登录一律 403(防抢注);未配置:首次登录自动生成随机默认密码(xxxx-xxxx,标记 must_change=1),存在先到先得竞态。云端密码最少 8 位;改密前除改密/登出外的后台 API 一律返回 403 PASSWORD_CHANGE_REQUIRED |
| 静态模式 | 密码以 sha256: 前缀哈希存储(兼容旧明文,登录后自动升级),最少 4 位(仅防君子) |
| 会话管理 | 32 字节随机 Token(64 位十六进制),7 天有效,存 D1 admin_sessions;登出即销毁;修改密码会清空所有会话;must_change 会话只能调用改密接口 |
| 登录限流 | 三层维度,且不做全局长锁定:单 IP 5 次失败 → 15 分钟;同一子网(IPv4 /24、IPv6 /64)15 次失败 → 60 秒冷却;全站 30 次失败 → 仅 10 秒冷却 + 告警日志。计数 1 小时老化;锁定期/冷却期内请求直接早退,不查库也不写库(顺带挡住「用失败登录刷爆 D1 免费写额度」) |
| 应急通道 | 登录请求携带正确的 X-Setup-Key(BLOG_ADMIN_SETUP_KEY)可跳过全部限流(不跳过密码校验);后台登录页触发限流时会自动展开该入口。保证「攻击者最多让你 10 秒登不进,但永远挡不死你」 |
| 边缘限流(可选) | 配 BLOG_RATE_LIMIT_BINDING 后启用 Workers Rate Limiting binding(env.LOGIN_LIMITER),在 Worker 入口按 IP 限流,被拦下的请求不查库不写库;也可用 Cloudflare Access 或 WAF 限流规则把 /api/admin/login 挡在边缘,见下方「加固后台登录入口」 |
| 接口鉴权 | 所有写操作校验 Authorization: Bearer <token>;常量时间比较(SHA-256 摘要后异或) |
| 评论安全 | 输入先做控制字符清洗与 HTML 转义、SQL 全参数化、每 IP 频率限制、Origin 校验、重复内容拦截(409)、单篇最多 300 条、最多 3 层嵌套 |
| 文章加密 | AES-GCM-256 + PBKDF2-SHA256(10 万次迭代、16 字节随机盐)纯前端加解密;服务器只存密文 enc,明文永不上传;后台可在本机浏览器记住密码(仅本机可见) |
| 预览分享链接 | HMAC-SHA256 签名(密钥取 BLOG_PREVIEW_SECRET,未配置时由管理员密码哈希派生)+ 过期时间;修改站点密码会让所有已发出的链接立即失效;预览页 noindex、不统计、不加载评论 |
| 评论反机器人 | 隐藏蜜罐字段(被填写即静默丢弃)+ 表单时间戳(提交不足 2 秒判为机器人);对正常访客无感,可在「功能开关」关闭 |
| 错误上报限流 | 公开的 /api/errors 每 IP 每分钟最多 20 次,字段全部截断,按错误指纹聚合去重,最多保留 300 条 |
| 依赖本地化 | KaTeX / Mermaid / Smoji 全部自托管在 public/libs/,CSP 的 script-src 仍限 'self',不引入任何第三方脚本 |
| 媒体 URL | 仅接受 http(s)(R2 公开地址或外链),拒绝 javascript: / data: 等,杜绝脚本类内容登记 |
| 接口边界 | 未知 /api/* 返回 JSON 404,绝不回退到 index.html;非 GET/HEAD 不做 SPA 回退 |
| 安全响应头 | Worker 侧对 API 与静态响应统一注入:CSP、X-Content-Type-Options: nosniff、Referrer-Policy、X-Frame-Options、Cross-Origin-Opener-Policy;Pages 纯静态路径由 public/_headers 提供同一套 |
| CORS | 配置 SITE_URL 后只允许本站来源 + 请求自身来源;未配置时 fail-closed(不返回 ACAO),而不是回显任意来源 |
| 错误信息 | 未捕获异常统一返回「服务端内部错误」,内部细节只进服务端日志 |
| 客户端 IP | 只信任 CF-Connecting-IP,不使用可被伪造的 X-Forwarded-For |
内置限流默认就能挡住爆破,若想从源头再消掉风险,可以在 Cloudflare 边缘加一道门。三种方案任选,都对访客无感:
| 方案 | 怎么做 | 效果 |
|---|---|---|
| Cloudflare Access(最推荐) | Zero Trust → Access → Applications → Add → Self-hosted;给你的域名建两条:Path 分别为 admin 与 api/admin;策略 = Allow → Emails → 你的邮箱;登录方式选 One-time PIN |
没通过身份验证的请求根本到不了 Worker 的登录接口 |
| WAF 限流规则 | 域名 → Security → Security rules → Rate limiting rules → Create rule;匹配 URI Path equals /api/admin/login,10 秒内超过 10 次 → Block 10 秒 |
拦在边缘,不消耗 Worker 请求额度,也不碰 D1/KV 配额 |
| Workers 限流绑定 | 加 GitHub Secret BLOG_RATE_LIMIT_BINDING(正整数,如 1001)后重新部署 |
Worker 入口按 IP 限流 10 次/分钟/位置,被拦下的请求不查库不写库 |
逐步操作(含控制台路径、注意事项与排查)见 Cloudflare 配置完全指南 · 第 9 节。
node smoke-test.js # 冒烟测试 178 例
node gb-verify.js # 留言板专项验证 18 例
node search-verify.js # 搜索专项验证 25 例三套测试都只用 Node 内置模块(无网络、无凭据依赖),在每次 CI 部署前自动运行,失败即中止不部署。 覆盖范围包括:Markdown 渲染 / 目录 / 高亮、导入导出与备份、加密文章(加解密往返 + 锁屏 + 兼容回归)、文章级 SEO、多作者、后台新版 UI 运行时挂载(所有页面逐个挂载,捕获跨作用域变量笔误等运行时报错)、评论(嵌套 / 分页 / 敏感词 / 反机器人 / 批量操作)、订阅与分组群发、Webmention、错误日志、草稿预览链接、图表 / 公式本地化、静态站导出、打印样式、i18n 完整性(5 语言包与兜底键完全一致、无重复键)、PWA、RSS / Sitemap、云端 API 与缓存策略等。
导入示例文章到已部署的云端实例:
node seed.js https://www.example.com [--token <会话或写入令牌>]重新生成 public/ 下的压缩资源:
node scripts/minify.mjs # 需要 npx terser / clean-css-cli| 限制 | 说明 |
|---|---|
| 加密密码无法找回 | 文章加密在浏览器端完成(AES-GCM + PBKDF2),服务器只保存密文;密码仅存于你的浏览器,一旦忘记无法解密,请务必自行备份密码 |
| 编辑文章保留原日期 | 未显式修改日期时沿用原值(服务端与编辑器双重兜底),不会因编辑而跳到最新 |
| 标签改名走服务端批量接口 | 云端为一次请求批量更新(POST /api/admin/tags);静态模式仍需本地逐篇改写后导出 posts.js |
| 被限流时要等一小会儿 | 触发登录限流后,即使密码正确也要等 10 秒(全局冷却)/ 60 秒(同一子网)/ 15 分钟(你自己的 IP)才能登录,除非带上安装密钥走应急通道。这是有意取舍:锁定期间照常跑 PBKDF2 会把「登录 DoS」变成「CPU / 额度 DoS」 |
| 后台列表已服务端化 | 文章列表的搜索 / 状态筛选 / 分页改为服务端查询;其余列表(媒体、音乐、订阅、备份、日志)仍在前端过滤 |
| 图表 / 公式按需加载 | Mermaid 单文件约 2.7 MB(gzip 后约 900 KB)、KaTeX 约 560 KB(含字体),只有正文真的用到才下载并随后被浏览器缓存;不想用可在「功能开关 → 内容渲染」整体关闭 |
| Webmention 需对方主动通知 | 只有支持 Webmention 的来源站主动发送通知才会被收录;来源页事后撤链不会自动删除已有记录,可在后台「引用管理」手动清理 |
| 静态导出不含媒体本体 | 「导出静态站」打包的是页面与静态资源;图片 / 音频若存放在 R2 等对象存储,需保证这些对象仍然可公开访问(ZIP 里不含媒体文件) |
| 预览链接有有效期 | 草稿预览链接默认 7 天有效(可生成 1~30 天),且修改站点密码会让所有已发出的链接立即失效 |
| 访问统计不存原始 IP | 只记录由 Cloudflare 边缘按 IP 解析出的两位国家码,以及 UA 推断出的设备 / 系统 / 品牌;不落原始 IP(隐私优先) |
| 错误日志有上限 | 按「相同错误」聚合计数,最多保留最近 300 条;超过后自动淘汰最旧的记录 |
| 未知路径返回 404 | 已知前端路由(首页 / 归档 / 标签 / 关于 / 留言板 / 热门 / 订阅 / 系列 / 文章 / 后台)仍 200;其余无扩展名路径返回真实 404 状态并渲染「内容不存在」页 |
所有文档均提供中英双语:长文档采用 X.md + X_EN.md 成对文件,短文档(贡献指南、Issue / PR 模板)在单个文件内中英对照(LICENSE 除外——MIT 许可证原文为英文,属法律文本,不做翻译)。
| 中文 | English | 内容 |
|---|---|---|
| README.md | README_EN.md | 项目总览:快速开始、功能、目录结构、Cloudflare 服务、配置、安全、测试 |
| DEVELOPMENT.md | DEVELOPMENT_EN.md | 开发与架构:运行时结构、目录职责、API 地图、D1 数据模型、配置、测试、发版与扩展 |
| CLOUDFLARE_SETUP_GUIDE.md | CLOUDFLARE_SETUP_GUIDE_EN.md | Cloudflare 配置完全指南(新手版):账号 / D1 / KV / API Token / R2 / Workers AI / 自定义域名 / Secrets / 部署 / 自检 / 排查 / 免费额度 |
| DEPLOYMENT_SECRETS_GUIDE.md | DEPLOYMENT_SECRETS_GUIDE_EN.md | 只聚焦 GitHub Secrets 与 R2 令牌:每个 Secret 从哪来、怎么填、两个桶用一个还是两个 Token |
| SECURITY.md | SECURITY_EN.md | 安全问题反馈方式与内置安全措施 |
| CONTRIBUTING.md | 同一文件(文内中英对照) | 参与贡献的流程 |
| CODE_OF_CONDUCT.md | CODE_OF_CONDUCT_EN.md | 社区行为准则 |
| ABOUT.md | ABOUT_EN.md | 关于项目与作者 |
如果 Qingyu'Blog 对你有帮助,欢迎 ⭐ Star / Fork,或到 Issues 提建议。
如果你觉得不错,请给个 ⭐ Star 支持一下!这会帮助更多人发现这个项目。