备份与恢复

This commit is contained in:
Yeomai committed 2026-10-18 20:28:46 +08:00
1 parent 5d5fecde1b
commit b4535d6aea
6 files changed
+252 -27

No files matched your search

+49 -2
View File
@@ -11,8 +11,9 @@ import {
getBackupStagingDir,
isSafeBackupFilename
} from '../../../utils/backup/paths'
import { basename, join } from 'node:path'
import { promises as fs } from 'node:fs'
import { createWriteStream, promises as fs } from 'node:fs'
import { basename, dirname, join } from 'node:path'
import { pipeline } from 'node:stream/promises'
interface RestoreBody {
archiveName?: string
@@ -20,6 +21,20 @@ interface RestoreBody {
dryRun?: boolean
}
async function streamUploadToFile(event: H3Event, archivePath: string): Promise<void> {
await fs.mkdir(dirname(archivePath), { recursive: true })
try {
await pipeline(event.node.req, createWriteStream(archivePath))
const stat = await fs.stat(archivePath)
if (stat.size === 0) {
throw new Error('备份文件为空')
}
} catch (error) {
await fs.rm(archivePath, { force: true }).catch(() => {})
throw error
}
}
async function resolveRestoreInput(event: H3Event, userId: number): Promise<{
archivePath: string
body: RestoreBody
@@ -27,6 +42,38 @@ async function resolveRestoreInput(event: H3Event, userId: number): Promise<{
}> {
const contentType = getHeader(event, 'content-type') ?? ''
if (contentType.includes('application/octet-stream')) {
const dryRun = getHeader(event, 'x-pichost-dry-run') === 'true'
const conflictMode = getHeader(event, 'x-pichost-conflict-mode') === 'skip'
? 'skip'
: 'overwrite'
if (dryRun) {
const tempDir = getBackupStagingDir(`upload-preview-${Date.now()}`)
await fs.mkdir(tempDir, { recursive: true })
const archivePath = join(tempDir, 'package.phost.tar.gz')
await streamUploadToFile(event, archivePath)
return {
archivePath,
body: { conflictMode, dryRun: true },
cleanup: async () => {
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {})
}
}
}
await fs.mkdir(getBackupsDir(), { recursive: true })
const archivePath = join(
getBackupsDir(),
`upload-${Date.now()}${BACKUP_ARCHIVE_EXT}`
)
await streamUploadToFile(event, archivePath)
return {
archivePath,
body: { conflictMode, dryRun: false }
}
}
if (contentType.includes('multipart/form-data')) {
const form = await readMultipartFormData(event)
const filePart = form?.find(part => part.name === 'file' && part.data?.length)