备份系统
├── BackupLogic # 备份核心逻辑
├── BackupService # 备份服务接口
├── BackupExecutor # 备份执行器
└── BackupRestorer # 备份恢复器
1. 分析清理任务 → 确定需要备份的数据
2. 创建备份计划 → 生成备份任务列表
3. 执行数据备份 → 将数据以INSERT语句形式存储到数据库
4. 验证备份完整性 → 确保备份数据完整
5. 记录备份信息 → 保存备份元数据
6. 执行清理操作 → 在备份完成后执行清理
CREATE TABLE `kku_cleanup_sql_backups` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`backup_id` bigint(20) unsigned NOT NULL COMMENT '关联备份记录ID',
`table_name` varchar(100) NOT NULL COMMENT '表名',
`sql_content` longtext NOT NULL COMMENT 'INSERT语句内容',
`records_count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '记录数量',
`content_size` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '内容大小(字节)',
`content_hash` varchar(64) DEFAULT NULL COMMENT '内容SHA256哈希',
`backup_conditions` json DEFAULT NULL COMMENT '备份条件',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_backup_id` (`backup_id`),
KEY `idx_table_name` (`table_name`),
KEY `idx_records_count` (`records_count`),
KEY `idx_created_at` (`created_at`),
FOREIGN KEY (`backup_id`) REFERENCES `kku_cleanup_backups` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='SQL备份记录表';
CREATE TABLE `kku_cleanup_backups` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`task_id` bigint(20) unsigned NOT NULL COMMENT '清理任务ID',
`backup_name` varchar(100) NOT NULL COMMENT '备份名称',
`backup_type` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '备份类型:1数据库备份',
`tables_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '备份表数量',
`records_count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '备份记录数量',
`backup_status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '备份状态:1进行中,2已完成,3已失败',
`started_at` timestamp NULL DEFAULT NULL COMMENT '备份开始时间',
`completed_at` timestamp NULL DEFAULT NULL COMMENT '备份完成时间',
`expires_at` timestamp NULL DEFAULT NULL COMMENT '备份过期时间',
`error_message` text COMMENT '错误信息',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_task_id` (`task_id`),
KEY `idx_backup_status` (`backup_status`),
KEY `idx_expires_at` (`expires_at`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='清理备份记录表';
/**
* 备份表到数据库
*
* @param CleanupBackup $backup 备份记录
* @param string $tableName 表名
* @return array 备份结果
*/
private static function backupTableToDatabase(CleanupBackup $backup, string $tableName): array
{
try {
// 获取表数据
$records = DB::table($tableName)->get();
if ($records->isEmpty()) {
// 即使没有数据也创建记录,记录表结构
$sqlContent = "-- 表 {$tableName} 的数据备份\n";
$sqlContent .= "-- 备份时间: " . now()->toDateTimeString() . "\n";
$sqlContent .= "-- 该表无数据记录\n\n";
// 获取表结构
$createTable = DB::select("SHOW CREATE TABLE `{$tableName}`")[0];
$sqlContent .= $createTable->{'Create Table'} . ";\n";
$recordsCount = 0;
} else {
// 生成INSERT语句
$sqlContent = static::generateInsertStatements($tableName, $records);
$recordsCount = $records->count();
}
$contentSize = strlen($sqlContent);
$contentHash = hash('sha256', $sqlContent);
// 保存到数据库
CleanupSqlBackup::create([
'backup_id' => $backup->id,
'table_name' => $tableName,
'sql_content' => $sqlContent,
'records_count' => $recordsCount,
'content_size' => $contentSize,
'content_hash' => $contentHash,
'backup_conditions' => null,
]);
return [
'success' => true,
'message' => "表 {$tableName} 数据库备份成功",
'file_size' => $contentSize,
'records_count' => $recordsCount,
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => $e->getMessage(),
'file_size' => 0,
'records_count' => 0,
];
}
}
/**
* 从数据库备份恢复数据
*
* @param int $backupId 备份记录ID
* @return bool 恢复是否成功
*/
public function restoreFromDatabaseBackup(int $backupId): bool
{
try {
DB::beginTransaction();
// 获取备份记录
$backup = CleanupBackup::find($backupId);
if (!$backup) {
throw new \Exception("备份记录不存在: {$backupId}");
}
// 获取所有SQL备份内容
$sqlBackups = CleanupSqlBackup::where('backup_id', $backupId)->get();
foreach ($sqlBackups as $sqlBackup) {
// 执行SQL语句恢复数据
$statements = $this->splitSqlStatements($sqlBackup->sql_content);
foreach ($statements as $statement) {
$statement = trim($statement);
if (!empty($statement) && !str_starts_with($statement, '--')) {
DB::statement($statement);
}
}
}
DB::commit();
Log::info('数据库备份恢复成功', [
'backup_id' => $backupId,
'tables_count' => $sqlBackups->count(),
'total_records' => $sqlBackups->sum('records_count')
]);
return true;
} catch (\Exception $e) {
DB::rollBack();
Log::error('数据库备份恢复失败', [
'backup_id' => $backupId,
'error' => $e->getMessage()
]);
return false;
}
}
/**
* 分割SQL语句
*
* @param string $sql SQL内容
* @return array SQL语句数组
*/
private function splitSqlStatements(string $sql): array
{
// 简单的SQL语句分割,按分号分割
$statements = explode(';', $sql);
return array_filter(array_map('trim', $statements));
}
/**
* 清理过期备份
*
* @return int 删除的备份数量
*/
public function cleanExpiredBackups(): int
{
$expiredBackups = CleanupBackup::where('expires_at', '<', now())
->where('backup_status', 2) // 已完成的备份
->get();
$deletedCount = 0;
foreach ($expiredBackups as $backup) {
try {
DB::beginTransaction();
// 删除相关的SQL备份记录
CleanupSqlBackup::where('backup_id', $backup->id)->delete();
// 删除备份记录
$backup->delete();
DB::commit();
$deletedCount++;
Log::info('删除过期备份成功', [
'backup_id' => $backup->id,
'backup_name' => $backup->backup_name
]);
} catch (\Exception $e) {
DB::rollBack();
Log::error('删除过期备份失败', [
'backup_id' => $backup->id,
'error' => $e->getMessage()
]);
}
}
return $deletedCount;
}
/**
* 验证数据库备份完整性
*
* @param int $backupId 备份ID
* @return bool 验证结果
*/
public function verifyBackupIntegrity(int $backupId): bool
{
try {
$backup = CleanupBackup::find($backupId);
if (!$backup) {
return false;
}
// 检查SQL备份记录是否存在
$sqlBackupsCount = CleanupSqlBackup::where('backup_id', $backupId)->count();
if ($sqlBackupsCount === 0) {
return false;
}
// 验证每个SQL备份的内容哈希
$sqlBackups = CleanupSqlBackup::where('backup_id', $backupId)->get();
foreach ($sqlBackups as $sqlBackup) {
$currentHash = hash('sha256', $sqlBackup->sql_content);
if ($currentHash !== $sqlBackup->content_hash) {
Log::warning('SQL备份内容哈希验证失败', [
'backup_id' => $backupId,
'sql_backup_id' => $sqlBackup->id,
'table_name' => $sqlBackup->table_name
]);
return false;
}
}
return true;
} catch (\Exception $e) {
Log::error('备份验证失败', [
'backup_id' => $backupId,
'error' => $e->getMessage()
]);
return false;
}
}
// config/cleanup.php
return [
'backup' => [
// 备份类型(固定为数据库备份)
'type' => 'database',
// 备份保留天数
'retention_days' => 30,
// 分批处理大小
'batch_size' => 1000,
// 是否自动清理过期备份
'auto_cleanup' => true,
// 生成INSERT语句时的最大记录数
'max_records_per_statement' => 100,
]
];
这个数据库备份设计专注于将备份数据直接存储到数据库中,具有以下优势:
这个备份方案专注于数据库备份,提供了完整的数据保护机制,确保在清理数据前能够安全地备份将要删除的数据,并支持快速恢复。