- Исправлен 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
260 lines
12 KiB
PHP
260 lines
12 KiB
PHP
<?php
|
||
error_reporting(E_ALL);
|
||
ini_set('display_errors', 1);
|
||
|
||
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';
|
||
$docIds = [386869, 394973];
|
||
$s3Bucket = $config['s3']['bucket'];
|
||
|
||
echo "Поиск файлов в корзине/удаленных версиях S3\n";
|
||
echo str_repeat("=", 80) . "\n\n";
|
||
|
||
try {
|
||
// Получаем информацию о документах из БД
|
||
$pdo = new PDO(
|
||
"mysql:host={$dbconfig['db_server']};port=3306;dbname={$dbconfig['db_name']};charset=utf8",
|
||
$dbconfig['db_username'],
|
||
$dbconfig['db_password'],
|
||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||
);
|
||
|
||
// Инициализация S3 клиента
|
||
$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
|
||
]);
|
||
|
||
foreach ($docIds as $docId) {
|
||
echo "Поиск файла для документа $docId:\n";
|
||
echo str_repeat("-", 80) . "\n";
|
||
|
||
$stmt = $pdo->prepare('SELECT notesid, title, s3_key, s3_etag, filesize FROM vtiger_notes WHERE notesid = ?');
|
||
$stmt->execute([$docId]);
|
||
$doc = $stmt->fetch(PDO::FETCH_ASSOC);
|
||
|
||
if (!$doc) {
|
||
echo " Документ не найден в БД\n\n";
|
||
continue;
|
||
}
|
||
|
||
echo " Название: {$doc['title']}\n";
|
||
echo " Ожидаемый путь: {$doc['s3_key']}\n";
|
||
echo " ETag: {$doc['s3_etag']}\n";
|
||
echo " Размер: " . number_format($doc['filesize'] / 1024, 2) . " KB\n\n";
|
||
|
||
$s3Key = $doc['s3_key'];
|
||
$targetEtag = trim($doc['s3_etag'], '"');
|
||
$targetSize = $doc['filesize'];
|
||
|
||
// 1. Проверяем версии объекта (если включено versioning)
|
||
echo " 1. Проверка версий объекта...\n";
|
||
try {
|
||
$versions = $s3Client->listObjectVersions([
|
||
'Bucket' => $s3Bucket,
|
||
'Prefix' => $s3Key,
|
||
'MaxKeys' => 100
|
||
]);
|
||
|
||
if (isset($versions['Versions']) && !empty($versions['Versions'])) {
|
||
echo " ✅ Найдено версий: " . count($versions['Versions']) . "\n";
|
||
foreach ($versions['Versions'] as $version) {
|
||
$versionKey = $version['Key'];
|
||
$versionId = $version['VersionId'];
|
||
$isDeleteMarker = isset($version['IsDeleteMarker']) && $version['IsDeleteMarker'];
|
||
$versionEtag = isset($version['ETag']) ? trim($version['ETag'], '"') : null;
|
||
$versionSize = isset($version['Size']) ? $version['Size'] : null;
|
||
|
||
if ($isDeleteMarker) {
|
||
echo " ⚠️ Delete Marker: VersionId=$versionId, Дата: " . ($version['LastModified'] ?? 'не указана') . "\n";
|
||
} else {
|
||
$matchInfo = [];
|
||
if ($versionEtag === $targetEtag) {
|
||
$matchInfo[] = "ETag совпадает";
|
||
}
|
||
if ($versionSize && abs($versionSize - $targetSize) <= 100) {
|
||
$matchInfo[] = "размер совпадает (" . number_format($versionSize / 1024, 2) . " KB)";
|
||
}
|
||
|
||
echo " ✅ Версия: VersionId=$versionId\n";
|
||
echo " Размер: " . ($versionSize ? number_format($versionSize / 1024, 2) . " KB" : 'не указан') . "\n";
|
||
echo " ETag: " . ($versionEtag ?: 'не указан') . "\n";
|
||
echo " Дата: " . ($version['LastModified'] ?? 'не указана') . "\n";
|
||
if (!empty($matchInfo)) {
|
||
echo " " . implode(', ', $matchInfo) . "\n";
|
||
}
|
||
echo "\n";
|
||
}
|
||
}
|
||
} else {
|
||
echo " ❌ Версии не найдены (возможно, versioning не включен)\n";
|
||
}
|
||
} catch (\Aws\Exception\AwsException $e) {
|
||
if ($e->getAwsErrorCode() == 'AccessDenied' || strpos($e->getMessage(), 'versioning') !== false) {
|
||
echo " ⚠️ Версионирование не включено или нет доступа: " . $e->getAwsErrorCode() . "\n";
|
||
} else {
|
||
echo " ❌ Ошибка: " . $e->getMessage() . "\n";
|
||
}
|
||
}
|
||
echo "\n";
|
||
|
||
// 2. Проверяем delete markers
|
||
echo " 2. Проверка delete markers...\n";
|
||
try {
|
||
$deleteMarkers = $s3Client->listObjectVersions([
|
||
'Bucket' => $s3Bucket,
|
||
'Prefix' => $s3Key,
|
||
'MaxKeys' => 100
|
||
]);
|
||
|
||
if (isset($deleteMarkers['DeleteMarkers']) && !empty($deleteMarkers['DeleteMarkers'])) {
|
||
echo " ✅ Найдено delete markers: " . count($deleteMarkers['DeleteMarkers']) . "\n";
|
||
foreach ($deleteMarkers['DeleteMarkers'] as $marker) {
|
||
echo " ⚠️ Delete Marker найден:\n";
|
||
echo " VersionId: " . ($marker['VersionId'] ?? 'не указан') . "\n";
|
||
echo " Дата удаления: " . ($marker['LastModified'] ?? 'не указана') . "\n";
|
||
echo " Ключ: " . ($marker['Key'] ?? 'не указан') . "\n";
|
||
echo "\n";
|
||
echo " 💡 Файл был удален, но можно восстановить, удалив delete marker\n";
|
||
}
|
||
} else {
|
||
echo " ❌ Delete markers не найдены\n";
|
||
}
|
||
} catch (\Aws\Exception\AwsException $e) {
|
||
echo " ⚠️ Ошибка при проверке delete markers: " . $e->getAwsErrorCode() . "\n";
|
||
}
|
||
echo "\n";
|
||
|
||
// 3. Проверяем папки с названиями типа trash, deleted, recycle и т.д.
|
||
echo " 3. Поиск в папках корзины...\n";
|
||
$trashPrefixes = [
|
||
'trash/',
|
||
'deleted/',
|
||
'recycle/',
|
||
'.trash/',
|
||
'deleted_files/',
|
||
'removed/',
|
||
];
|
||
|
||
$foundInTrash = false;
|
||
foreach ($trashPrefixes as $trashPrefix) {
|
||
try {
|
||
$objects = $s3Client->listObjectsV2([
|
||
'Bucket' => $s3Bucket,
|
||
'Prefix' => $trashPrefix,
|
||
'MaxKeys' => 1000
|
||
]);
|
||
|
||
if (isset($objects['Contents'])) {
|
||
foreach ($objects['Contents'] as $object) {
|
||
$key = $object['Key'];
|
||
|
||
// Ищем файлы с ID документа или похожим размером
|
||
if (strpos($key, (string)$docId) !== false ||
|
||
(isset($object['Size']) && abs($object['Size'] - $targetSize) <= 100)) {
|
||
|
||
try {
|
||
$headResult = $s3Client->headObject([
|
||
'Bucket' => $s3Bucket,
|
||
'Key' => $key
|
||
]);
|
||
|
||
$fileEtag = isset($headResult['ETag']) ? trim($headResult['ETag'], '"') : null;
|
||
|
||
echo " ✅ НАЙДЕН в $trashPrefix:\n";
|
||
echo " Путь: $key\n";
|
||
echo " Размер: " . number_format($headResult['ContentLength'] / 1024, 2) . " KB\n";
|
||
echo " ETag: " . ($fileEtag ?: 'не указан') . "\n";
|
||
|
||
if ($fileEtag === $targetEtag) {
|
||
echo " ✅ ТОЧНОЕ СОВПАДЕНИЕ ПО ETAG!\n";
|
||
}
|
||
|
||
$foundInTrash = true;
|
||
echo "\n";
|
||
} catch (\Aws\Exception\AwsException $e) {
|
||
// Пропускаем
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (\Aws\Exception\AwsException $e) {
|
||
// Пропускаем ошибки
|
||
}
|
||
}
|
||
|
||
if (!$foundInTrash) {
|
||
echo " ❌ Файлы не найдены в папках корзины\n";
|
||
}
|
||
echo "\n";
|
||
|
||
// 4. Проверяем, может быть файл был перемещен в другую папку проекта
|
||
echo " 4. Поиск по ETag во всех папках проекта...\n";
|
||
$projectId = 384256; // Из предыдущих запросов
|
||
|
||
$projectPrefixes = [
|
||
"crm2/CRM_Active_Files/Documents/Project/Гафиев_ООО_ЭДЭКС_$projectId/",
|
||
"temp/$projectId/",
|
||
];
|
||
|
||
$foundByEtag = false;
|
||
foreach ($projectPrefixes as $prefix) {
|
||
try {
|
||
$objects = $s3Client->listObjectsV2([
|
||
'Bucket' => $s3Bucket,
|
||
'Prefix' => $prefix,
|
||
'MaxKeys' => 1000
|
||
]);
|
||
|
||
if (isset($objects['Contents'])) {
|
||
foreach ($objects['Contents'] as $object) {
|
||
$key = $object['Key'];
|
||
|
||
try {
|
||
$headResult = $s3Client->headObject([
|
||
'Bucket' => $s3Bucket,
|
||
'Key' => $key
|
||
]);
|
||
|
||
$fileEtag = isset($headResult['ETag']) ? trim($headResult['ETag'], '"') : null;
|
||
|
||
if ($fileEtag === $targetEtag) {
|
||
echo " ✅ НАЙДЕН ПО ETAG в $prefix:\n";
|
||
echo " Путь: $key\n";
|
||
echo " Размер: " . number_format($headResult['ContentLength'] / 1024, 2) . " KB\n";
|
||
echo " ETag: $fileEtag\n";
|
||
echo " 💡 Это точно нужный файл!\n";
|
||
$foundByEtag = true;
|
||
echo "\n";
|
||
}
|
||
} catch (\Aws\Exception\AwsException $e) {
|
||
// Пропускаем
|
||
}
|
||
}
|
||
}
|
||
} catch (\Aws\Exception\AwsException $e) {
|
||
// Пропускаем ошибки
|
||
}
|
||
}
|
||
|
||
if (!$foundByEtag) {
|
||
echo " ❌ Файл с таким ETag не найден в папках проекта\n";
|
||
}
|
||
echo "\n";
|
||
}
|
||
|
||
} catch (Exception $e) {
|
||
echo "ОШИБКА: " . $e->getMessage() . "\n";
|
||
echo "Trace: " . $e->getTraceAsString() . "\n";
|
||
}
|
||
|