DOPY
数据库:Cloudflare D1,内容实体(文章/作品/页面/留言/碎碎念/回收站)**各自独立成表**;站点级配置存 `settings` 键值表;全站 `version` 存 `site_meta` 表
🚀 Jalpei CMS - Cloudflare 部署指南
本文档介绍如何将本系统部署到 Cloudflare Pages + D1 + Workers,实现全球边缘加速 + 多端实时同步。
架构概览
Cloudflare Pages (前端静态站点)
↓ fetch /api/*
Cloudflare Worker (API 服务)
↓ D1 (关系表:文章/作品/页面/留言各自成表)
Cloudflare D1 (SQLite 数据库)
- 前端:React SPA,部署到 Cloudflare Pages
- 后端:Cloudflare Worker,处理数据读写与鉴权
- 数据库:Cloudflare D1,内容实体(文章/作品/页面/留言/碎碎念/回收站)各自独立成表;站点级配置存
settings键值表;全站version存site_meta表
安全架构(重要)
鉴权机制
登录流程:
POST /api/login { username, password }
→ Worker 校验 env.ADMIN_USERNAME / env.ADMIN_PASSWORD
→ 成功:返回 HMAC-SHA256 签名的 session token(24h 有效)
→ 客户端将 token 存入 sessionStorage,后续请求自动携带
请求鉴权:
Authorization: Bearer <token>
→ Worker 用 WebCrypto 验证签名 + 检查过期时间
→ 验证通过 → 放行;否则 → 401 Unauthorized
关键安全特性:
- ✅ 管理员密码存于 D1
admin_credentials表(哈希,2026-08 起);表为空时回退 Worker 环境变量ADMIN_USERNAME/ADMIN_PASSWORD;客户端源码中没有任何密码 - ✅ 公开 API(
GET /api/site-data)响应中剥离adminPassword、adminPin等敏感字段 - ✅ Session token 使用 HMAC-SHA256 签名,无法伪造
- ✅ 登录端点配合前端防爆破锁定(5次错误锁定60秒)
- ✅ 向后兼容:旧版 raw password Bearer 仍然可用(平滑迁移期)
修改管理员密码
- 后台「设置 → 登录密码」面板直接修改(2026-08 起写入 D1
admin_credentials表,无需改环境变量) - 当
admin_credentials表为空时,登录回退校验 Worker 环境变量ADMIN_USERNAME/ADMIN_PASSWORD(默认admin/admin888)
数据写入优化(2026-08 关系化重构)
存储模型:内容实体各自成表
articles / works / pages / messages / memos / trash / settings / site_meta
- 文章、作品、页面、留言/评论、碎碎念、回收站各自独立成表,可 SQL 查询、分页、并发安全。
- 留言与评论共用
messages表,以target_type('article'|'work'|'page'|'contact'|'about')区分;回复是parent_id自引用的独立行(不再嵌套数组)。 - 站点级配置(profile / nowStatus / nav / hero / sidebar / footer / links / sponsor / announcement / layout / 主题 / 通知 Webhook)存
settings键值表。 - 浏览量/点赞是
articles/works表上的原子自增列(单条UPDATE ... SET views = views + 1),并发安全、不丢增量(2026-08 起不再需要独立stats表)。 - 回收站存
trash表(entity_type + 完整 JSON 快照),7 天 TTL 自动清理,保留最近 50 条防数据膨胀。 - 全站
version存site_meta表,每次写操作原子 +1;多端轮询/api/site-data/version检测到变化即整站刷新 —— 跨设备实时同步。
写路径:定向 SQL(非整库 JSON 重写)
- 评论/留言:INSERT 单行(并发不丢,不再整库重写)
- 文章/作品/页面编辑:INSERT ... ON CONFLICT(id) DO UPDATE(UPSERT,编辑不重置浏览/点赞数)
- 删除:DELETE 行(文章/作品/页面/留言删除联动回收站 trash 动作)
- 配置:settings 键值 upsert(对象配置浅合并,兼容前端部分字段 patch)
- 每次写操作后 site_meta.version + 1
公开读契约不变:GET /api/site-data 仍返回与前端 SiteData 一致的组合对象(由各关系表拼装 + 剥离 notifyWebhookUrl 等敏感字段),因此前端 DataContext / 后台组件 / 预渲染 Functions 全部无需改动。仅已登录管理员请求会额外回传 notifyWebhookUrl(含 access_token 密钥;前端 fetchSiteData 有 token 时自动携带),后台「互动通知」面板据此回显。
收益:
- 评论并发写入不再互相覆盖(last-write-wins 问题消失)
- 写放大消失:发一条评论 = 插一行,不再读整站 JSON 再整行重写
- 告别 D1 单行 2MB 上限:正文与评论拆表后,任何一行都不会接近上限
- 可 SQL 过滤 / 分页 / 批量统计 / 按 target_type 查询
一、前提条件
- Cloudflare 账号
- 安装 Node.js 18+
- 安装 wrangler CLI:
npm install -g wrangler
二、创建 D1 数据库
# 登录 Cloudflare
wrangler login
# 创建 D1 数据库
wrangler d1 create blog-db
# 复制输出的 database_id,填入 wrangler.toml 的 database_id 字段
初始化数据库表:
wrangler d1 execute blog-db --remote --file=worker/schema.sql
三、配置 Worker 环境变量
在 wrangler.toml 的 [vars] 段中配置:
[vars]
ADMIN_USERNAME = "admin"
ADMIN_PASSWORD = "你的强密码" # ← 请务必修改为强密码!
⚠️ 安全警告:
wrangler.toml中的密码会随代码提交。生产环境建议在 Cloudflare Dashboard → Worker → Settings → Variables 中设置为加密变量, 并从wrangler.toml中删除明文密码。
四、部署 Worker API
- 确保
wrangler.toml中的database_id已配置为实际 ID。 - 部署 Worker:
wrangler deploy - 记下 Worker URL(例如
https://jalpei-cms-api.your-subdomain.workers.dev)。
五、部署前端到 Cloudflare Pages
重要:前端使用相对路径
/api调用 API,不是绝对 URL。 因此必须在 Pages 项目中配置路由转发,将/api/*代理到 Worker。 项目根目录已包含functions/api/[[path]].ts转发文件,部署时自动生效。
方式 A:通过 Git 自动部署(推荐)
- 将代码推送到 GitHub/GitLab。
- 登录 Cloudflare Dashboard → Workers & Pages → Create → Pages → Connect to Git。
- 构建设置:
- Framework preset:
Vite - Build command:
npm run build - Build output directory:
dist
- Framework preset:
- 部署。
functions/目录会被自动识别,/api/*请求自动转发到 Worker。
方式 B:通过 wrangler CLI 部署
# 构建前端
npm run build
# 部署到 Cloudflare Pages(functions/ 目录会自动包含)
wrangler pages deploy dist
如果
wrangler pages deploy dist未自动包含 functions,需指定项目根目录:wrangler pages deploy . --build-output-dir dist
### Pages 与 Worker 的路由集成
在 Cloudflare Pages 项目中配置 **Functions** 或使用 **`_routes.json`** 将 `/api/*` 请求转发到 Worker。
或者更简单:将 Worker 绑定到 Pages 项目(推荐):
```bash
wrangler pages project create blog-frontend
然后在 Cloudflare Dashboard 中为 Pages 项目添加 Service Binding 指向你的 Worker。
六、本地开发
启动 Worker(端口 8787):
wrangler dev启动前端(端口 3000,自动代理 /api 到 Worker):
npm run dev浏览器访问
http://localhost:3000
前端使用相对路径
/api调用 API,Vite 开发服务器自动代理到localhost:8787。 生产环境通过 Cloudflare Pages 的 Service Binding 或路由规则将/api转发到 Worker。
七、API 端点
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | /api/login |
无 | 登录,返回 session token |
| GET | /api/site-data |
无 | 获取全站数据(敏感字段已剥离) |
| GET | /api/site-data/version |
无 | 获取版本号(轻量轮询) |
| PUT | /api/site-data |
管理员 | 全量覆写(导入用) |
| PATCH | /api/site-data |
按操作 | 定向更新(section-based 路由) |
PATCH 请求体格式:
{ "section": "articles", "action": "add", "article": { ... } }
支持的 section:profile / articles / works / messages / pages / memos / now-status / config / trash / links
公开操作(访客无需登录):
messages.add/messages.addReply(留言与回复)、articles.incrementViews/articles.incrementLikes(浏览量/点赞)、works.incrementLikes(点赞)。其余所有操作需要管理员 token。 另:Worker 在每次读取 site-data / version 时惰性执行「定时发布」提升(草稿 +publishAt到期 → 自动转正式发布),并可通过configSection: 'notify'配置 Webhook(notifyWebhookUrl存入settings表;公开响应剥离,仅已登录管理员回传,见前文)。互动通知 Webhook 平台适配(
sendNotify按 URL 自动识别):钉钉(oapi.dingtalk.com)/ 企业微信群机器人(qyapi.weixin.qq.com/cgi-bin/webhook)→{msgtype:'text'};Server酱(sctapi.ftqq.com/sc.ftqq.com)→{title, desp};其余 → 通用结构化 JSON。所有通知正文固定前缀「【互动通知】」——钉钉「自定义关键词」安全校验请设为「通知」即可同时覆盖留言与回复。
八、多端同步机制
- 写操作:任何设备修改后,Worker 写入 D1 并递增版本号。
- 读操作:页面首次打开时从 D1 加载。之后每 5 秒轮询版本号,版本号变化则拉取最新数据。
- 回收站:删除时把完整条目写入云端
$.trash(上限 50 条、7 天 TTL 自动清理);还原/永久删除同步移除云端条目——跨设备可查看与恢复。
九、数据备份
在管理后台 → 设置 → 导出 JSON,可下载全站数据备份(含回收站条目)。
十、免费额度估算
Cloudflare 免费计划:
| 资源 | 免费额度 | 个人博客消耗 |
|---|---|---|
| Pages 请求 | 无限 | ✅ |
| Pages 带宽 | 无限 | ✅ |
| Worker 请求 | 10 万次/天 | 2~3 万次/天 ✅ |
| D1 读取 | 500 万行/月 | ~50 万行/月 ✅ |
| D1 存储 | 5 GB | <1 MB ✅ |
完全够用,不需要付费。
十一、SEO 预渲染与站点地图
自 2026-08 起,站点对首页 / 文章 / 作品 / 自定义页面 / 关于 提供边缘预渲染:爬虫与真实用户都能直接拿到带完整内容的 HTML(不依赖 JS 执行),并输出动态
sitemap.xml/robots.txt。解决了纯 CSR(SPA)下「内容不在 HTML 里」导致的收录问题。
架构
访客请求 /articles/xxx
→ Pages Functions functions/articles/[slug].ts(边缘执行)
→ 直接读 D1(组合读各关系表,与 Worker 共用同一数据库,少一跳)
→ 用构建产物 index.html 作模板,注入:
· 完整 <head>:title / description / canonical / OG / Twitter / JSON-LD(Article, Breadcrumb)
· #root 内预渲染正文(markdown → HTML,已剥离 script/iframe/事件属性)
· window.__SITE_DATA__(客户端 React 读取作为初始数据 → 秒开、无加载转圈)
→ 响应头 Cache-Control: public, s-maxage=600(边缘缓存 10 分钟)
关键设计
- 数据源:Pages 项目直接绑定 D1(绑定名
DB),不经 Worker。 - 敏感字段:预渲染输出与公开 API 一致,剥离
adminPassword/adminPin。 - 404:不存在的 slug 返回
404状态码 + SPA 壳(真实用户仍可正常浏览,爬虫收到正确的 404 信号,避免软 404)。 - 域名无关:所有绝对 URL(canonical / og:image / sitemap / robots 的 Sitemap 行)均从请求 Host 推导 —— 日后绑定自定义域名无需改任何代码。
- 覆盖范围:
/、/articles、/works、/about、/articles/:slug、/works/:slug、/page/:slug;/admin等仍走纯 SPA。
涉及文件
| 文件 | 说明 |
|---|---|
functions/_shared/siteModel.ts |
共享组合层:从关系表拼装 SiteData(Worker 与预渲染共用,2026-08 新增) |
functions/_shared/siteData.ts |
D1 读取 + 敏感字段剥离(与 Worker sanitizeForPublic 一致) |
functions/_shared/render.ts |
HTML 拼装 / markdown 渲染 / JSON-LD / 种子数据注入 |
functions/index.ts、articles.ts、articles/[slug].ts、works.ts、works/[slug].ts、page/[slug].ts、about.ts |
各路由预渲染入口 |
functions/sitemap.xml.ts |
动态站点地图(只含已发布文章,slug 空回退 id) |
functions/robots.txt.ts |
动态 robots(Sitemap 行自动跟随当前域名) |
src/context/DataContext.tsx |
读取 window.__SITE_DATA__ 作为初始数据(秒开) |
src/App.tsx / index.html |
运行时与兜底的 OG / Twitter / 语言(zh-CN)元信息 |
部署注意
- D1 绑定(一次性,配置在 Pages 项目上):Pages 项目需有名为
DB的 D1 绑定指向blog-db。- 方式:Cloudflare 控制台 → Pages 项目 → Settings → Functions → Bindings 手动添加;或通过 Cloudflare API
PATCH /accounts/{account_id}/pages/projects/{project_name}的deployment_configs.production.d1_databases配置。 - 绑定存在项目上,之后每次部署自动继承,无需重复配置。
- ⚠️ 不要试图写进
wrangler.toml:Pages 部署不支持--config自定义路径,且wrangler.toml同时含main(Worker)与pages_build_output_dir(Pages)会被 wrangler 拒绝。
- 方式:Cloudflare 控制台 → Pages 项目 → Settings → Functions → Bindings 手动添加;或通过 Cloudflare API
- 部署命令:
npx wrangler pages deploy dist --project-name=jalpei-cms --branch=main(Functions 随部署自动编译上传)。 - Worker 本身无需改动(
wrangler deploy照常)。 - 内容更新后,sitemap / 预渲染最长 10 分钟内生效(边缘缓存 TTL)。
- 上线后建议:向 Google Search Console / Bing Webmaster / 百度站长 提交
sitemap.xml并完成站点验证;国内访问pages.dev不稳定,若以百度收录为主要目标,需另行评估国内静态托管。
附录:2026-08 关系化重构(破坏性变更)
⚠️ 本次重构为破坏性变更:删除旧
site_data(整站 JSON 单行)与stats表, 改为关系表(articles / works / pages / messages / memos / trash / settings / site_meta)。 旧数据不会自动迁移(若旧库有需保留的内容,先在旧版后台「数据管理 → 导出 JSON」备份, 再在新版后台「数据管理 → 导入 JSON」恢复)。
第 1 步:更新 D1 数据库(建关系表,删旧表)
wrangler d1 execute blog-db --remote --file=worker/schema.sql
schema.sql用CREATE TABLE IF NOT EXISTS创建全部关系表,并在文件末尾DROP TABLE IF EXISTS site_data, stats清理旧结构(幂等,可重复执行)。
第 2 步:重新部署 Worker 与前端
wrangler deploy
npm run build && npx wrangler pages deploy dist --project-name=jalpei-cms --branch=main
第 3 步:重新初始化内容
后台逐项重建 profile / 文章 / 作品 / 页面等;或用上文警告中导出的备份 JSON 一键导入。
验证升级成功
- 打开你的网站,确认前台正常加载
- 进入后台 → 数据统计,确认文章/留言等计数正常
- 后台 → 设置 → 登录密码,确认能用当前密码登录
- 若登录失败:用默认
admin/admin888登录(admin_credentials表为空时回退环境变量),成功后再在后台改密