Files
crm.clientright.ru/restore_all_deleted_files.php

275 lines
14 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* Скрипт для массового восстановления всех удаленных файлов из S3
*
* Восстанавливает файлы, удаленные через delete markers, удаляя эти маркеры
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
set_time_limit(0); // Без ограничения времени
require_once '/var/www/fastuser/data/www/crm.clientright.ru/vendor/autoload.php';
require_once '/var/www/fastuser/data/www/crm.clientright.ru/config.inc.php';
$config = require '/var/www/fastuser/data/www/crm.clientright.ru/crm_extensions/file_storage/config.php';
$s3Bucket = $config['s3']['bucket'];
echo "Массовое восстановление удаленных файлов из S3\n";
echo str_repeat("=", 80) . "\n\n";
// Параметры
$dryRun = isset($argv[1]) && $argv[1] === '--dry-run';
$limit = isset($argv[2]) ? (int)$argv[2] : null; // Ограничение количества файлов
$prefix = isset($argv[3]) ? $argv[3] : 'crm2/CRM_Active_Files/Documents/'; // Префикс для поиска
if ($dryRun) {
echo "⚠️ РЕЖИМ ПРОВЕРКИ (dry-run) - файлы не будут восстановлены\n\n";
}
try {
$s3Client = new \Aws\S3\S3Client([
'version' => 'latest',
'region' => $config['s3']['region'],
'endpoint' => $config['s3']['endpoint'],
'use_path_style_endpoint' => true,
'credentials' => [
'key' => $config['s3']['key'],
'secret' => $config['s3']['secret'],
],
'suppress_php_deprecation_warning' => true
]);
$stats = [
'total_markers' => 0,
'restored' => 0,
'failed' => 0,
'skipped' => 0,
'errors' => []
];
$processedKeys = []; // Для отслеживания уже обработанных ключей
echo "Поиск delete markers в префиксе: $prefix\n";
echo "Ограничение: " . ($limit ? "$limit файлов" : "нет") . "\n\n";
$isTruncated = true;
$continuationToken = null;
$pageCount = 0;
$maxPages = isset($argv[4]) ? (int)$argv[4] : 10; // БЕЗОПАСНО: максимум 10 страниц по умолчанию (можно увеличить через параметр)
echo "⚠️ ВНИМАНИЕ: Обработка ограничена {$maxPages} страницами для безопасности\n";
echo " Для обработки большего количества используйте: php restore_all_deleted_files.php [--dry-run] [limit] [prefix] [maxPages]\n\n";
while ($isTruncated && $pageCount < $maxPages && (!$limit || $stats['restored'] + $stats['failed'] < $limit)) {
$params = [
'Bucket' => $s3Bucket,
'Prefix' => $prefix,
'MaxKeys' => 100 // БЕЗОПАСНО: уменьшено с 1000 до 100 для снижения нагрузки
];
if ($continuationToken) {
$params['ContinuationToken'] = $continuationToken;
}
echo "Обработка страницы " . ($pageCount + 1) . "/{$maxPages}...\r";
try {
$versions = $s3Client->listObjectVersions($params);
$pageCount++;
// БЕЗОПАСНОСТЬ: пауза между страницами для снижения нагрузки
if ($pageCount < $maxPages && $isTruncated) {
usleep(500000); // 0.5 секунды пауза между страницами
}
if (isset($versions['DeleteMarkers']) && !empty($versions['DeleteMarkers'])) {
foreach ($versions['DeleteMarkers'] as $marker) {
$key = $marker['Key'];
$versionId = $marker['VersionId'];
$deleteDate = $marker['LastModified'] ?? 'не указана';
// Пропускаем, если уже обработали этот ключ
if (isset($processedKeys[$key])) {
continue;
}
$stats['total_markers']++;
// Проверяем лимит
if ($limit && ($stats['restored'] + $stats['failed']) >= $limit) {
break 2; // Выходим из обоих циклов
}
// Пропускаем папки (заканчиваются на /)
if (substr($key, -1) === '/') {
$stats['skipped']++;
continue;
}
try {
if (!$dryRun) {
// Сначала проверяем, есть ли версии файла
try {
$versionsList = $s3Client->listObjectVersions([
'Bucket' => $s3Bucket,
'Prefix' => $key,
'MaxKeys' => 10
]);
$hasVersions = isset($versionsList['Versions']) && !empty($versionsList['Versions']);
if ($hasVersions) {
// Есть версии - удаляем delete marker и файл восстановится автоматически
$s3Client->deleteObject([
'Bucket' => $s3Bucket,
'Key' => $key,
'VersionId' => $versionId
]);
// Проверяем, восстановился ли файл
try {
$headResult = $s3Client->headObject([
'Bucket' => $s3Bucket,
'Key' => $key
]);
$stats['restored']++;
$processedKeys[$key] = true;
// БЕЗОПАСНОСТЬ: пауза каждые 10 файлов
if ($stats['restored'] % 10 == 0) {
echo "Восстановлено: {$stats['restored']} файлов...\r";
usleep(200000); // 0.2 секунды пауза
}
} catch (\Aws\Exception\AwsException $e) {
if ($e->getAwsErrorCode() == 'NotFound') {
// Файл все еще не доступен, пробуем восстановить последнюю версию вручную
$latestVersion = $versionsList['Versions'][0];
$latestVersionId = $latestVersion['VersionId'];
try {
// Копируем версию в текущий объект
$s3Client->copyObject([
'Bucket' => $s3Bucket,
'CopySource' => urlencode($s3Bucket . '/' . $key) . '?versionId=' . $latestVersionId,
'Key' => $key
]);
$stats['restored']++;
$processedKeys[$key] = true;
if ($stats['restored'] % 100 == 0) {
echo "Восстановлено: {$stats['restored']} файлов...\r";
}
} catch (\Aws\Exception\AwsException $e2) {
$stats['failed']++;
$stats['errors'][] = "Ошибка восстановления версии $key: " . $e2->getMessage();
}
} else {
$stats['failed']++;
$stats['errors'][] = "Ошибка проверки $key: " . $e->getMessage();
}
}
} else {
// Нет версий - файл удален безвозвратно
$stats['failed']++;
$stats['errors'][] = "Не найдена версия для восстановления: $key (файл удален безвозвратно)";
}
} catch (\Aws\Exception\AwsException $e) {
$stats['failed']++;
$stats['errors'][] = "Ошибка проверки версий для $key: " . $e->getMessage();
}
} else {
// Dry-run режим - проверяем наличие версий
try {
$versionsList = $s3Client->listObjectVersions([
'Bucket' => $s3Bucket,
'Prefix' => $key,
'MaxKeys' => 1
]);
if (isset($versionsList['Versions']) && !empty($versionsList['Versions'])) {
$stats['restored']++;
} else {
$stats['failed']++;
}
$processedKeys[$key] = true;
if (($stats['restored'] + $stats['failed']) % 100 == 0) {
echo "Проверено: " . ($stats['restored'] + $stats['failed']) . " файлов...\r";
}
} catch (\Aws\Exception\AwsException $e) {
$stats['failed']++;
$processedKeys[$key] = true;
}
}
} catch (\Aws\Exception\AwsException $e) {
$stats['failed']++;
$stats['errors'][] = "Ошибка удаления delete marker для $key: " . $e->getMessage();
if (count($stats['errors']) <= 10) {
echo "\n Ошибка: {$stats['errors'][count($stats['errors']) - 1]}\n";
}
}
}
}
$isTruncated = isset($versions['IsTruncated']) && $versions['IsTruncated'];
$continuationToken = isset($versions['NextContinuationToken']) ? $versions['NextContinuationToken'] : null;
if (!$isTruncated) {
break;
}
} catch (\Aws\Exception\AwsException $e) {
echo "\n❌ Ошибка при обработке страницы: " . $e->getMessage() . "\n";
break;
}
}
echo "\n\n";
echo str_repeat("=", 80) . "\n";
echo "ИТОГОВЫЙ ОТЧЕТ:\n\n";
echo "Всего найдено delete markers: {$stats['total_markers']}\n";
echo "Восстановлено файлов: {$stats['restored']}\n";
echo "Ошибок: {$stats['failed']}\n";
echo "Пропущено (папки): {$stats['skipped']}\n\n";
if (!empty($stats['errors']) && count($stats['errors']) <= 20) {
echo "Ошибки (первые " . count($stats['errors']) . "):\n";
foreach ($stats['errors'] as $error) {
echo " - $error\n";
}
echo "\n";
} elseif (!empty($stats['errors'])) {
echo "Всего ошибок: " . count($stats['errors']) . " (показаны первые 20)\n";
foreach (array_slice($stats['errors'], 0, 20) as $error) {
echo " - $error\n";
}
echo "\n";
}
if ($dryRun) {
echo "⚠️ Это был режим проверки. Для реального восстановления запустите:\n";
echo " php restore_all_deleted_files.php\n\n";
} else {
echo "✅ Восстановление завершено!\n";
echo " Проверьте доступность файлов в интерфейсе CRM\n\n";
}
// Сохраняем статистику в файл
$logFile = '/var/www/fastuser/data/www/crm.clientright.ru/restore_log_' . date('Y-m-d_H-i-s') . '.json';
file_put_contents($logFile, json_encode($stats, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
echo "📝 Лог сохранен в: $logFile\n";
} catch (Exception $e) {
echo "❌ КРИТИЧЕСКАЯ ОШИБКА: " . $e->getMessage() . "\n";
echo "Trace: " . $e->getTraceAsString() . "\n";
}