DOPY

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 键值表;全站 versionsite_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)响应中剥离 adminPasswordadminPin 等敏感字段
  • ✅ Session token 使用 HMAC-SHA256 签名,无法伪造
  • ✅ 登录端点配合前端防爆破锁定(5次错误锁定60秒)
  • ✅ 向后兼容:旧版 raw password Bearer 仍然可用(平滑迁移期)

修改管理员密码

  1. 后台「设置 → 登录密码」面板直接修改(2026-08 起写入 D1 admin_credentials 表,无需改环境变量)
  2. 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 条防数据膨胀。
  • 全站 versionsite_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 查询

一、前提条件

  1. Cloudflare 账号
  2. 安装 Node.js 18+
  3. 安装 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

  1. 确保 wrangler.toml 中的 database_id 已配置为实际 ID。
  2. 部署 Worker:
    wrangler deploy
    
  3. 记下 Worker URL(例如 https://jalpei-cms-api.your-subdomain.workers.dev)。

五、部署前端到 Cloudflare Pages

重要:前端使用相对路径 /api 调用 API,不是绝对 URL。 因此必须在 Pages 项目中配置路由转发,将 /api/* 代理到 Worker。 项目根目录已包含 functions/api/[[path]].ts 转发文件,部署时自动生效。

方式 A:通过 Git 自动部署(推荐)

  1. 将代码推送到 GitHub/GitLab。
  2. 登录 Cloudflare Dashboard → Workers & PagesCreatePagesConnect to Git
  3. 构建设置:
    • Framework preset: Vite
    • Build command: npm run build
    • Build output directory: dist
  4. 部署。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。


六、本地开发

  1. 启动 Worker(端口 8787):

    wrangler dev
    
  2. 启动前端(端口 3000,自动代理 /api 到 Worker):

    npm run dev
    
  3. 浏览器访问 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.tsarticles.tsarticles/[slug].tsworks.tsworks/[slug].tspage/[slug].tsabout.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)元信息

部署注意

  1. 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 拒绝。
  2. 部署命令:npx wrangler pages deploy dist --project-name=jalpei-cms --branch=main(Functions 随部署自动编译上传)。
  3. Worker 本身无需改动(wrangler deploy 照常)。
  4. 内容更新后,sitemap / 预渲染最长 10 分钟内生效(边缘缓存 TTL)。
  5. 上线后建议:向 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.sqlCREATE 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 一键导入。

验证升级成功

  1. 打开你的网站,确认前台正常加载
  2. 进入后台 → 数据统计,确认文章/留言等计数正常
  3. 后台 → 设置 → 登录密码,确认能用当前密码登录
  4. 若登录失败:用默认 admin / admin888 登录(admin_credentials 表为空时回退环境变量),成功后再在后台改密