Skip to content

Instantly share code, notes, and snippets.

@syhily
Created May 17, 2026 14:47
Show Gist options
  • Select an option

  • Save syhily/8a7479f21bc9e6bce700bc1df8453a4e to your computer and use it in GitHub Desktop.

Select an option

Save syhily/8a7479f21bc9e6bce700bc1df8453a4e to your computer and use it in GitHub Desktop.

备份与还原(Backup & Restore)设置功能

Context

需要新增第 15 个设置 section backup,位于 /wp-admin/settings/backup。功能包括:定时自动备份(依赖 S3)、手动备份、备份文件列表/下载、从备份还原、手动上传还原。所有备份文件存储在 S3 的 backup/ 前缀下。

阶段一:Settings 管道注册

1.1 Zod Schema

src/server/domains/settings/schema.ts — 追加 backupSchema

export const backupSchema = z.object({
  scheduled: z.object({
    enabled: z.coerce.boolean(),
    frequency: z.enum(['daily', 'weekly', 'monthly']).default('daily'),
    hour: z.coerce.number().int().min(0).max(23).default(3),
    minute: z.enum([0, 30] as const).default(0),
    dayOfWeek: z.coerce.number().int().min(1).max(7).optional(),   // 1=周一, 7=周日
    dayOfMonth: z.coerce.number().int().min(1).max(28).optional(), // 1-28
  }),
  retention: z.object({
    enabled: z.coerce.boolean().default(true),
    days: z.coerce.number().int().min(1).max(365).default(30),
  }),
})

1.2 TypeScript 类型

src/shared/config/blog.ts — 追加 BackupSettings 接口,在 BlogSettingsBundle 末尾加 backup: BackupSettings | null

1.3 Section 注册

src/shared/config/settings.ts

  • SETTINGS_SECTIONS 末尾追加 'backup'
  • SECTION_TO_BUNDLE_KEY 追加 backup: 'backup'
  • SECTION_DISPLAY 追加:
    backup: { to: '/wp-admin/settings/backup', label: '备份与还原', description: '数据库自动备份、手动备份与还原' }

src/server/domains/settings/sections.ts

  • 导入 backupSchema
  • SECTION_REGISTRY 追加:
    backup: {
      scope: 'blog.backup', schema: backupSchema, key: 'backup',
      defaults: {
        scheduled: { enabled: false, frequency: 'daily', hour: 3, minute: 0 },
        retention: { enabled: true, days: 30 },
      },
    }

src/server/domains/settings/snapshot.tsPROBES 追加:

backup: (value) =>
  typeof value.scheduled === 'object' && value.scheduled !== null &&
  typeof (value.scheduled as Record<string, unknown>).enabled === 'boolean' &&
  typeof value.retention === 'object' && value.retention !== null &&
  typeof (value.retention as Record<string, unknown>).enabled === 'boolean',

src/shared/contracts/settings.tsblogSettingsBundleDto 追加 backup: sectionPayload

src/routes.ts — settings layout 内追加 route('wp-admin/settings/backup', 'routes/wp-admin/settings/backup.tsx')

src/server/domains/settings/service.ts — 无需修改(backup 无 secret 字段,走默认透传)。

阶段二:Master-Worker 进程模型

还原后需要重启 Hono server,但不使用 process.exit(0)(依赖外部进程管理器),而是采用 Node.js cluster 模块 实现 nginx 风格的主进程 + 工作进程模型:

  • 主进程(primary)永不退出,负责管理 worker 生命周期
  • 工作进程(worker)运行实际的 Hono HTTP server
  • 还原完成后,worker 通过 IPC 通知 primary 重启自己
  • primary 杀掉旧 worker → fork 新 worker → 无缝恢复服务

2.1 改造 src/server.ts

在文件顶部加入 cluster 判断:

import cluster from 'node:cluster'

// Master-worker process model for graceful restart during DB restore.
// Primary process never exits; workers run the actual HTTP server.
// When a backup restore completes, the worker sends an IPC "restart"
// message and the primary forks a fresh worker.
if (cluster.isPrimary) {
  const log = getLogger('cluster')
  let worker = cluster.fork()
  cluster.on('exit', (w, code, signal) => {
    if (w.exitedAfterDisconnect) return // intentional kill
    log.error('Worker crashed, restarting', { code, signal })
    worker = cluster.fork()
  })
  // Listen for restart requests from the worker (e.g. after DB restore).
  const handleMessage = (msg: unknown) => {
    if (msg === 'restart') {
      log.info('Restart requested — killing worker')
      worker.kill()
      worker = cluster.fork()
      worker.on('message', handleMessage)
    }
  }
  worker.on('message', handleMessage)
  log.info('Primary started', { pid: process.pid, workerPid: worker.process.pid })
  // Prevent primary from exiting
} else {
  // ─── Worker: existing Hono server code ───
  // (the rest of the current server.ts code stays here, unchanged)
}

导出辅助函数供 restore 流程调用:

// In a shared module (e.g. src/server/infra/cluster.ts)
export function requestRestart(): void {
  if (process.send) {
    process.send('restart')
  }
}

2.2 S3 ListObjects 扩展

src/server/domains/images/s3-client.ts — 追加 listS3Objects(prefix) 函数:

  • 使用 ListObjectsV2Command 分页列举
  • 返回 { key, size, lastModified }[]

2.3 备份服务

新文件 src/server/domains/backup/backup-service.ts

函数 职责
createBackup() execFile('pg_dump') → gzip → putImageObject({ key: 'backup/YYYY-MM-DD_HH-mm-ss.sql.gz', ... })
listBackups() listS3Objects('backup/') → 过滤排序返回
getBackupFile(key) getImageObject(key) → 返回 Buffer
cleanupOldBackups(days) 列举备份 → 删除超龄文件
restoreFromBackup(buffer) gunzip → execFile('psql', [DATABASE_URL], { input })requestRestart()

pg_dump 参数:--no-owner --no-acl --clean --if-exists,输出 plain SQL 经 gzip 压缩后上传 S3。

还原完成后调用 requestRestart() 发送 IPC 消息给 primary,由 primary 杀掉当前 worker 并 fork 新的。

2.4 定时调度器

新文件 src/server/domains/backup/scheduler.ts

  • 遵循 analytics batcher 的 setTimeout + .unref() 模式
  • 读取 getBlogSettingsBundleSync().backup 获取调度配置
  • 根据频率/时间计算下次执行时刻,设定定时器
  • 执行后自动清理超龄备份,然后重新调度
  • 调度器在 S3 未开启或 scheduled.enabled=false 时以 5 分钟轮询检查配置变化

启动入口:在 src/server.ts 的 worker 分支中导入并调用 startBackupScheduler()(settings hydration 之后)。

阶段三:API 端点

3.1 Admin oRPC Controller

新文件 src/server/http/controllers/admin/backup.controller.ts

Procedure Method Path 职责
create POST /admin/backup/create 触发手动备份
list GET /admin/backup/list 列举 S3 备份文件
restore POST /admin/backup/restore 从指定 key 还原

全部使用 adminProc,create/restore 前置检查 S3 enabled。

src/server/http/api-router.tsapiRouter.admin 追加 backup: adminBackupRouter

3.2 下载 Resource Route

新文件 src/server/http/resources/backup-download.ts

  • Hono route GET /api/admin/backup/download/:key{.+}
  • 校验 admin session → getBackupFile(key) → 设置 Content-Type/Content-Disposition → c.body(buffer)

3.3 上传还原 Resource Route

新文件 src/server/http/resources/backup-upload.ts

  • Hono route POST /api/admin/backup/upload-restore
  • 校验 admin session → 解析 multipart → 读取 Buffer → restoreFromBackup(buffer)

两个 resource route 在 src/server.ts 中注册。

阶段四:路由 + UI

4.1 Route Module

新文件 src/routes/wp-admin/settings/backup.tsx

  • loader:requireRole('admin') → 检查 bundle.assets.storage.enabled → 若 S3 已开则 listBackups() → 返回 { s3Enabled, backups }
  • component:读取 bundle.backup + loaderData → 渲染 <BackupView />

4.2 Settings UI Component

新文件 src/ui/admin/settings/BackupView.tsx

分为四个区块:

  1. S3 未配置提示(s3Enabled=false 时)— 带跳转链接到 assets 设置页
  2. 定时备份配置 — 使用 useSettingsForm 管理 scheduled/retention 表单状态:
    • 开关:scheduled.enabled
    • 频率选择:daily / weekly / monthly
    • 时间选择:hour (0-23) + minute (0 或 30)
    • 周频率:星期选择 (1-7)
    • 月频率:日期选择 (1-28)
    • 保留策略开关 + 天数
    • 底部 SettingsFormBar
  3. 备份文件列表 — 表格显示文件名/大小/时间,每行有下载和还原按钮:
    • 左上角"手动备份"按钮(useMutationorpc.admin.backup.create
    • 还原按钮弹出确认对话框(参考 cache 的 ConfirmClearDialog
  4. 手动还原 — 文件上传 input,上传后调用 upload-restore endpoint

修改文件清单

文件 操作
src/server/domains/settings/schema.ts 修改:追加 backupSchema
src/shared/config/blog.ts 修改:追加 BackupSettings + 扩展 BlogSettingsBundle
src/shared/config/settings.ts 修改:追加 section 注册
src/shared/contracts/settings.ts 修改:追加 backup 到 DTO
src/server/domains/settings/sections.ts 修改:追加 registry entry
src/server/domains/settings/snapshot.ts 修改:追加 probe
src/server/domains/images/s3-client.ts 修改:追加 listS3Objects
src/server/http/api-router.ts 修改:注册 backup router
src/routes.ts 修改:追加 backup route
src/server.ts 修改:cluster master-worker 包装 + 资源路由注册
src/server/infra/cluster.ts 新建requestRestart() IPC 辅助
src/server/domains/backup/backup-service.ts 新建
src/server/domains/backup/scheduler.ts 新建
src/server/http/controllers/admin/backup.controller.ts 新建
src/server/http/resources/backup-download.ts 新建
src/server/http/resources/backup-upload.ts 新建
src/routes/wp-admin/settings/backup.tsx 新建
src/ui/admin/settings/BackupView.tsx 新建

Verification

  • vp check 通过
  • vp test run 通过
  • vp build 成功
  • /wp-admin/settings/backup 页面正常渲染
  • S3 未开启时显示配置提示
  • S3 开启后可配置定时备份、查看备份列表、手动创建备份

备份与还原功能实现计划(修订版)

对比总结

与原计划(welcome-24-sparkling-knuth.md)相比,本修订版吸取了对方 schema 设计、Resource Route 架构的优点,同时保留了更简单的重启模型和更详尽的设置系统 plumbing 说明。核心差异:

维度 对方计划 本计划(修订后) 理由
Schema hour+minute 分离数字字段,更简洁 采用 避免字符串解析,Select 渲染更直观
下载/上传 Hono Resource Route(二进制流) 采用 oRPC 面向 JSON,二进制走 Hono native 更自然
重启模型 Node.js cluster 主从进程 process.exit(0) react-router-hono-server 内部已接管 node:http/serve,叠加 cluster 风险高、与 Vite HMR 冲突;容器编排(Docker/K8s)自带重启策略,exit(0) 足够
定时调度 5min 轮询检查配置变化 settings update 显式 reschedule 轮询 unnecessary,现有设置保存后已可触发钩子
pg_dump --no-owner --no-acl 采纳 提升跨环境还原兼容性
Loader 预取 loader 中 listBackups() 采纳 减少首屏一次 round-trip
事务还原 仅提到 psql 详细说明 BEGIN/COMMIT 包裹 满足用户「事务方式」要求

1. 目标与范围

在后台设置中新增「备份与还原」section,实现:

  • 定时备份开关(依赖 S3 已配置)
  • 定时频率配置(天/周/月,半小时间隔选时)
  • 保留策略(默认 30 天,可关闭)
  • S3 /backup 目录下的备份文件列表、手动备份、下载、还原
  • 手动上传备份文件还原
  • 还原后重启 Hono server

2. 关键架构决策

  • 备份引擎pg_dump --no-owner --no-acl --clean --if-existsgzip → S3 backup/。恢复:gunzipBEGIN; SET CONSTRAINTS ALL DEFERRED; <SQL>; COMMIT;psql。PostgreSQL 支持 transactional DDL,满足「事务方式还原」要求。
  • 外部二进制可用性pg_dump/psql 是必需运行时依赖。服务启动时检测,不可用时 gracefully 禁用备份功能(日志警告 + API 503 + UI 提示),不阻断其他功能。
  • 调度器:进程内 setTimeout(与现有 purgeStaleLikeTokens 模式一致),无外部 cron。settings 更新后显式 rescheduleBackup()
  • 恢复后重启:成功恢复后返回响应,随后 setTimeout(() => process.exit(0), 500),由容器编排自动重启。
  • S3 列表:新增 ListObjectsV2Command 到现有 S3 client;backup 独立前缀 backup/
  • 二进制传输:备份下载与上传还原走 Hono Resource Route(非 oRPC),避免大文件 Base64/JSON 开销。
  • 设置模型:新增 blog.backup JSONB row,通过现有 SECTION_REGISTRY 机制注册,惰性 backfill。

3. 设置系统扩展

3.1 Zod Schema(src/server/domains/settings/schema.ts

export const backupSchema = z.object({
  scheduled: z.object({
    enabled: z.coerce.boolean(),
    frequency: z.enum(['daily', 'weekly', 'monthly']).default('daily'),
    hour: z.coerce.number().int().min(0).max(23).default(3),
    minute: z.enum([0, 30] as const).default(0),
    dayOfWeek: z.coerce.number().int().min(1).max(7).optional(),   // 1=周一, 7=周日
    dayOfMonth: z.coerce.number().int().min(1).max(28).optional(), // 1-28
  }),
  retention: z.object({
    enabled: z.coerce.boolean().default(true),
    days: z.coerce.number().int().min(1).max(365).default(30),
  }),
}).superRefine((value, ctx) => {
  if (!value.scheduled.enabled) return
  if (value.scheduled.frequency === 'weekly' && value.scheduled.dayOfWeek === undefined) {
    ctx.addIssue({ code: 'custom', path: ['scheduled', 'dayOfWeek'], message: '请选择星期几' })
  }
  if (value.scheduled.frequency === 'monthly' && value.scheduled.dayOfMonth === undefined) {
    ctx.addIssue({ code: 'custom', path: ['scheduled', 'dayOfMonth'], message: '请选择每月日期' })
  }
})

3.2 TypeScript 类型(src/shared/config/blog.ts

export interface BackupSettings {
  scheduled: {
    enabled: boolean
    frequency: 'daily' | 'weekly' | 'monthly'
    hour: number
    minute: 0 | 30
    dayOfWeek?: number
    dayOfMonth?: number
  }
  retention: {
    enabled: boolean
    days: number
  }
}

BlogSettingsBundle 追加 backup: BackupSettings | null

3.3 Section 注册

src/shared/config/settings.ts

  • SETTINGS_SECTIONS 追加 'backup'
  • SECTION_TO_BUNDLE_KEY 追加 backup: 'backup'
  • SECTION_DISPLAY 追加:
    backup: { to: '/wp-admin/settings/backup', label: '备份与还原', description: '数据库自动备份、手动备份与还原' }
  • BackupLoaderShape 定义(供 admin projection 使用)

src/server/domains/settings/sections.ts

backup: {
  scope: 'blog.backup',
  schema: backupSchema,
  key: 'backup',
  defaults: {
    scheduled: { enabled: false, frequency: 'daily', hour: 3, minute: 0 },
    retention: { enabled: true, days: 30 },
  },
}

src/server/domains/settings/snapshot.tsPROBES 追加:

backup: (value) =>
  typeof value.scheduled === 'object' && value.scheduled !== null &&
  typeof (value.scheduled as Record<string, unknown>).enabled === 'boolean' &&
  typeof value.retention === 'object' && value.retention !== null &&
  typeof (value.retention as Record<string, unknown>).enabled === 'boolean',

3.4 其余 plumbing

  • src/shared/contracts/settings.tsblogSettingsBundleDto 追加 backup: sectionPayload
  • src/ui/lib/blog-config-context.tsxSECTION_CONTEXTS 追加 backup;导出 useBackupSettings() / useBackupSettingsOptional()
  • src/server/domains/settings/service.tsupdateBlogSettingsSection 在 section === 'backup' 时调用 rescheduleBackup()

4. S3 Client 增强(src/server/domains/images/s3-client.ts

  • 导入追加 ListObjectsV2Command
  • 新增 listS3Objects(prefix: string):分页列举,返回 { key, size, lastModified }[]
  • 新增 getS3ObjectBuffer(key: string):返回 Buffer(与 getImageObject 类似,但用于任意 key)
  • 新增 putS3Object(key, body, contentType?):通用 PUT
  • 新增 deleteS3Objects(keys: string[]):批量删除(复用现有 MD5 fallback middleware)

5. Backup Domain(src/server/domains/backup/

5.1 service.ts

函数 职责
checkPgToolsAvailable() 启动时 which('pg_dump') + which('psql'),缓存结果到模块级变量
ensurePgTools() 若不可用,抛 ActionFailure(503, '备份功能需要安装 postgresql-client')
createBackup() ensurePgTools()spawn('pg_dump', ['--no-owner','--no-acl','--clean','--if-exists','--dbname='+DATABASE_URL]) → pipe gzip stream → putS3Object('backup/YYYY-MM-DD_HH-mm-ss.sql.gz', stream, 'application/gzip')
listBackups() listS3Objects('backup/') → 按 LastModified 倒序
getBackupBuffer(key) getS3ObjectBuffer(key) → 返回 Buffer
cleanupOldBackups(days) 列举 → 过滤 LastModified < now - daysdeleteS3Objects(keys)
restoreFromBackup(buffer) ensurePgTools()gunzip(buffer) → 首尾包裹 BEGIN; SET CONSTRAINTS ALL DEFERRED; ... COMMIT;spawn('psql', ['--dbname='+DATABASE_URL, '--echo-all']) stdin 写入。非 0 退出抛 DomainError。成功后返回 { success: true }

5.2 scheduler.ts

  • 模块级 let backupTimer: NodeJS.Timeout | null = null
  • computeNextRun(settings, now, timeZone): Date
    • 根据 frequency/hour/minute/dayOfWeek/dayOfMonth 和运营时区(siteIdentity.timeZone)计算下一个触发时刻。
  • scheduleNextBackup()
    • 读取 getBlogSettingsBundleSync().backup;若 scheduled.enabled === false 或 S3 未启用,清除 timer 返回。
    • 计算下次时间,设定 setTimeout。超时后 await createBackup();若 retention 启用则 await cleanupOldBackups();catch 记录日志不抛;最后递归 scheduleNextBackup()
  • rescheduleBackup()
    • clearTimeout(backupTimer)backupTimer = null;调用 scheduleNextBackup()

启动时机src/server.ts 中 server 创建完成后调用 scheduleNextBackup()


6. HTTP API 层

6.1 oRPC Controller(src/server/http/controllers/admin/backup.controller.ts

Procedure Method Path 说明
create POST /admin/backup/create 手动触发备份;前置检查 S3 enabled + pg tools 可用性
list GET /admin/backup/list 返回备份文件列表(供 loader / UI 刷新)
restore POST /admin/backup/restore 按 key 从 S3 下载并还原;成功后 setTimeout(() => process.exit(0), 500)
status GET /admin/backup/status 返回 { s3Enabled, pgToolsAvailable } 供 UI 判断功能可用性

全部使用 adminProc

6.2 Hono Resource Routes

src/server/http/resources/backup-download.ts

  • GET /api/admin/backup/download/:key{.+}
  • requireRoleMw('admin')getBackupBuffer(key)c.body(buffer, 200, { 'Content-Type': 'application/gzip', 'Content-Disposition': 'attachment; filename="..."' })

src/server/http/resources/backup-upload.ts

  • POST /api/admin/backup/upload-restore
  • requireRoleMw('admin') → Hono bodyLimit({ maxSize: 500 * 1024 * 1024 }) → multipart 读取 Blob/Buffer → restoreFromBackup(buffer) → 成功后延迟 exit

6.3 注册点

  • src/server/http/api-router.tsapiRouter.admin 追加 backup: adminBackupRouter
  • src/server.tsapp.route('/', backupDownloadRouter) / app.route('/', backupUploadRouter)(放在现有 resource routers 旁)

7. Admin UI 层

7.1 Route Module(src/routes/wp-admin/settings/backup.tsx

export async function loader() {
  const ctx = getRouteRequestContext(...)
  requireRole(ctx, 'admin')
  const s3Enabled = bundle.assets.storage.enabled
  const backups = s3Enabled ? await listBackups() : []
  return { s3Enabled, backups }
}

Component 读取 bundle.backup + loaderData → 渲染 <BackupView backup={bundle.backup} s3Enabled={loaderData.s3Enabled} initialBackups={loaderData.backups} timeZone={bundle.siteIdentity.timeZone} />

7.2 Settings UI(src/ui/admin/settings/BackupView.tsx

约 400–500 LOC。

四个区块(从上到下):

  1. 功能可用性提示
    • pgToolsAvailable === false:显示「当前运行环境缺少 postgresql-client,备份与还原功能不可用。」
    • s3Enabled === false:显示「请先前往存储配置启用 S3 存储」+ 跳转链接
    • 两者同时满足才显示配置表单
  2. 定时备份配置(使用 useSettingsForm
    • 开关:scheduled.enabled
    • 频率 Select:daily / weekly / monthly
    • 时间:hour (0–23) + minute (0 | 30) 两个 Select
    • weekly 时显示 dayOfWeek Select(1–7,中文标签「周一」~「周日」)
    • monthly 时显示 dayOfMonth Select(1–28)
    • 保留策略开关 + days Number input(1–365)
    • 底部 <SettingsFormBar />
    • 时区提示小字:「备份时间按 {timeZone} 时区计算」
  3. 备份文件列表
    • 表格:文件名、大小、备份时间
    • 左上角「手动备份」按钮(useMutationorpc.admin.backup.create,成功后 revalidator.revalidate()
    • 每行「下载」按钮 → window.open('/api/admin/backup/download/' + encodeURIComponent(key))
    • 每行「还原」按钮 → <ConfirmDialog> → 调 orpc.admin.backup.restore({ key }) → 成功后显示「还原成功,服务即将重启…」
  4. 手动还原
    • <input type="file" accept=".sql.gz" />
    • 选择文件后显示确认 Dialog → fetch('/api/admin/backup/upload-restore', { method: 'POST', body: formData }) → 成功后提示重启

8. Docker 运行时依赖

Dockerfile runtime stage 追加:

RUN apk add --no-cache postgresql-client

9. 测试策略

  • tests/contract.backup-schema.test.ts — 验证 backupSchema 对各种有效/无效输入的解析(特别关注 frequency 条件字段)。
  • tests/service.backup.test.tscomputeNextRun 边界(跨天/跨周/跨月/时区)、cleanupOldBackups 过滤。
  • tests/server.http.orpc-smoke.test.ts — 追加 backup 端点存在性断言。
  • tests/contract.settings-sections.test.ts 已遍历 SETTINGS_SECTIONS,新增 backup 后自动覆盖 defaults 校验。

10. 实现顺序

  1. Settings plumbing:schema.ts → blog.ts → settings.ts → sections.ts → snapshot.ts → blog-config-context.tsx → contracts/settings.ts → routes.ts → backup.tsx route shell
  2. S3 列表能力:s3-client.ts 新增 list/get/put/delete
  3. Backup domain:service.ts(create/list/cleanup) + scheduler.ts(computeNextRun/scheduleNextBackup)
  4. Resource routes:backup-download.ts + backup-upload.ts
  5. oRPC controller:backup.controller.ts(create/list/restore)
  6. UI 骨架:BackupView.tsx(定时配置表单 + SettingsFormBar)
  7. 备份列表与操作:手动备份按钮、下载链接、还原 Dialog
  8. 手动还原上传:文件上传 + upload-restore endpoint 集成
  9. 调度启动:server.ts 末尾启动 scheduler;settings update 联动 reschedule
  10. Docker + 测试:Dockerfile 加 pg tools;写 schema/scheduler 测试

11. 风险与缓解

风险 缓解
pg_dump / psql 不在运行环境中 1) Dockerfile 安装 postgresql-client;2) 启动时检测,不可用时 gracefully 禁用备份(API 503 + UI 提示),不 crash 其他功能;3) 开发环境文档说明安装方式
用户在没有 pg tools 的环境误开启备份 status endpoint 实时暴露可用性,UI 在 pg tools 不可用时禁用开关并展示提示
备份文件过大导致内存溢出 pg_dump 直接 pipe 到 gzip stream 上传 S3;restore 时 buffer 不可避免,但可接受(典型 < 1GB)
还原中途失败导致数据库半完成 BEGIN/COMMIT 包裹;PostgreSQL transactional DDL 原子回滚
恢复后 session / settings snapshot 旧 process.exit(0) 触发容器重启,全新进程重新 hydrate
定时调度在设置变更后不同步 updateBlogSettingsSection 成功后显式 rescheduleBackup()
多实例同时触发备份 可接受冗余;如需互斥可在 Redis 加分布式锁(V2)
cluster 主从模型与 react-router-hono-server 冲突 本计划放弃 cluster,直接使用单进程 + 容器重启

12. 文件变更清单

新增

  • src/server/domains/backup/service.ts
  • src/server/domains/backup/scheduler.ts
  • src/server/http/controllers/admin/backup.controller.ts
  • src/server/http/resources/backup-download.ts
  • src/server/http/resources/backup-upload.ts
  • src/routes/wp-admin/settings/backup.tsx
  • src/ui/admin/settings/BackupView.tsx
  • tests/contract.backup-schema.test.ts
  • tests/service.backup.test.ts

修改

  • src/server/domains/settings/schema.ts
  • src/shared/config/blog.ts
  • src/shared/config/settings.ts
  • src/shared/contracts/settings.ts
  • src/server/domains/settings/sections.ts
  • src/server/domains/settings/snapshot.ts
  • src/server/domains/settings/service.ts
  • src/server/domains/images/s3-client.ts
  • src/ui/lib/blog-config-context.tsx
  • src/server/http/api-router.ts
  • src/server.ts
  • src/routes.ts
  • Dockerfile
  • tests/server.http.orpc-smoke.test.ts

无需修改

  • DB schema / migration(settings 为 JSONB,无需 DDL)
  • src/server/infra/db/schema.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment