(自开发)Cloudflare Workers 博客系统 V3.9完整部署方案
> 版本:V3.9(上线版)
> 发布日期:2026-08-06
> 核心架构:Cloudflare Workers + D1(数据库)+ KV(配置/会话/限流)+ R2(图片/主题/搜索索引)+ Cache API + JWT 无状态认证
> 版本定位:以 V3.8(七轮安全审查 + 三份外部审查定稿,代码冻结)为基座,叠加纯前端美化层 + 一处遗留 bug 修复的正式上线版本。后端逻辑与 V3.8 完全一致(65 个函数集合相同,14 个关键函数体哈希一致),可放心上线。
---
目录
- 版本演进与上线版定位
- 系统概览与架构
- 环境准备
- 资源创建(D1 / KV / R2)
- 数据库初始化(完整 SQL)
- 环境变量配置(必读)
- Worker 部署与资源绑定
- KV 系统配置
- 主题模板部署(V3.0 架构 + V3.9 美化)
- 前端外观自定义(V3.9 新增)
- Cron 定时任务配置
- 安全机制说明(与 V3.8 一致 + V3.9 补遗)
- 缓存策略与性能优化
- 功能验证清单
- 从旧版本升级指南
- 故障排查指南
- 免费额度与资源用量参考
---
1. 版本演进与上线版定位
| 版本 | 日期 | 核心变更 |
|---|---|---|
| V3.0 | 2026-07-29 | 基础架构(Workers+D1+KV+R2)、模板分离(R2 托管主题) |
| V3.1 | 2026-07-30 | 草稿箱(status 字段)、统计系统(GraphQL Analytics)、后台分页/批量删除、图片压缩 |
| V3.2 | 2026-07-31 | 搜索索引(R2 JSON)、首页列表缓存、自动后台任务 |
| V3.3 | - | 评论 CSRF、Markdown XSS 修复、Turnstile、导出剔除密码、搜索限长 |
| V3.5 | 2026-08-02 | JWT 无状态认证、KV 配置内存缓存、R2 静态搜索索引、草稿箱完善 |
| V3.6 | - | KV 合并为 site_config 单键、Cron 定时索引、9 项安全加固、手动刷新按钮 |
| V3.7 | - | XSS 防御体系补全(sanitizeHtml/highlightSafe/escapeJsString)、后台分页修复、核心路由补全、render 自动转义 |
| V3.8 | 2026-08-04 | 七轮安全审查收尾(CSRF 全局化、匿名评论机制重构、缓存流修复、导入分批、登录锁定、旧密码自动升级等) |
| V3.9 | 2026-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 边缘缓存 |
| 浏览器 localStorage | V3.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 数据库
- 登录 Cloudflare Dashboard。
- 左侧栏选择 Workers & Pages → D1 SQL Database。
- 点击 Create database,名称输入
blog-db,点击 Create。 - 复制并保存 Database ID(格式:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)。
4.2 创建 KV 命名空间
- 左侧栏选择 Workers & Pages → KV。
- 点击 Create a namespace,名称输入
blog-config,点击 Add。 - 复制并保存 Namespace ID(格式:32 位十六进制字符串)。
4.3 创建 R2 存储桶
- 左侧栏选择 R2 Object Storage → Create bucket,名称输入
blog-images。 - 安全配置(必须):进入 Bucket → Settings → Public access,确保 Public Access 开关处于关闭状态。所有资源通过 Worker 代码(
/r2/*路由)鉴权代理访问。 - 目录预创建:点击 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_SECRET | Secret(加密) | ✅ 必须 | JWT 签名密钥,必须 ≥32 位强随机字符串。缺失或使用默认值将拒绝登录(代码主动拦截) | aB3xK9mP7wR2yU5zF8jL4nQ1tV6cX0eS |
CF_ZONE_ID | Text | ✅ 是 | 域名 Zone ID(统计功能),域名概览页右侧可找到 | abc123... |
CF_API_TOKEN | Secret(加密) | ✅ 是 | 统计 API Token,权限 Zone > Analytics > Read | xxxx... |
CF_STATS_HOST | Text | ❌ 否 | 指定统计的子域名(留空统计全 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 步骤:
- 右上角头像 → 我的个人资料 → API 令牌 → 创建令牌 → 自定义令牌。
- 权限:
Zone → Analytics → Read;资源:您的域名或所有区域。 - 创建后立即复制保存(只显示一次)。
> ⚠️ V3.9 特别提醒:Turnstile 的 Site Key / Secret Key 若配置,必须成对配置(只配一个会导致验证静默跳过)。主题/布局/配色切换是纯前端功能,不依赖任何新增环境变量。
---
7. Worker 部署与资源绑定
7.1 创建 Worker
- Workers & Pages → Overview → Create Worker。
- 名称输入
my-blog,点击 Deploy,再点击 Edit code。
7.2 绑定资源(关键,大小写敏感)
在 Worker 详情页 → Settings → Variables → Bindings:
| 绑定类型 | Variable name | 选择资源 | 说明 |
|---|---|---|---|
| D1 Database | DB | blog-db | 数据库连接 |
| KV Namespace | CONFIG | blog-config | 配置/会话/限流存储 |
| R2 Bucket | BUCKET | blog-images | 图片与主题模板存储 |
> ⚠️ Variable name 必须与表格完全一致(大小写敏感),否则代码报错。
7.3 部署代码
- 将 V3.9 最终版
worker代码.txt(位于v3.9模板分离/目录)完整粘贴到编辑器(覆盖默认代码)。 - 点击 Save and Deploy。
- 记录分配的
*.workers.dev域名。
> 📌 V3.9 的 Worker 源码与 V3.8 逻辑完全一致,仅多出前端美化相关的 globalScript 注入与 8 条 status='published' 查询过滤,已通过 node --check 语法校验与 v3.8→v3.9 逻辑一致性 diff 验证。
7.4 绑定自定义域名(推荐)
- Worker 详情页 → Triggers → Custom Domains → Add Custom Domain。
- 输入域名(如
blog.example.com)。Cloudflare 自动添加 DNS 记录、申请并续期 SSL 证书。
---
8. KV 系统配置
访问 Workers & Pages → KV → blog-config。
8.1 必须手动添加的键
| Key | Value | 说明 |
|---|---|---|
admin_user | admin | 管理员用户名 |
admin_pass | YourPassword123! | 管理员初始密码(≥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": ""
}
| 字段 | 默认值 | 说明 |
|---|---|---|
siteName | BLOG | 网站名称 |
siteDesc | 记录生活 | 网站简介 |
footerText | Powered by Workers | 页脚版权(支持 HTML) |
avatarUrl | "" | 博主头像(后台上传自动写入) |
pageSize | 10 | 每页文章数 |
turnstileSiteKey | "" | Turnstile 公钥(与 Secret 成对) |
turnstileSecretKey | "" | Turnstile 私钥 |
> 首次部署新代码时自动检测旧版 7 个独立键并迁移到 site_config,无需手动迁移。旧键保留(便于回滚),1~2 周后可清理。
8.3 系统自动维护的键(无需手动创建)
| 键名 | TTL | 用途 |
|---|---|---|
cache:home_list_raw | 3600s | 首页列表缓存 |
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_cache | 1800s | 后台统计面板缓存 |
> 说明: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)注入(CSPscript-src要求所有内联脚本带 nonce),模板中无需、也不应手写<script>来实现切换逻辑。
---
10. 前端外观自定义(V3.9 新增)
V3.9 的外观自定义是纯前端能力,无需任何后端配置或新增环境变量。
10.1 三套偏好
| 偏好 | 取值 | 持久化键(localStorage) | 默认 |
|---|---|---|---|
| 布局 layout | three(三栏)/ top(顶栏) | blog_layout | three |
| 主题 theme | light / dark | blog_theme | 跟随系统(prefers-color-scheme) |
| 配色 accent | blue / cyan / green / purple / pink | blog_accent | blue |
- 三套偏好通过
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 配置步骤
- Worker 详情页 → Triggers(触发器) → Cron Triggers → Add Cron Trigger。
- 推荐表达式(Cloudflare Cron 为 UTC 时间,北京时间 = UTC+8):
| 目标执行时间(北京时间) | Cron 表达式(UTC) |
|---|---|
| 凌晨 4:00 | 20 20 * |
| 中午 12:00 | 0 4 * |
| 午夜 0:00 | 16 16 * |
- 保存后,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(必做)
| # | 项目 | 遗漏后果 |
|---|---|---|
| 1 | JWT_SECRET ≥32 字符强随机值(Secret 类型) | 默认值被拒绝,所有后台登录直接失败 |
| 2 | Turnstile 密钥 siteKey / secretKey 成对配置 | 只配一个则静默跳过人机验证 |
| 3 | R2 模板文件 6 个上传到 blog/themes/default/ | 模板缺失页面无法渲染 |
| 4 | KV 预置 admin_user / admin_pass | 未预置则首次需手动初始化 |
| 5 | D1 建表 三表 + 8 索引 + 触发器(见第 5 节) | 表不存在则全站 500 |
| 6 | Cron 三时段(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、安全机制相同),升级只需替换代码 + 重传改动的模板 + 刷缓存:
- 备份:后台
/export/json导出全站备份(可选但建议)。 - 替换 Worker 代码:粘贴
v3.9模板分离/worker代码.txt→ Save and Deploy。 - 重传模板:将 V3.9 的 6 个模板文件上传到 R2
blog/themes/default/(至少重传style.css/sidebar.html/layout.html三个改动文件)。 - 刷新系统缓存:后台「主题管理 → 刷新系统缓存」(或 🚀 刷新缓存),强制重载 R2 模板 + 清边缘缓存。
- 验证:登录、评论(匿名 + 管理员)、缓存刷新、导入导出、外观切换、草稿隔离。
- 无需改表:D1 表结构与 V3.8 一致。
> ⚠️ 若线上仍显示旧样式 / 草稿仍出现,99% 是因为模板改动后没刷缓存或R2 上传的是旧文件——优先执行步骤 3、4。
15.2 V3.5 及更早 → V3.9
- 数据库:执行
ALTER TABLE posts ADD COLUMN status TEXT DEFAULT 'published';(若无 status 字段)+ 索引语句(见第 5 节)。 - 环境变量:新增
JWT_SECRET(必须)、CF_ZONE_ID、CF_API_TOKEN。 - KV:首次启动自动迁移旧 7 键 →
site_config;旧session:{id}可清理。 - 模板:确认使用新版模板(占位符
content_html改名,V3.7 配套)。 - 其余步骤同 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 |
| 登录/解锁报 1102 | PBKDF2 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、后续按需处理的可选项:
- 评论
author_name/content无长度限制(建议 ≤50 / ≤2000 字符)。 - 分页缓存不随发布即时失效(purge 只清第 1 页;分类页
?page=2+缓存项因url.search=''无法精确清除,分页滞后为已知边界,非 bug)。 - Cron 定时任务未做 try/catch 隔离(任一子任务抛错会整体标记失败并重试;当前定时任务少,影响有限)。
- 评论接口无 Origin/Referer 检查(匿名评论有 Turnstile + 60s/IP 限流兜底,风险低)。
verifyPassword普通字符串比较(非 constant-time;PBKDF2 10 万次迭代本身掩盖时序差,低危)。- 搜索索引
search_index.json走 R2 但统一一年缓存头(与首页列表 1h 策略不一致,已知边界)。 - 文章页 TOC 仅当正文含 ≥2 个 h2–h4 时渲染;首页列表无标题,挂载点为空,无害。
17. 免费额度与资源用量参考
17.1 Cloudflare 免费额度
| 服务 | 免费额度 |
|---|---|
| Workers | 10 万次请求/天;CPU 10ms/请求 |
| D1 | 5GB 存储;batch 语句上限约 100 条;读写行数有限额(行读约 500 万/天、行写约 10 万/天) |
| KV | 读 10 万次/天,写 1000 次/天,存储 1GB |
| R2 | 10GB-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/:id | GET | 文章详情(加密文章走密码页;V3.9 含 TOC + 阅读时长 + 返回首页) |
/category/:slug | GET | 分类页(含 memo / other;V3.9 已过滤草稿) |
/search | GET | 搜索(R2 索引,限 20 条) |
/comment | POST | 提交评论(匿名跳 CSRF / 管理员强校验) |
/login | GET/POST | 登录(Turnstile + IP 锁定) |
/admin | GET | 后台首页(统计面板 + 文章列表) |
/admin/stats | GET | 后台统计 JSON(dashboard_cache 30 分钟缓存) |
/admin/theme | GET | 主题文件列表 |
/admin/theme/edit | GET | 主题编辑 |
/admin/theme/save | POST | 保存模板(白名单 + 占位符校验 + nonce) |
/admin/theme/refresh | POST | 刷新系统缓存(强制重载 R2 模板 + 清内存缓存,模板改动后必点) |
/publish | POST | 发布文章(CSRF 保护) |
/update | POST | 更新文章 |
/edit/:id | GET | 编辑页面 |
/change_password | POST | 修改密码(先验旧密码,新旧不得相同) |
/approve_comment/:id | POST | 审核评论(CSRF 保护) |
/delete_comment/:id | POST | 删除评论 |
/delete_category/:id | POST | 删除分类 |
/batch_delete_posts | POST | 批量删除文章 |
/purge_cache | POST | 刷新边缘缓存(CSRF 保护) |
/admin/refresh_index | POST | 手动重建搜索索引(搜索索引 + 首页缓存 + 清首页边缘缓存) |
/export/json | GET | 全站备份(剔除密码字段) |
/import/json | POST | 导入备份(100 条分批) |
/export/md/:id | GET | 单篇 Markdown 导出 |
/upload_image | POST | 图片上传(登录态 + CSRF + 魔数校验 + ≤10MB + 前端压缩) |
/set_avatar | POST | 设置头像 |
/r2/* | GET | R2 静态资源代理(路径规范化) |
/sitemap.xml | GET | 站点地图 |
---
评论 (0)
发表评论