跳至正文
Yukihime

管理后台与 GitHub 内容发布

管理后台与 GitHub 内容发布

说明 AstroBlog 管理后台如何编辑内容,并通过受保护的 GitHub Proxy 发布到静态站点。

AstroBlog 的后台是对仓库内容的受保护编辑界面。文章、动态、书籍、友链、导航和配置最终仍然写回内容文件或配置文件,提交后由 Cloudflare 重新构建静态页面。后台的关键是把浏览器编辑、鉴权、GitHub API 和构建校验连接起来。

评论审核、音乐库和首页壁纸使用各自的运行时接口。它们与文章编辑共用管理员认证和操作反馈,但保存位置与生效方式不同:文章发布更新 GitHub 文件,评论写入 D1,壁纸清单和播放规则保存到 R2。判断一次操作是否成功时,要检查对应的数据源。

后台页面结构

管理页面位于 src/pages/admin/,使用独立的 AdminLayout.astro,不加入主站的 Swup 容器。布局加载管理员验证、后台样式、侧栏和命令面板;各页面把数据和操作交给 Svelte Manager,例如文章列表使用 PostManager.svelte,文章编辑使用 WriteEditor.svelte,评论管理使用 CommentManager.svelte。各模块沿用统一的认证、请求状态和操作反馈。

后台页面不直接持有 GitHub token。AdminLoginGate.svelte 负责取得管理员会话和发送 X-Admin-Token,组件请求统一的 /api/github 或其他同源 Worker 路由。这样 token 不会因为 Svelte 编译而出现在公开 JavaScript 中,后台页面也能在认证失败时统一显示状态。

文章编辑数据

WriteEditor.svelte 同时处理 Frontmatter 表单、Markdown 正文、预览和本地草稿。真正生成文件内容的逻辑位于 src/utils/adminContent.ts。它不会把整个文档解析成对象后重新序列化,而是定位 Frontmatter 的字段范围,尽可能保留原有注释、换行、字段顺序和正文。

这种处理方式对 Markdown 很重要。重新序列化会改变大量无关行,造成难以审核的提交;局部替换只修改用户真正改动的字段。日期、布尔值、数组和空值仍然需要按 Schema 预期输出,例如 draft: false 必须是 YAML 布尔值,而不是字符串。

发布链路

Text
WriteEditor.svelte
      ↓
adminContent.ts 生成目标文本
      ↓
AdminLoginGate / X-Admin-Token
      ↓
同源 /api/github
      ↓
src/workers/github-proxy.js
      ↓
GitHub Contents 或 Git Data API
      ↓
仓库分支更新
      ↓
Cloudflare 构建 Astro 页面

代理层先检查来源、管理员授权、请求体大小、目标仓库、分支名、HTTP 方法和路径白名单。允许写入的路径只覆盖配置过的内容、资源和后台文件,拒绝点路径、反斜杠、编码分隔符和不安全分支名。生产分支还要经过来源和部署环境保护,避免预览页面把内容写入生产。

并发保护

单文件写入使用 GitHub Contents API 的文件 SHA;批量内容操作还会先读取分支 HEAD 和仓库 tree。后台提交时带上 expectedHead 和每个文件的 expectedSha,代理发现远程版本已经变化就返回 409,要求编辑器刷新预览后再提交。

这一步避免两个浏览器互相覆盖。按钮上的 loading 状态只能防止同一个页面重复点击,不能判断 GitHub 上是否有人修改过同一个文件。批量提交还会限制文件数量、单文件大小和总请求体大小,并在更新分支引用前再次确认 HEAD 没有变化。

删除和资源写入

删除文章不是发送空字符串。后台要调用允许的 DELETE 路径,并提供当前文件 SHA;代理仍然执行路径白名单和生产分支保护。图片或书籍封面上传到仓库时,文件扩展名、大小和目标目录也会被验证,避免后台成为任意文件写入接口。

如果新增一种可编辑资源,需要同步四层:Manager 的表单模型、adminContent.ts 的文本修改、github-proxy.js 的路径/方法白名单、scripts/verify-admin/ 的验证脚本。只改 UI 会出现预览正常但提交被拒绝,只改代理又可能产生构建无法解析的内容。

后台与静态构建的关系

后台发布成功只代表 GitHub 分支已更新,不代表线上页面已经完成构建。Cloudflare 需要拉取新内容、运行 pnpm build、生成静态页面和 Pagefind 索引。发布后应该检查构建状态和实际页面,而不是只根据 API 的 200 响应判断成功。

壁纸管理页面的 WallpaperManager.svelte 则直接调用 /api/home-videos/admin。保存后前台在下一次清单刷新时读取新规则,无需等待文章构建;如果返回 409,应先刷新服务端配置再编辑。这个版本保护与文章提交的文件 SHA 校验用途相近,但两者分别保护 R2 配置和 GitHub 内容。

文章列表为空时,先检查后台写入的目标路径是否位于 src/content/posts/、Frontmatter 是否符合 content.config.ts、是否误设为草稿,再检查部署是否使用了最新构建。后台显示已保存和公开页面出现之间存在构建延迟,这是静态站点的正常边界。

修改后台的检查范围

修改表单字段:看对应 Manager 和 adminContent.ts;修改登录:看 AdminLoginGate.svelte、Worker 鉴权和管理员限流;修改 GitHub 能力:看 github-proxy.js 的允许路径、方法和分支保护;修改内容格式:看 Schema、Frontmatter 验证和构建脚本。后台页面使用独立布局,涉及导航或 Swup 时不要直接套用主站页面的脚本。

powershell
# 验证 Frontmatter 保留、后台草稿、taxonomy、认证和代理路径白名单。
pnpm verify:admin
# 检查仓库中是否出现被跟踪的密钥或 token。
pnpm verify:secrets
# 验证发布内容能通过集合、Markdown、图片和静态构建。
pnpm build

管理员验证脚本覆盖 Frontmatter、草稿、taxonomy、例行任务、认证限流和代理规则。远程 GitHub 权限、Cloudflare 环境变量和实际部署仍然需要在目标环境验收。

Contents API 和 Git Data API

简单的单文件创建、更新和删除可以使用 GitHub Contents API:请求中包含仓库路径、分支、内容和当前文件 SHA。需要同时修改多篇内容时,代理会读取分支 commit、base tree 和 blob,创建新 tree 与 commit,再用 fast-forward 方式更新分支引用。两条路径都必须经过相同的白名单、认证和分支保护,不能因为批量接口走 Git Data API 就绕过保护。

代理还会限制允许的文件扩展名和目录。文章只能写入内容目录,媒体只能写入配置过的资源目录,配置文件只能写入明确列出的文件。路径须校验编码与解码形式并规范化,拒绝 ..、反斜杠、编码分隔符、空路径和不安全分支名,不能让白名单通过后再由 URL 解析改变目标路径。

草稿、本地预览和发布

后台本地草稿保存在浏览器状态,不会自动进入仓库;点击发布才会生成 Markdown 文件并提交。编辑器与规划页面的 Markdown 预览经 src/utils/adminPreview.ts 清理 HTML,只允许受控标签、属性与 URL 协议,移除脚本、事件、活动文档和页面样式。它不是正式 Astro Markdown/MDX 管线,不能保证 MDX import 或文章专属插件在预览中完整执行;预览成功也不代表远程构建成功,尤其是图片相对路径、MDX import、集合字段和 Worker 环境变量仍可能在构建阶段失败。

如果要增加“定时发布”或“草稿箱”,不能只在 Svelte 中增加一个状态字段。当前架构没有数据库草稿表,真正可靠的方案需要新增存储位置、权限边界和构建策略,否则刷新浏览器就会丢失服务端状态。

后台模块清单以 adminModulesConfig.ts 为准,AI 与访客洞察管理已删除;旧访客后台地址由 Worker 返回不可缓存的退役状态。后台继续使用既有蓝色强调亮/暗覆盖和共享字体,不套用普通前台粉白主题或文章阅读配色。

预览安全可运行 pnpm verify:runtime-utils,最新构建后再运行 pnpm verify:browser --admin-preview-only。pnpm verify:admin 包含构建产物检查,需先准备 dist/;pnpm verify:admin:crud 默认只预检,显式 --run 才执行 GitHub 创建、更新和删除并产生提交,不能自动当成无写入检查,完整前提见仓库 scripts/verify-admin/README.md。

Manager 的状态边界

Manager 组件通常同时维护列表、编辑对象、请求状态和 toast。列表数据来自 GitHub 读取结果,编辑对象是用户尚未提交的本地副本,提交成功后才用返回的 commit 或文件内容刷新列表。不要在输入框绑定到列表原对象,否则取消编辑时无法恢复原值,多个表单也会互相覆盖。

后台的删除、上传和批量操作都应显示明确的进行中状态,并在 401、403、409、413 和 429 时给出不同处理:401 重新登录,403 检查权限,409 刷新远程版本,413 缩小请求,429 等待重试。统一错误处理比在每个按钮里拼接字符串更容易维护。

[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

找不到相关结果。
[ 全部文章 ]