#!/usr/bin/env php isFile()) { continue; } $src = $fileInfo->getPathname(); $relative = ltrim(str_replace($snapshotDir, '', $src), DIRECTORY_SEPARATOR); if ($relative === '.snapshot_meta.json') { continue; } $dst = $uploadsDir . DIRECTORY_SEPARATOR . $relative; if ($dryRun) { echo "[RESTORE] {$relative}\n"; $restored++; continue; } $dstDir = dirname($dst); if (!is_dir($dstDir) && !mkdir($dstDir, 0755, true) && !is_dir($dstDir)) { echo "[ERR] 无法创建目录: {$dstDir}\n"; $errors++; continue; } if (!copy($src, $dst)) { echo "[ERR] 恢复失败: {$relative}\n"; $errors++; continue; } @chmod($dst, 0644); $restored++; $bytes += filesize($src) ?: 0; if ($restored % 200 === 0) { echo "--- 已恢复 {$restored} 文件 ---\n"; } } echo "\n========== 完成 ==========\n"; echo "恢复: {$restored} 个文件"; if (!$dryRun) { echo ', ' . formatBytes($bytes); } echo "\n"; echo "失败: {$errors}\n"; exit($errors > 0 ? 1 : 0); function listSnapshots(string $snapshotsRoot): void { if (!is_dir($snapshotsRoot)) { echo "暂无快照(目录不存在: {$snapshotsRoot})\n"; echo "请先执行: bash deploy/compress-uploads.sh --safe\n"; return; } $dirs = glob($snapshotsRoot . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR); if (!$dirs) { echo "暂无快照\n"; return; } rsort($dirs); echo "可用快照:\n"; foreach ($dirs as $dir) { $id = basename($dir); $metaFile = $dir . DIRECTORY_SEPARATOR . '.snapshot_meta.json'; $extra = ''; if (file_exists($metaFile)) { $meta = json_decode(file_get_contents($metaFile), true); if ($meta) { $extra = ' | ' . ($meta['created_at'] ?? '') . ' | ' . ($meta['image_count'] ?? '?') . ' 张 | ' . formatBytes((int) ($meta['total_bytes'] ?? 0)); } } $marker = ($dir === $dirs[0]) ? ' <- latest' : ''; echo " {$id}{$extra}{$marker}\n"; } } function resolveSnapshotDir(string $snapshotsRoot, string $id): ?string { if ($id === 'latest') { if (!is_dir($snapshotsRoot)) { return null; } $dirs = glob($snapshotsRoot . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR); if (!$dirs) { return null; } rsort($dirs); return $dirs[0]; } $dir = $snapshotsRoot . DIRECTORY_SEPARATOR . $id; return is_dir($dir) ? $dir : null; } function formatBytes(int $bytes): string { if ($bytes >= 1073741824) { return round($bytes / 1073741824, 2) . 'GB'; } if ($bytes >= 1048576) { return round($bytes / 1048576, 2) . 'MB'; } if ($bytes >= 1024) { return round($bytes / 1024, 1) . 'KB'; } return $bytes . 'B'; }