Files
crm.clientright.ru/restore_all_deleted_files.php
Fedor 840acca51a feat(documents): дедупликация documents_meta и исправление field_label
- Исправлен N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js: использовать uploads_field_labels[0] вместо [grp]
- Создан SQL_CLAIMSAVE_FIXED_NEW_FLOW_DEDUP.sql с дедупликацией documents_meta
- Создан SQL_CLEANUP_DOCUMENTS_META_DUPLICATES.sql для очистки существующих дубликатов
- Создан полный уникальный индекс idx_document_texts_hash_unique на document_texts(file_hash)
- Добавлен SESSION_LOG_2025-11-28_documents_dedup.md с описанием всех изменений

Fixes:
- field_label теперь корректно отображает 'Переписка' вместо 'group-2'
- documents_meta не накапливает дубликаты при повторных сохранениях
- ON CONFLICT (file_hash) теперь работает для document_texts
2025-11-28 18:16:53 +03:00

275 lines
14 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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";
}