mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-06-13 05:39:35 +08:00
feat(backup): 新增差异备份(differential)模式 (#88)
文件备份新增差异模式:仅打包自上次全量以来的变更并记录删除,恢复自动按全量+差异链还原。含基线解析、链式恢复、保留链保护与本机文件任务校验;清单/比对/删除/往返/保留保护单测全覆盖。
This commit is contained in:
@@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { CronInput } from '../CronInput'
|
||||
import type { StorageTargetDetail, StorageTargetPayload, StorageTargetSummary } from '../../types/storage-targets'
|
||||
import type { StorageConnectionTestResult } from '../../types/storage-targets'
|
||||
import type { BackupTaskDetail, BackupTaskPayload, BackupTaskType } from '../../types/backup-tasks'
|
||||
import type { BackupMode, BackupTaskDetail, BackupTaskPayload, BackupTaskType } from '../../types/backup-tasks'
|
||||
import type { NodeSummary } from '../../types/nodes'
|
||||
import { DatabasePicker } from '../common/DatabasePicker'
|
||||
import { DirectoryPicker } from '../common/DirectoryPicker'
|
||||
@@ -69,6 +69,8 @@ function createEmptyDraft(storageTargets?: StorageTargetSummary[]): BackupTaskPa
|
||||
keepWeekly: 0,
|
||||
keepMonthly: 0,
|
||||
keepYearly: 0,
|
||||
backupMode: 'full',
|
||||
diffFullIntervalDays: 7,
|
||||
extraConfig: undefined,
|
||||
verifyEnabled: false,
|
||||
verifyCronExpr: '',
|
||||
@@ -142,6 +144,8 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa
|
||||
keepWeekly: initialValue.keepWeekly ?? 0,
|
||||
keepMonthly: initialValue.keepMonthly ?? 0,
|
||||
keepYearly: initialValue.keepYearly ?? 0,
|
||||
backupMode: initialValue.backupMode ?? 'full',
|
||||
diffFullIntervalDays: initialValue.diffFullIntervalDays ?? 7,
|
||||
extraConfig: initialValue.extraConfig,
|
||||
verifyEnabled: initialValue.verifyEnabled ?? false,
|
||||
verifyCronExpr: initialValue.verifyCronExpr ?? '',
|
||||
@@ -588,6 +592,34 @@ export function BackupTaskFormDrawer({ visible, loading, initialValue, storageTa
|
||||
<Typography.Text>压缩策略</Typography.Text>
|
||||
<Select value={draft.compression} options={backupCompressionOptions as unknown as { label: string; value: string }[]} onChange={(value) => updateDraft({ compression: value as BackupTaskPayload['compression'] })} />
|
||||
</div>
|
||||
{isFileBackupTask(draft.type) && (
|
||||
<div>
|
||||
<Typography.Text>备份模式</Typography.Text>
|
||||
<Select
|
||||
value={draft.backupMode}
|
||||
options={[
|
||||
{ label: '全量备份', value: 'full' },
|
||||
{ label: '差异备份(仅文件、本机)', value: 'differential' },
|
||||
]}
|
||||
onChange={(value) => updateDraft({ backupMode: value as BackupMode })}
|
||||
/>
|
||||
{draft.backupMode === 'differential' && (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
差异备份仅打包自上次全量以来的变更,显著减小体积;超过下列天数将自动重做一次全量以控制差异链长度。恢复时自动按「全量 + 差异」链还原。
|
||||
</Typography.Text>
|
||||
<InputNumber
|
||||
style={{ width: '100%', marginTop: 4 }}
|
||||
placeholder="强制全量间隔(天)"
|
||||
prefix="全量间隔(天)"
|
||||
min={1}
|
||||
value={draft.diffFullIntervalDays}
|
||||
onChange={(value) => updateDraft({ diffFullIntervalDays: Number(value ?? 7) })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Typography.Text>保留天数</Typography.Text>
|
||||
<InputNumber style={{ width: '100%' }} value={draft.retentionDays} min={0} onChange={(value) => updateDraft({ retentionDays: Number(value ?? 0) })} />
|
||||
|
||||
@@ -113,6 +113,7 @@ export function BackupRecordsPage() {
|
||||
<Space size={4}>
|
||||
<Typography.Text>{record.fileName || '-'}</Typography.Text>
|
||||
{record.locked && <Tag color="orange" size="small" bordered>已锁定</Tag>}
|
||||
{record.backupKind === 'differential' && <Tag color="purple" size="small" bordered>差异</Tag>}
|
||||
</Space>
|
||||
<Typography.Text type="secondary">{formatBytes(record.fileSize)}</Typography.Text>
|
||||
{record.checksum && (
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface BackupRecordSummary {
|
||||
startedAt: string
|
||||
completedAt?: string
|
||||
locked: boolean
|
||||
backupKind: 'full' | 'differential'
|
||||
}
|
||||
|
||||
export interface StorageUploadResultItem {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type BackupTaskType = 'file' | 'mysql' | 'sqlite' | 'postgresql' | 'saphana' | 'mongodb'
|
||||
export type BackupTaskStatus = 'idle' | 'running' | 'success' | 'failed'
|
||||
export type BackupCompression = 'gzip' | 'none'
|
||||
export type BackupMode = 'full' | 'differential'
|
||||
|
||||
export interface BackupTaskSummary {
|
||||
id: number
|
||||
@@ -25,6 +26,8 @@ export interface BackupTaskSummary {
|
||||
keepWeekly: number
|
||||
keepMonthly: number
|
||||
keepYearly: number
|
||||
backupMode: BackupMode
|
||||
diffFullIntervalDays: number
|
||||
lastRunAt?: string
|
||||
lastStatus: BackupTaskStatus
|
||||
verifyEnabled: boolean
|
||||
@@ -81,6 +84,8 @@ export interface BackupTaskPayload {
|
||||
keepWeekly: number
|
||||
keepMonthly: number
|
||||
keepYearly: number
|
||||
backupMode: BackupMode
|
||||
diffFullIntervalDays: number
|
||||
/** 类型特有的扩展配置(如 SAP HANA 的 backupLevel/backupChannels 等) */
|
||||
extraConfig?: Record<string, unknown>
|
||||
verifyEnabled: boolean
|
||||
|
||||
Reference in New Issue
Block a user