← 返回首页

(自开发)Cloudflare Workers 博客系统 V3.9完整部署方案

> 版本:V3.9(上线版)

> 发布日期:2026-08-06

> 核心架构:Cloudflare Workers + D1(数据库)+ KV(配置/会话/限流)+ R2(图片/主题/搜索索引)+ Cache API + JWT 无状态认证

> 版本定位:以 V3.8(七轮安全审查 + 三份外部审查定稿,代码冻结)为基座,叠加纯前端美化层 + 一处遗留 bug 修复的正式上线版本。后端逻辑与 V3.8 完全一致(65 个函数集合相同,14 个关键函数体哈希一致),可放心上线。

---

目录

  1. 版本演进与上线版定位
  2. 系统概览与架构
  3. 环境准备
  4. 资源创建(D1 / KV / R2)
  5. 数据库初始化(完整 SQL)
  6. 环境变量配置(必读)
  7. Worker 部署与资源绑定
  8. KV 系统配置
  9. 主题模板部署(V3.0 架构 + V3.9 美化)
  10. 前端外观自定义(V3.9 新增)
  11. Cron 定时任务配置
  12. 安全机制说明(与 V3.8 一致 + V3.9 补遗)
  13. 缓存策略与性能优化
  14. 功能验证清单
  15. 从旧版本升级指南
  16. 故障排查指南
  17. 免费额度与资源用量参考

---

1. 版本演进与上线版定位

版本日期核心变更
V3.02026-07-29基础架构(Workers+D1+KV+R2)、模板分离(R2 托管主题)
V3.12026-07-30草稿箱(status 字段)、统计系统(GraphQL Analytics)、后台分页/批量删除、图片压缩
V3.22026-07-31搜索索引(R2 JSON)、首页列表缓存、自动后台任务
V3.3-评论 CSRF、Markdown XSS 修复、Turnstile、导出剔除密码、搜索限长
V3.52026-08-02JWT 无状态认证、KV 配置内存缓存、R2 静态搜索索引、草稿箱完善
V3.6-KV 合并为 site_config 单键、Cron 定时索引、9 项安全加固、手动刷新按钮
V3.7-XSS 防御体系补全(sanitizeHtml/highlightSafe/escapeJsString)、后台分页修复、核心路由补全、render 自动转义
V3.82026-08-04七轮安全审查收尾(CSRF 全局化、匿名评论机制重构、缓存流修复、导入分批、登录锁定、旧密码自动升级等)
V3.92026-08-06上线版:在 V3.8 冻结代码上叠加纯前端美化层(布局/主题/配色切换、暗色模式、阅读体验、微交互、文章页 TOC + 阅读时长),并修复「草稿(status=draft)泄露到分类页与随心说侧栏」遗留 bug。后端逻辑 0 改动。

V3.9 与 V3.8 相比的核心变化:

  • ✅ 前端外观自定义:左下角 🎨 浮动面板,支持三套偏好切换并 localStorage 持久化:
  • 布局:三栏(左栏 + 主内容 + 右栏)/ 顶栏(横向导航吸顶)
  • 主题:亮色 / 暗色(暗色自动跟随系统 prefers-color-scheme,也可手动锁定)
  • 配色:蓝 / 青 / 绿 / 紫 / 粉(与明暗正交,共 10 种组合)
  • ✅ 阅读体验优化:正文限宽 720px、行高 1.85、代码块深色高亮观感 + 等宽字体、引用块/标题层级美化、图片圆角阴影。
  • ✅ 微交互:阅读进度条、滚动淡入(reveal)、平滑滚动、自定义滚动条、键盘焦点可见、prefers-reduced-motion 无障碍降级、回到顶部按钮(右下角)。
  • ✅ 文章页增强:目录 TOC(右侧浮层吸顶 + 滚动高亮 scrollspy)、阅读时长徽章、h2–h4 标题锚点、返回首页链接。
  • ✅ Bug 修复(上线前补丁):handleCategory 的 6 条 SQL + 随心说侧栏 2 条查询补 status = 'published' 过滤,草稿不再出现在任何前台列表/分类页/侧栏。
  • ✅ 修复一处部署语法错误(误删 const layoutData = { 行,已恢复,node --check 通过);清理无用 CSS(Hero/标签云/旧侧栏切换按钮);🎨 切换面板位于左下角(避免与右下角回到顶部按钮重叠)。

> ⚠️ V3.9 不做的事:不开 V3.10;不引入任何第三方 JS 库(代码高亮按"不引库"决策做深色观感,非逐语言语法着色);核心安全/后端逻辑零改动。

---

2. 系统概览与架构

2.1 系统特点

  • 零运维:全球边缘节点部署,自动扩缩容,无需管理服务器。
  • 高性价比:全部依赖 Cloudflare 免费额度,个人博客零成本。
  • 极速响应:边缘缓存 + KV 内存缓存 + JWT 无状态认证。
  • 功能完善:Markdown 写作、草稿箱、文章加密、评论审核、图片上传压缩、数据备份、访问统计、全文搜索。
  • V3.9 新增:前端外观自定义(布局/主题/配色三件套,纯前端持久化,无需后端改造)。

2.2 技术架构

graph TD
    A[用户请求] --> B{Workers 运行时}

    B -->|静态资源| C[Cache API 边缘缓存]
    B -->|图片/主题请求| D[R2 鉴权代理 /r2/*]
    B -->|动态请求| E[Worker 逻辑]

    E -->|认证| F[JWT 签名验证]
    E -->|配置读取| G[site_config 内存缓存]
    E -->|文章/评论查询| H[D1 数据库]
    E -->|搜索请求| I[R2 search_index.json]
    E -->|评论/登录防护| J[Turnstile + KV 限流]

    K[Cron 每日定时] --> L[重建搜索索引 + 首页缓存]

2.3 数据存储职责

存储职责
D1文章、分类、评论(核心数据)
KV站点配置(site_config)、管理员凭据、加密文章解锁态、登录限流、统计缓存
R2主题模板、文章图片、头像、搜索索引
Cache API首页/分类页/文章页 HTML 边缘缓存
浏览器 localStorageV3.9 新增:外观偏好(layout/theme/accent)持久化

---

3. 环境准备

3.1 必备条件

  • Cloudflare 账号:免费计划即可。
  • 域名:必须使用托管在 Cloudflare 的域名(用于绑定 Worker 和统计功能)。
  • 浏览器:Chrome / Firefox / Safari 等现代浏览器。
  • 编辑器:VS Code + Cloudflare Workers 插件,或直接使用 Dashboard 在线编辑器。

3.2 服务检查

确认账号已开通以下服务(均在免费额度内):

  • Workers & Pages
  • D1 SQL Database
  • KV
  • R2 Object Storage

---

4. 资源创建(D1 / KV / R2)

请按顺序创建并记录所有 ID。

4.1 创建 D1 数据库

  1. 登录 Cloudflare Dashboard。
  2. 左侧栏选择 Workers & Pages → D1 SQL Database。
  3. 点击 Create database,名称输入 blog-db,点击 Create。
  4. 复制并保存 Database ID(格式:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)。

4.2 创建 KV 命名空间

  1. 左侧栏选择 Workers & Pages → KV。
  2. 点击 Create a namespace,名称输入 blog-config,点击 Add。
  3. 复制并保存 Namespace ID(格式:32 位十六进制字符串)。

4.3 创建 R2 存储桶

  1. 左侧栏选择 R2 Object Storage → Create bucket,名称输入 blog-images。
  2. 安全配置(必须):进入 Bucket → Settings → Public access,确保 Public Access 开关处于关闭状态。所有资源通过 Worker 代码(/r2/* 路由)鉴权代理访问。
  3. 目录预创建:点击 Upload → Create Folder,创建:
   blog/themes/default/
   blog/themes/default/admin/

---

5. 数据库初始化(完整 SQL)

在 D1 控制台(blog-db → Console)一次性全部执行以下脚本。整合了 V3.0 基础结构 + V3.1 status 字段 + V3.2 性能索引。

-- =============================================
-- 1. 分类表
-- =============================================
CREATE TABLE IF NOT EXISTS categories (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL UNIQUE,
  slug TEXT NOT NULL UNIQUE
);

-- =============================================
-- 2. 文章/说说表(含 V3.1 草稿功能 status 字段)
-- =============================================
CREATE TABLE IF NOT EXISTS posts (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT,
  content TEXT NOT NULL,
  cover_image TEXT,
  category_id INTEGER,
  password TEXT,
  type TEXT DEFAULT 'post',             -- 'post'(文章) / 'memo'(说说)
  is_top INTEGER DEFAULT 0,             -- 0=普通, 1=置顶
  status TEXT DEFAULT 'published',      -- 'published'(已发布) / 'draft'(草稿)
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
);

-- =============================================
-- 3. 评论表
-- =============================================
CREATE TABLE IF NOT EXISTS comments (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  post_id INTEGER NOT NULL,
  author_name TEXT NOT NULL,
  content TEXT NOT NULL,
  status TEXT DEFAULT 'pending',        -- 'pending'(待审核) / 'approved'(已通过)
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE
);

-- =============================================
-- 4. 性能优化索引(强烈推荐)
-- =============================================
CREATE INDEX IF NOT EXISTS idx_posts_status ON posts(status);
CREATE INDEX IF NOT EXISTS idx_posts_type ON posts(type);
CREATE INDEX IF NOT EXISTS idx_posts_category ON posts(category_id);
CREATE INDEX IF NOT EXISTS idx_posts_created ON posts(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_posts_is_top ON posts(is_top DESC);
CREATE INDEX IF NOT EXISTS idx_comments_post_id ON comments(post_id);
CREATE INDEX IF NOT EXISTS idx_comments_status ON comments(status);
CREATE INDEX IF NOT EXISTS idx_comments_created ON comments(created_at DESC);

-- =============================================
-- 5. 自动更新时间触发器(可选,推荐)
-- =============================================
CREATE TRIGGER IF NOT EXISTS update_posts_timestamp
AFTER UPDATE ON posts
BEGIN
  UPDATE posts SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END;

> 升级场景:若已有旧表,只需执行 ALTER TABLE posts ADD COLUMN status TEXT DEFAULT 'published'; 及索引语句,无需重复建表。V3.9 与 V3.8 表结构完全一致,从 V3.8 升级无需改表。

---

6. 环境变量配置(必读)

进入 Worker → Settings → Variables and Secrets 配置:

变量名类型必填说明示例值
JWT_SECRETSecret(加密)✅ 必须JWT 签名密钥,必须 ≥32 位强随机字符串。缺失或使用默认值将拒绝登录(代码主动拦截)aB3xK9mP7wR2yU5zF8jL4nQ1tV6cX0eS
CF_ZONE_IDText✅ 是域名 Zone ID(统计功能),域名概览页右侧可找到abc123...
CF_API_TOKENSecret(加密)✅ 是统计 API Token,权限 Zone > Analytics > Readxxxx...
CF_STATS_HOSTText❌ 否指定统计的子域名(留空统计全 Zone)blog.example.com

生成 JWT_SECRET 的建议命令(任选其一):

# Linux/macOS
openssl rand -base64 48
# Windows PowerShell
[Convert]::ToBase64String((1..48 | ForEach-Object { Get-Random -Maximum 256 }))

创建 CF_API_TOKEN 步骤:

  1. 右上角头像 → 我的个人资料 → API 令牌 → 创建令牌 → 自定义令牌。
  2. 权限:Zone → Analytics → Read;资源:您的域名或所有区域。
  3. 创建后立即复制保存(只显示一次)。

> ⚠️ V3.9 特别提醒:Turnstile 的 Site Key / Secret Key 若配置,必须成对配置(只配一个会导致验证静默跳过)。主题/布局/配色切换是纯前端功能,不依赖任何新增环境变量。

---

7. Worker 部署与资源绑定

7.1 创建 Worker

  1. Workers & Pages → Overview → Create Worker。
  2. 名称输入 my-blog,点击 Deploy,再点击 Edit code。

7.2 绑定资源(关键,大小写敏感)

在 Worker 详情页 → Settings → Variables → Bindings:

绑定类型Variable name选择资源说明
D1 DatabaseDBblog-db数据库连接
KV NamespaceCONFIGblog-config配置/会话/限流存储
R2 BucketBUCKETblog-images图片与主题模板存储

> ⚠️ Variable name 必须与表格完全一致(大小写敏感),否则代码报错。

7.3 部署代码

  1. 将 V3.9 最终版 worker代码.txt(位于 v3.9模板分离/ 目录)完整粘贴到编辑器(覆盖默认代码)。
  2. 点击 Save and Deploy。
  3. 记录分配的 *.workers.dev 域名。

> 📌 V3.9 的 Worker 源码与 V3.8 逻辑完全一致,仅多出前端美化相关的 globalScript 注入与 8 条 status='published' 查询过滤,已通过 node --check 语法校验与 v3.8→v3.9 逻辑一致性 diff 验证。

7.4 绑定自定义域名(推荐)

  1. Worker 详情页 → Triggers → Custom Domains → Add Custom Domain。
  2. 输入域名(如 blog.example.com)。Cloudflare 自动添加 DNS 记录、申请并续期 SSL 证书。

---

8. KV 系统配置

访问 Workers & Pages → KV → blog-config。

8.1 必须手动添加的键

KeyValue说明
admin_useradmin管理员用户名
admin_passYourPassword123!管理员初始密码(≥8 位)。建议直接设明文,首次登录后自动升级为 PBKDF2 哈希(格式 100000:saltHex:hashHex)

8.2 站点配置(site_config 单键,V3.6 起)

系统配置已合并为单个 JSON 键,无需再逐个添加 7 个独立键。可通过 KV 界面手动写入,或在后台设置中修改:

{
  "siteName": "我的博客",
  "siteDesc": "记录生活与技术",
  "footerText": "Powered by Cloudflare Workers",
  "avatarUrl": "",
  "pageSize": 10,
  "turnstileSiteKey": "",
  "turnstileSecretKey": ""
}
字段默认值说明
siteNameBLOG网站名称
siteDesc记录生活网站简介
footerTextPowered by Workers页脚版权(支持 HTML)
avatarUrl""博主头像(后台上传自动写入)
pageSize10每页文章数
turnstileSiteKey""Turnstile 公钥(与 Secret 成对)
turnstileSecretKey""Turnstile 私钥

> 首次部署新代码时自动检测旧版 7 个独立键并迁移到 site_config,无需手动迁移。旧键保留(便于回滚),1~2 周后可清理。

8.3 系统自动维护的键(无需手动创建)

键名TTL用途
cache:home_list_raw3600s首页列表缓存
post_session:{postId}86400s加密文章解锁态(token + IP 双重绑定)
login_lock:{ip}3600s登录 IP 锁定(连续失败 3 次触发)
login_attempts:{ip}3600s登录失败计数
rate:{ip}60s评论频率限制
stats_total_pv永久累计访问量
stats_last_sync_date永久统计同步日期
dashboard_cache1800s后台统计面板缓存

> 说明:post_auth_{postId} 是浏览器 Cookie(非 KV 键),由 /verify/:id 解锁成功后下发(HttpOnly + Secure + SameSite=Strict,TTL 24 小时),与 KV 中的 post_session:{postId} 配对使用。

8.4 已废弃键(V3.8 起可清理)

键名说明
session:{sessionId}V3.5 起被 JWT 替代,可删除
comment_token:{ip}V3.8 起匿名 CSRF 机制整体移除,此键不再写入,旧键 TTL 到期自动清理

---

9. 主题模板部署(V3.0 架构 + V3.9 美化)

V3.0 起模板从代码中分离,存储于 R2。必须上传 6 个模板文件,否则前端无法渲染。

9.1 上传路径

blog/
└── themes/
    └── default/
        ├── style.css          # 全局样式(V3.9 重写:CSS 变量 + 暗色 + 配色 + 布局 + 微交互)
        ├── layout.html        # 前台骨架模板(三栏布局;V3.9 新增阅读进度条 + 左下角🎨切换面板 DOM)
        ├── sidebar.html       # 左侧栏模板(头像/站点名/分类导航;V3.9 删除旧侧栏🌙切换按钮)
        ├── memo_feed.html     # 右侧"随心说"卡片列表
        ├── footer.html        # 页脚模板
        └── admin/
            └── layout.html    # 后台管理骨架模板(独立)

> V3.9 相对 V3.8 改动了 3 个模板文件:style.css、layout.html、sidebar.html(另 3 个 memo_feed.html / footer.html / admin/layout.html 可沿用 V3.8 版,但为一致性建议 6 个一并上传)。

9.2 占位符系统

模板使用 <!--{variable}--> 语法(支持空格,如 <!-- { key } -->),由 Worker 动态替换。安全相关:

  • 默认所有占位符值 自动 HTML 转义(防 XSS),仅以下白名单键保留原始 HTML:style、scripts、head_extra、script_global、script_extra、meta_image,以及 _html 后缀的键(如 content_html)。
  • 后台模板占位符 <!--{content}--> 已改名为 <!--{content_html}-->(V3.7 配套),请使用新版模板文件,勿混用旧模板。

9.3 日常修改

  • 访问 https://您的域名/admin/theme 在线编辑模板,保存即热加载(无需重新部署 Worker)。
  • 安全限制:只能编辑白名单内的 6 个模板文件;保存时校验占位符开闭数量一致性;含 <script> 时记录审计日志(CSP 限制执行)。
  • 修改后如未生效,点击后台右上角 🚀 刷新缓存(或「主题管理 → 刷新系统缓存」)。
  • V3.9 提示:外观切换面板的 JS 由 Worker 的 globalScript(nonce) 注入(CSP script-src 要求所有内联脚本带 nonce),模板中无需、也不应手写 <script> 来实现切换逻辑。

---

10. 前端外观自定义(V3.9 新增)

V3.9 的外观自定义是纯前端能力,无需任何后端配置或新增环境变量。

10.1 三套偏好

偏好取值持久化键(localStorage)默认
布局 layoutthree(三栏)/ top(顶栏)blog_layoutthree
主题 themelight / darkblog_theme跟随系统(prefers-color-scheme)
配色 accentblue / cyan / green / purple / pinkblog_accentblue
  • 三套偏好通过 globalScript(带 nonce)在页面加载时读取,套用到 <html> 的 data-layout / data-theme / data-accent 属性上。
  • 布局使用 flex 双层结构(.layout-wrapper → .sidebar-left + .content-row(.main-content + .sidebar-right));顶栏态 data-layout="top" 下侧栏横排吸顶。
  • 主题/配色通过 CSS 变量(--accent-color 等)驱动,暗色用 :root[data-theme="dark"] 与 @media (prefers-color-scheme: dark) 双通道。

10.2 切换面板

  • 页面左下角浮动 🎨 按钮 → 展开面板,可点选布局/主题/配色;点击面板外部自动收起。
  • 回到顶部按钮固定在右下角,与 🎨 面板(左下角)不重叠。
  • 所有选择即时生效并写入 localStorage,刷新后保留。

10.3 文章页增强

  • TOC 目录:当正文含 ≥2 个 h2–h4 标题时,右侧浮层显示目录,点击平滑滚动 + 当前章节高亮(IntersectionObserver scrollspy)。首页/列表页无标题,挂载点为空,无害。
  • 阅读时长:按正文纯文本字数 / 400 估算「约 N 分钟阅读」徽章。
  • 标题锚点:h2–h4 自动加 id="h-N" 锚点。
  • 返回首页链接:文章页顶部「← 返回首页」。

10.4 微交互

阅读进度条(顶部细条)、滚动淡入(.reveal,JS 启用时才隐藏待淡入,JS 故障时默认可见防白屏)、平滑滚动、自定义滚动条、键盘焦点可见、prefers-reduced-motion 降级。

---

11. Cron 定时任务配置

11.1 作用

每日定时重建搜索索引(search_index.json)并刷新首页列表缓存。不计入每日请求额度。

11.2 配置步骤

  1. Worker 详情页 → Triggers(触发器) → Cron Triggers → Add Cron Trigger。
  2. 推荐表达式(Cloudflare Cron 为 UTC 时间,北京时间 = UTC+8):
目标执行时间(北京时间)Cron 表达式(UTC)
凌晨 4:0020 20 *
中午 12:000 4 *
午夜 0:0016 16 *
  1. 保存后,Worker 的 scheduled(event, env, ctx) 每天自动执行:generateSearchIndex() + updateHomeListCache()。

11.3 手动触发

后台新增 🔄 手动刷新索引 按钮,立即重建搜索索引 + 首页缓存 + 清除首页边缘缓存。发布重要文章后建议手动点击,无需等次日 Cron。

> 注意:V3.7 起发布/更新/删除文章不再实时重建搜索索引(改为 Cron + 手动),但文章仍立即可在首页/分类页看到(写操作会清除相关页面边缘缓存)。V3.9 此机制不变。

---

12. 安全机制说明(与 V3.8 一致 + V3.9 补遗)

> V3.9 安全架构 = V3.8,经七轮安全审查 + 三份外部审查报告核实,核心安全架构全部正确。以下为与 V3.8 一致的要点;V3.9 唯一的后端安全改动是草稿泄露修复(12.8)。

12.1 认证体系

  • JWT 无状态认证:HMAC-SHA256 签名,符合 RFC 7519(base64url 编码标准化)。
  • JWT_SECRET 强制校验:缺失或使用默认值时拒绝签发 token 并告警。
  • 登录 IP 锁定:连续失败 3 次 → 写入 login_lock:{ip}(TTL 3600s),锁定期间拒绝登录。
  • 旧密码自动升级:兼容旧版明文密码(verifyPassword 返回 needsUpgrade),登录成功后自动重哈希为 PBKDF2 并写回 KV。

12.2 密码安全

  • PBKDF2-HMAC-SHA256,迭代 100,000 次,16 字节随机盐,256 位输出。
  • 存储格式:100000:saltHex:hashHex。
  • /change_password:先 PBKDF2 验证旧密码 → 新密码 ≥8 位 → 新旧密码不得相同 → 修改成功后强制重新登录。

12.3 CSRF 防护(全局化)

  • 后台所有 POST 端点统一在 handleAdminRoute 前置校验 _csrf 与 session.csrf,失败返回 403 + 审计日志。
  • /purge_cache 为 POST,纳入 CSRF 保护。
  • 评论提交:管理员强校验 session.csrf;匿名用户跳过 CSRF(机制重构——原一次性 comment_token:{ip} 机制整体移除,避免与页面缓存冲突),匿名安全由 Turnstile + 60 秒 IP 限流兜底。
  • 评论表单:登录态渲染真实 csrf,匿名态渲染空值(防止 token 固定)。

12.4 XSS 防御体系

层面机制
Markdown 链接sanitizeUrl 白名单协议(http/https/mailto,其余替换 #)
评论输出sanitizeHtml 白名单净化(标签 + 属性双重白名单,移除 on* 事件)
样式属性sanitizeStyle 安全属性白名单
搜索高亮highlightSafe 分段转义后插入 <mark>(防反射型 XSS)
动态脚本escapeJsString 全量转义 alert(msg) 等注入点
模板渲染render() 默认转义所有占位符,仅白名单键输出原始 HTML
全局转义parseMarkdown 开头统一转义,代码块无双重转义

12.5 CSP 与响应头

  • 所有页面经 getSecurityHeaders() 注入安全头:X-Content-Type-Options: nosniff、X-Frame-Options: DENY、Referrer-Policy、Permissions-Policy、CSP(default-src 'self';script-src 含 'nonce-${nonce}' + jsdelivr + challenges.cloudflare.com,无 unsafe-inline;style-src 含 'unsafe-inline')。
  • 所有内联脚本(全局脚本 globalScript、EasyMDE、上传、导入、主题保存/刷新等)均带 nonce="${nonce}"。V3.9 的切换面板/进度条/淡入/TOC/回到顶部 JS 全部经 globalScript(nonce) 注入,符合 CSP。

12.6 上传安全

  • 魔数校验 checkMagicBytes:JPG(FF D8 FF)、PNG(89 50 4E 47)、GIF(含版本号 87a/89a)、WebP(RIFF + WEBP 完整签名);ext 空值防御校验。
  • 大小限制 ≤10MB;前端自动压缩至 1920px 宽、JPEG 质量 0.8。
  • 文件名:{时间戳}-{随机}.{ext},按 blog/image/{YYYYMM}/ 组织。
  • R2 路径规范化:移除 ..、强制 blog/ 前缀,否则 403。

12.7 内容安全

  • 加密文章(post.password):密码验证 → post_session:{id}(token + IP 双重绑定)→ Cookie 凭证 post_auth_{id}(Secure + SameSite=Strict)。加密文章不参与边缘缓存(防缓存泄露)。
  • 搜索:关键词限 50 字符;搜索索引 content 截断 3000 字符,最多返回 20 条。
  • 备份导出:JSON 剔除 password 字段(防离线爆破)。

12.8 V3.9 补遗:草稿泄露修复

  • 问题:草稿(status='draft')会出现在分类页(/category/xxx)与首页右侧「随心说」侧栏(V3.8 即有此遗留 bug,非 V3.9 引入)。
  • 根因:handleCategory 的 6 条 SQL 仅按 type/category_id/categories.name 过滤,缺 status='published' 条件;首页列表用的是 SQL_WHERE_PUBLISHED_POST(含状态过滤),两侧不一致。
  • 修复:在 handleCategory 的 6 条查询 + 随心说侧栏 2 条查询中追加 status = 'published',共 8 条。草稿不再出现在任何前台列表/分类页/侧栏。后台管理列表(/admin?status=draft)过滤独立,不受影响。
  • 生效提醒:分类页/首页走 handleCache 边缘缓存,修复后需后台刷新系统缓存(或等缓存自然过期)才立即生效。

12.9 数据安全

  • 导入分批:BATCH_SIZE = 100 循环 env.DB.batch(),突破免费版 batch 语句上限(约 100 条),大备份可完整导入。
  • 导入完整性:文章导入还原 is_top 置顶状态,备份还原后置顶顺序与导出时一致。
  • 统一错误页:renderErrorPage 隐藏堆栈,仅显示错误编号。
  • 审计日志:主题保存、密码修改、CSRF 失败等均通过 auditLog() 记录 JSON 格式输出至 Worker 日志;备份导出 JSON 含 version: '2.0' 标识。

---

13. 缓存策略与性能优化

13.1 缓存层级

浏览器缓存 → 边缘缓存(Cache API)→ Worker 动态生成 → D1/R2 数据

13.2 缓存规则

内容策略
首页/分类页/无密码文章页handleCache:public, max-age=86400, stale-while-revalidate;带 Cookie 请求(管理员)直接跳过缓存动态渲染
加密文章页不缓存(动态渲染)
搜索索引(R2 JSON)边缘缓存 1 小时
R2 图片(/r2/*)缓存 1 年
后台/登录/评论提交/搜索不缓存

> V3.9 的外观切换(layout/theme/accent)是客户端 localStorage + CSS 变量,不参与服务端缓存,切换瞬时生效、各用户独立。

13.3 缓存写入正确性

  • handleCache 中 cache.put(request, finalResponse.clone()):clone 内部 tee 分流,缓存与客户端各持一个分支,首次访问(miss)不再白屏;非 200 响应不写入缓存。
  • Cookie 跳过判断位于 cache.match 之前:管理员请求永不命中公共缓存,也不会把含 CSRF 的管理员页面写入公共缓存。

13.4 缓存失效

  • 写操作自动失效:发布/编辑/删除文章、修改分类、审核评论后,purgePostRelatedCaches 统一清除:首页、/sitemap.xml、所有文章详情页、所有分类页(含 /category/memo、/category/other)。
  • 手动刷新:后台 🚀 刷新缓存(POST /purge_cache);或「主题管理 → 刷新系统缓存」(POST /admin/theme/refresh,强制重载 R2 模板 + 清内存模板缓存,更安全,推荐用于模板改动后)。
  • 已知边界:分页缓存(?page=2+)为独立条目,发布新文章后最长 24 小时自然过期(stale-while-revalidate 短暂旧数据)。个人博客流量下可接受。

13.5 性能特性

  • KV 配置读取降低 99.9%:site_config 内存缓存(TTL 5 分钟)。
  • 搜索零数据库查询:直接读 R2 JSON 索引。
  • 首页第 1 页走 cache:home_list_raw(TTL 1 小时),其他页直接查库。
  • 后台统计 30 分钟缓存,秒开。
  • V3.9 新增的 CSS/JS 均为本地内联或同域,不增加外部请求;TOC/淡入/进度条等均在客户端执行,不影响服务端耗时。

---

14. 功能验证清单

部署完成后逐项验证:

后端 / 基础

  • 首页访问:根路径 / 正常渲染(主题样式加载成功)
  • 后台登录:/admin 用 KV 账号登录;未配 JWT_SECRET 时应被拒绝
  • 密码自动升级:登录后 KV 中 admin_pass 变为 100000:salt:hash 格式
  • 统计面板:后台首页显示今日/昨日/累计访问、文章/评论/说说/分类数(数据延迟 5-15 分钟)
  • 头像设置:上传头像,首页侧边栏正常显示
  • 发布文章:Markdown 渲染、图片上传压缩、置顶、分类正常
  • 草稿箱:保存草稿 → 后台草稿箱可见、前台分类页与侧栏均不显示(V3.9 修复验证)
  • 修改密码:旧密码错误被拒;新旧密码相同被拒;成功后强制重新登录
  • 登录锁定:连续输错 3 次 → 提示锁定 1 小时
  • 匿名评论:退出登录发表评论 → 待审核;60 秒内重复提交被限流;缓存命中页可正常评论
  • 管理员评论:登录状态评论正常(CSRF 强校验)
  • 搜索功能:搜索关键词 → 高亮显示;<script> 输入被转义
  • 加密文章:密码访问 → 解锁成功;换 IP 需重新输入密码
  • 主题编辑:/admin/theme 在线修改保存 → 热加载生效;非法文件名被拒
  • 缓存刷新:后台刷新缓存 → 新文章立即可见
  • 手动刷新索引:点击后新文章可被搜索到
  • 备份导出:JSON 中无 password 字段
  • 备份导入:大文件(500+ 文章)完整导入(分批);导入后置顶文章仍保持置顶
  • Cron 执行:次日日志出现 Scheduled tasks completed
  • 错误页:访问不存在 URL 显示友好 404,无堆栈泄露

V3.9 前端外观(新增验证)

  • 布局切换:🎨 面板切「顶栏」→ 侧栏变横向吸顶;切回「三栏」恢复;刷新后保留
  • 主题切换:切「暗色」→ 整站变暗;系统切深色时自动跟随;刷新后保留
  • 配色切换:蓝/青/绿/紫/粉 五种任选即时生效;刷新后保留
  • 阅读进度条:向下滚动时顶部细条跟随
  • 滚动淡入:文章卡片进入视口淡入(开启 prefers-reduced-motion 时不淡入)
  • 回到顶部:右下角按钮滚动后出现,点击回顶;与左下角 🎨 面板不重叠
  • 文章页 TOC:正文含多个标题时右侧浮层显示目录,点击跳转到对应章节并高亮
  • 阅读时长:文章页显示「约 N 分钟阅读」
  • 响应式:窗口缩窄 / 移动端下三栏退化为单列,顶栏模式正常

---

14.1 部署前 Checklist(必做)

#项目遗漏后果
1JWT_SECRET ≥32 字符强随机值(Secret 类型)默认值被拒绝,所有后台登录直接失败
2Turnstile 密钥 siteKey / secretKey 成对配置只配一个则静默跳过人机验证
3R2 模板文件 6 个上传到 blog/themes/default/模板缺失页面无法渲染
4KV 预置 admin_user / admin_pass未预置则首次需手动初始化
5D1 建表 三表 + 8 索引 + 触发器(见第 5 节)表不存在则全站 500
6Cron 三时段(UTC 换算:4:00=20 20 、0:00=16 16 、12:00=0 4 *)换算错则定时索引/缓存更新时间偏移

> R2 免费额度:存储 10 GB-month/月、Class A 100 万次/月、Class B 1000 万次/月、Egress 全免费。日均 1 万访客/500 篇文章下全部免费资源 ≤30%,无需付费。

14.2 部署后验证(建议逐项确认)

验证项方法预期
前台首页访问 /文章列表正常,暗色/配色随系统或面板切换
分类页草稿隔离建草稿 → 访问其分类 /category/xxx草稿不出现
随心说侧栏建草稿 memo侧栏不显示草稿
后台登录/login 用管理员账号登录成功,无 1102 报错
写操作发文章/上传图片/评论功能正常
缓存刷新后台「主题管理 → 刷新系统缓存」模板改动立即生效
外观面板点 🎨 切换布局/主题/配色即时生效并持久化(刷新后保留)
响应式缩窄窗口/移动端三栏退化为单列,顶栏模式正常

15. 从旧版本升级指南

15.1 V3.8 → V3.9(推荐,数据零迁移)

V3.9 后端与 V3.8 完全一致(表结构、KV、Cron、安全机制相同),升级只需替换代码 + 重传改动的模板 + 刷缓存:

  1. 备份:后台 /export/json 导出全站备份(可选但建议)。
  2. 替换 Worker 代码:粘贴 v3.9模板分离/worker代码.txt → Save and Deploy。
  3. 重传模板:将 V3.9 的 6 个模板文件上传到 R2 blog/themes/default/(至少重传 style.css / sidebar.html / layout.html 三个改动文件)。
  4. 刷新系统缓存:后台「主题管理 → 刷新系统缓存」(或 🚀 刷新缓存),强制重载 R2 模板 + 清边缘缓存。
  5. 验证:登录、评论(匿名 + 管理员)、缓存刷新、导入导出、外观切换、草稿隔离。
  6. 无需改表:D1 表结构与 V3.8 一致。

> ⚠️ 若线上仍显示旧样式 / 草稿仍出现,99% 是因为模板改动后没刷缓存或R2 上传的是旧文件——优先执行步骤 3、4。

15.2 V3.5 及更早 → V3.9

  1. 数据库:执行 ALTER TABLE posts ADD COLUMN status TEXT DEFAULT 'published';(若无 status 字段)+ 索引语句(见第 5 节)。
  2. 环境变量:新增 JWT_SECRET(必须)、CF_ZONE_ID、CF_API_TOKEN。
  3. KV:首次启动自动迁移旧 7 键 → site_config;旧 session:{id} 可清理。
  4. 模板:确认使用新版模板(占位符 content_html 改名,V3.7 配套)。
  5. 其余步骤同 15.1(替换代码 + 重传模板 + 刷缓存)。

15.3 回滚方案

重新部署旧版代码即可,数据层完全兼容。回滚后旧版已修复的缺陷(分页失效、路由缺失、V3.9 的草稿修复等)会重现,建议优先修复新版问题。

---

16. 故障排查指南

症状原因解决
首页显示 "Config Error"KV 绑定错误或配置缺失检查 CONFIG 绑定、admin_user/admin_pass 存在
登录被拒绝/提示密钥缺失未配置 JWT_SECRET 或使用默认值配置 ≥32 位随机 JWT_SECRET
页面无样式/白屏主题模板未上传或路径错误检查 R2 blog/themes/default/ 6 个文件;刷新系统缓存
主题保存后白屏内联脚本无 nonce(旧版缺陷)使用 V3.9 代码(已补全 nonce)
图片无法显示R2 Public Access 开启或路径错误关闭 Public Access,检查 /r2/blog/... 路径
评论提交被拒60 秒限流 / Turnstile 未通过等待 1 分钟;检查 Turnstile 成对配置
统计显示 0 或 -API Token 权限不足 / 数据延迟确认 Zone > Analytics > Read;等待 5-15 分钟
搜索搜不到新文章索引未重建(V3.7 起非实时)后台点"手动刷新索引"或等次日 Cron
登录/解锁报 1102PBKDF2 100k 迭代 CPU 超时(极端情况)实测登录耗时;如出现可降迭代至 30000(有 8 位密码强度校验兜底)或升级付费计划
分页列表滞后分页缓存独立条目,purge 不覆盖可接受(24h 自然过期);或扩展 purge 枚举 ?page=2~20
🎨 切换面板无反应 / 布局切换无效多半是 R2 模板未更新或缓存未刷(旧文件里 data-layout/globalScript 不匹配)重传 V3.9 的 style.css/layout.html/worker代码.txt 并刷新系统缓存
改了模板仍看到旧样式内存模板缓存(24h)或边缘缓存未清后台「刷新系统缓存」;必要时手动 /purge_cache
草稿仍出现在分类页缓存未刷新(分类页走 handleCache)后台刷新系统缓存,或等缓存自然过期
侧栏看不到文章/侧栏线上 R2 的 style.css 损坏或旧版(块未闭合)重新上传 V3.9 的 style.css 并刷新系统缓存

---

16.5 已知可选增强(不阻塞上线)

审查期间记录但不纳入 V3.9、后续按需处理的可选项:

  1. 评论 author_name / content 无长度限制(建议 ≤50 / ≤2000 字符)。
  2. 分页缓存不随发布即时失效(purge 只清第 1 页;分类页 ?page=2+ 缓存项因 url.search='' 无法精确清除,分页滞后为已知边界,非 bug)。
  3. Cron 定时任务未做 try/catch 隔离(任一子任务抛错会整体标记失败并重试;当前定时任务少,影响有限)。
  4. 评论接口无 Origin/Referer 检查(匿名评论有 Turnstile + 60s/IP 限流兜底,风险低)。
  5. verifyPassword 普通字符串比较(非 constant-time;PBKDF2 10 万次迭代本身掩盖时序差,低危)。
  6. 搜索索引 search_index.json 走 R2 但统一一年缓存头(与首页列表 1h 策略不一致,已知边界)。
  7. 文章页 TOC 仅当正文含 ≥2 个 h2–h4 时渲染;首页列表无标题,挂载点为空,无害。

17. 免费额度与资源用量参考

17.1 Cloudflare 免费额度

服务免费额度
Workers10 万次请求/天;CPU 10ms/请求
D15GB 存储;batch 语句上限约 100 条;读写行数有限额(行读约 500 万/天、行写约 10 万/天)
KV读 10 万次/天,写 1000 次/天,存储 1GB
R210GB-month 存储/月;100 万次 Class A 操作/月;1000 万次 Class B 操作/月;Egress 全免费(额度按自然月计)

17.2 本博客实际用量(参考,日均 1 万访客 / 500 篇文章)

指标实际量级说明
Workers 请求~3 万次/天约 30% 免费额度,余量充足
D1 行读取远低于限额首页第 1 页走 cache:home_list_raw KV 缓存,大幅降低
KV 写入200~300 次/天主要来自评论限流与登录计数
CPU 时长常规请求 <1ms仅登录/改密/解锁触发 PBKDF2(约 5~20ms,原生 WebCrypto 实测不超时)
R2极少量读写模板加载 + 图片 + 搜索索引,月额度远未触顶

> 结论(按 2026-04 官方最新口径重算):日均 1 万访客 / 500 篇文章下,全部免费资源使用率 ≤30%,无需付费、无需监控压力。V3.9 的前端美化不增加任何后端资源消耗(外观切换纯客户端)。

17.3 省钱建议

  • 保持边缘缓存策略,让静态页面命中 Cache API(不消耗 Workers 请求额度)。
  • Cron 任务不计入请求额度。
  • 图片上传前自动压缩(1920px/0.8),控制 R2 存储增长。
  • V3.9 的 CSS/JS 全部本地内联或同域,不产生额外外部请求费用。

---

附录:V3.9 核心路由清单

路由方法说明
/GET首页(分页)
/post/:idGET文章详情(加密文章走密码页;V3.9 含 TOC + 阅读时长 + 返回首页)
/category/:slugGET分类页(含 memo / other;V3.9 已过滤草稿)
/searchGET搜索(R2 索引,限 20 条)
/commentPOST提交评论(匿名跳 CSRF / 管理员强校验)
/loginGET/POST登录(Turnstile + IP 锁定)
/adminGET后台首页(统计面板 + 文章列表)
/admin/statsGET后台统计 JSON(dashboard_cache 30 分钟缓存)
/admin/themeGET主题文件列表
/admin/theme/editGET主题编辑
/admin/theme/savePOST保存模板(白名单 + 占位符校验 + nonce)
/admin/theme/refreshPOST刷新系统缓存(强制重载 R2 模板 + 清内存缓存,模板改动后必点)
/publishPOST发布文章(CSRF 保护)
/updatePOST更新文章
/edit/:idGET编辑页面
/change_passwordPOST修改密码(先验旧密码,新旧不得相同)
/approve_comment/:idPOST审核评论(CSRF 保护)
/delete_comment/:idPOST删除评论
/delete_category/:idPOST删除分类
/batch_delete_postsPOST批量删除文章
/purge_cachePOST刷新边缘缓存(CSRF 保护)
/admin/refresh_indexPOST手动重建搜索索引(搜索索引 + 首页缓存 + 清首页边缘缓存)
/export/jsonGET全站备份(剔除密码字段)
/import/jsonPOST导入备份(100 条分批)
/export/md/:idGET单篇 Markdown 导出
/upload_imagePOST图片上传(登录态 + CSRF + 魔数校验 + ≤10MB + 前端压缩)
/set_avatarPOST设置头像
/r2/*GETR2 静态资源代理(路径规范化)
/sitemap.xmlGET站点地图

---

评论 (0)

暂无评论,快来抢沙发吧!

发表评论

布局
主题
配色