> 🌐 **中文** · [English](README_EN.md)

Qingyu'Blog

Qingyu'Blog

零框架 · 零构建 · 零依赖 —— 双击 index.html 就能用的个人博客

Demo MIT License Vanilla JS Cloudflare Workers

GitHub Stars GitHub Forks GitHub Issues GitHub Pull Requests

Last Commit Commit Activity PRs Welcome Issues Welcome License

--- ## 📖 项目介绍 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.109`**。除写作 / 评论 / 统计等基础能力外,还内置:**文章加密**(AES-GCM 纯前端)、**文章级 SEO**(标题 / 描述 / canonical / noindex)、**草稿预览分享链接**(HMAC 签名)、**一键导出静态站**、**打印 / 导出 PDF**、**Webmention**、**多作者与作者页**、**Mermaid 图表 + KaTeX 公式**(本地化按需加载)、**订阅分组群发**、**前端错误日志**、**评论反机器人**、**访问国家 / 设备识别**,以及后台「**功能开关**」等。 > 🖼️ 首页有两套主题:**经典**(紧凑列表)与 **编辑部杂志**(精选大图 + 双栏 + 实时时间天气),在「后台 → 博客设置 → 站点基础信息 → 首页风格」用可视化卡片随时切换。 > 完整变更历史见仓库 [提交记录](https://github.com/kejiland/qingyu-blog/commits/main)。 > 💡 仓库根目录的 `index.html` 只是一个跳转页,会自动打开 `public/index.html`(Workers / Pages 的部署目录)。本地双击 `public/index.html` 同样可用。 > 🆕 **第一次部署 Cloudflare?** 请直接看 **[Cloudflare 配置完全指南(新手版)](CLOUDFLARE_SETUP_GUIDE.md)** —— 从注册账号、创建 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 引用、多作者页——写作与分发闭环 | | **可观测** | 后台仪表盘(趋势 / 来源 / 国家 / 设备 / 品牌)、站点健康检查、操作审计日志、**前端错误日志**(自动捕获访客端异常并按相同错误聚合) | --- ## 🚀 快速开始 ### 方式一:本地静态(零安装) ```bash git clone https://github.com/kejiland/qingyu-blog.git cd qingyu-blog ``` 双击 `public/index.html`,或启动本地服务器: ```bash # Python python -m http.server 8080 -d public # Node.js npx serve public ``` 打开 `http://localhost:8080/admin`,设置密码即可开始写作。 ### 方式二:Cloudflare Workers(推荐·正式发布) 只需要四件事:**创建 D1 数据库 → 创建 KV 命名空间 → 申请 API Token → 填进 GitHub Secrets**,剩下的交给 GitHub Actions。 ```bash # 登录 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,工作流会自动: 1. ✅ 安装 Wrangler CLI 2. ✅ 运行三套测试(`smoke-test.js` 183 例 / `gb-verify.js` 18 例 / `search-verify.js` 25 例,失败即中止不部署) 3. ✅ 校验必要 Secrets 与 ID 格式 4. ✅ 执行 D1 迁移(`schema_migrations` 记账表 + 列预检 + 报错兜底,三层幂等) 5. ✅ 部署 Worker 到 Cloudflare 6. ✅ 写入运行时 Secret(安装密钥 / R2 凭据 / 清缓存凭证,配置了才写) 部署完成后访问 `https://你的域名/admin`:配置了安装密钥就点「首次部署?使用安装密钥初始化」;没配置就用任意密码登录一次、拿页面上弹出的随机默认密码登录,然后立刻改密。 > 详细的每一步(含控制台截图级别的路径说明、权限勾选表、R2 CORS 配置、故障排查速查表)见 **[CLOUDFLARE_SETUP_GUIDE.md](CLOUDFLARE_SETUP_GUIDE.md)**。 > 只想搞清 Secrets 与 R2 令牌的区别,看 **[DEPLOYMENT_SECRETS_GUIDE.md](DEPLOYMENT_SECRETS_GUIDE.md)**。 #### 从 KV 迁移到 D1(旧数据) 若你此前使用 KV 单 key 存储,可把线上数据搬到 D1: ```bash # 本地执行(需 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**,无首屏字体下载 | | **老文章兼容** | 早期从 WordPress 导入的文章正文里混着原始 HTML:` ` 等实体会被还原成真实字符(不再是"字面的  ")、`` 等注释自动清除、``/`` 等 Flash 播放器整块移除、`font`/`span`/`div`/`p`/`a`/`img` 等老排版标签转成正常段落。围栏代码块里的示例 HTML 不受影响,`