- Исправлен 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
237 lines
9.8 KiB
PHP
237 lines
9.8 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';
|
||
$s3Bucket = $config['s3']['bucket'];
|
||
|
||
echo "Детальный анализ паттернов удалений\n";
|
||
echo str_repeat("=", 80) . "\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
|
||
]);
|
||
|
||
// Анализируем удаления по времени
|
||
echo "1. Анализ удалений по времени суток...\n";
|
||
$deletionsByHour = [];
|
||
$deletionsByDay = [];
|
||
$batchDeletions = []; // Массовые удаления (много файлов за короткое время)
|
||
|
||
$totalChecked = 0;
|
||
$maxToCheck = 10000;
|
||
|
||
try {
|
||
$isTruncated = true;
|
||
$continuationToken = null;
|
||
$pageCount = 0;
|
||
$maxPages = 20;
|
||
|
||
$currentBatch = [];
|
||
$lastDeleteTime = null;
|
||
|
||
while ($isTruncated && $pageCount < $maxPages && $totalChecked < $maxToCheck) {
|
||
$params = [
|
||
'Bucket' => $s3Bucket,
|
||
'Prefix' => 'crm2/CRM_Active_Files/Documents/Project/',
|
||
'MaxKeys' => 1000
|
||
];
|
||
|
||
if ($continuationToken) {
|
||
$params['ContinuationToken'] = $continuationToken;
|
||
}
|
||
|
||
$versions = $s3Client->listObjectVersions($params);
|
||
$pageCount++;
|
||
|
||
if (isset($versions['DeleteMarkers'])) {
|
||
foreach ($versions['DeleteMarkers'] as $marker) {
|
||
$totalChecked++;
|
||
$deleteDate = isset($marker['LastModified']) ? $marker['LastModified'] : null;
|
||
|
||
if ($deleteDate) {
|
||
$dateTime = new DateTime($deleteDate);
|
||
$hour = $dateTime->format('H');
|
||
$day = $dateTime->format('Y-m-d');
|
||
|
||
if (!isset($deletionsByHour[$hour])) {
|
||
$deletionsByHour[$hour] = 0;
|
||
}
|
||
$deletionsByHour[$hour]++;
|
||
|
||
if (!isset($deletionsByDay[$day])) {
|
||
$deletionsByDay[$day] = 0;
|
||
}
|
||
$deletionsByDay[$day]++;
|
||
|
||
// Определяем массовые удаления (более 10 файлов за минуту)
|
||
$deleteTimestamp = strtotime($deleteDate);
|
||
if ($lastDeleteTime && abs($deleteTimestamp - $lastDeleteTime) < 60) {
|
||
$currentBatch[] = $marker;
|
||
} else {
|
||
if (count($currentBatch) > 10) {
|
||
$batchDeletions[] = [
|
||
'count' => count($currentBatch),
|
||
'time' => date('Y-m-d H:i:s', $lastDeleteTime),
|
||
'files' => array_slice($currentBatch, 0, 5) // Первые 5 для примера
|
||
];
|
||
}
|
||
$currentBatch = [$marker];
|
||
}
|
||
$lastDeleteTime = $deleteTimestamp;
|
||
}
|
||
}
|
||
}
|
||
|
||
$isTruncated = isset($versions['IsTruncated']) && $versions['IsTruncated'];
|
||
$continuationToken = isset($versions['NextContinuationToken']) ? $versions['NextContinuationToken'] : null;
|
||
|
||
if (!$isTruncated) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Проверяем последний батч
|
||
if (count($currentBatch) > 10) {
|
||
$batchDeletions[] = [
|
||
'count' => count($currentBatch),
|
||
'time' => $lastDeleteTime ? date('Y-m-d H:i:s', $lastDeleteTime) : 'неизвестно',
|
||
'files' => array_slice($currentBatch, 0, 5)
|
||
];
|
||
}
|
||
|
||
echo " Проверено delete markers: $totalChecked\n\n";
|
||
|
||
echo " Удаления по часам суток:\n";
|
||
ksort($deletionsByHour);
|
||
foreach ($deletionsByHour as $hour => $count) {
|
||
if ($count > 0) {
|
||
echo " {$hour}:00 - " . ($hour + 1) . ":00: $count удалений\n";
|
||
}
|
||
}
|
||
echo "\n";
|
||
|
||
echo " Удаления по дням (топ 15):\n";
|
||
arsort($deletionsByDay);
|
||
$count = 0;
|
||
foreach ($deletionsByDay as $day => $deleteCount) {
|
||
echo " $day: $deleteCount удалений\n";
|
||
if (++$count >= 15) break;
|
||
}
|
||
echo "\n";
|
||
|
||
if (!empty($batchDeletions)) {
|
||
echo " Массовые удаления (более 10 файлов за минуту): " . count($batchDeletions) . "\n";
|
||
foreach (array_slice($batchDeletions, 0, 5) as $batch) {
|
||
echo " Время: {$batch['time']}, удалено файлов: {$batch['count']}\n";
|
||
}
|
||
echo "\n";
|
||
}
|
||
|
||
} catch (\Aws\Exception\AwsException $e) {
|
||
echo " Ошибка: " . $e->getMessage() . "\n";
|
||
}
|
||
|
||
// Проверяем, может быть это связано с удалением документов в CRM
|
||
echo "2. Проверка связи с удалением документов в CRM...\n";
|
||
$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]
|
||
);
|
||
|
||
// Проверяем, сколько документов было удалено в последние дни
|
||
$stmt = $pdo->prepare('
|
||
SELECT DATE(e.modifiedtime) as delete_date, COUNT(*) as count
|
||
FROM vtiger_crmentity e
|
||
INNER JOIN vtiger_notes n ON n.notesid = e.crmid
|
||
WHERE e.deleted = 1
|
||
AND n.filelocationtype = "E"
|
||
AND e.modifiedtime >= DATE_SUB(NOW(), INTERVAL 60 DAY)
|
||
GROUP BY DATE(e.modifiedtime)
|
||
ORDER BY delete_date DESC
|
||
LIMIT 20
|
||
');
|
||
$stmt->execute();
|
||
$deletedDocs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
if (!empty($deletedDocs)) {
|
||
echo " Удаленные документы в CRM (за последние 60 дней):\n";
|
||
foreach ($deletedDocs as $doc) {
|
||
echo " {$doc['delete_date']}: {$doc['count']} документов\n";
|
||
}
|
||
echo "\n";
|
||
} else {
|
||
echo " Удаленных документов не найдено\n\n";
|
||
}
|
||
|
||
// Сравниваем даты удалений в S3 и CRM
|
||
echo "3. Сравнение дат удалений в S3 и CRM...\n";
|
||
if (!empty($deletionsByDay) && !empty($deletedDocs)) {
|
||
echo " Сравнение:\n";
|
||
foreach ($deletedDocs as $doc) {
|
||
$crmDate = $doc['delete_date'];
|
||
$s3Count = $deletionsByDay[$crmDate] ?? 0;
|
||
$crmCount = $doc['count'];
|
||
|
||
if ($s3Count > 0) {
|
||
echo " $crmDate:\n";
|
||
echo " Удалено в CRM: $crmCount документов\n";
|
||
echo " Delete markers в S3: $s3Count\n";
|
||
|
||
if ($s3Count > $crmCount * 2) {
|
||
echo " ⚠️ В S3 удалено значительно больше файлов!\n";
|
||
} elseif (abs($s3Count - $crmCount) <= 10) {
|
||
echo " ✅ Количество примерно совпадает\n";
|
||
}
|
||
echo "\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
echo str_repeat("=", 80) . "\n";
|
||
echo "ВЫВОДЫ:\n\n";
|
||
|
||
// Определяем наиболее вероятную причину
|
||
$maxHour = array_search(max($deletionsByHour), $deletionsByHour);
|
||
$maxDay = array_search(max($deletionsByDay), $deletionsByDay);
|
||
$maxDayCount = max($deletionsByDay);
|
||
|
||
echo "1. Пик удалений:\n";
|
||
echo " - Время: {$maxHour}:00\n";
|
||
echo " - День: $maxDay ($maxDayCount удалений)\n\n";
|
||
|
||
if ($maxHour >= 6 && $maxHour <= 8) {
|
||
echo "2. 💡 ВЕРОЯТНАЯ ПРИЧИНА: Автоматическая задача (cron job)\n";
|
||
echo " Удаления происходят рано утром (6-8 утра) - типичное время для cron\n\n";
|
||
}
|
||
|
||
if (!empty($batchDeletions)) {
|
||
echo "3. 💡 ВЕРОЯТНАЯ ПРИЧИНА: Массовое удаление (скрипт или автоматизация)\n";
|
||
echo " Найдено " . count($batchDeletions) . " случаев массового удаления\n\n";
|
||
}
|
||
|
||
echo "4. 💡 РЕКОМЕНДАЦИЯ: Проверить:\n";
|
||
echo " - DeleteOrphanedItems в Nextcloud (запускается ежедневно)\n";
|
||
echo " - Cron задачи, которые могут удалять файлы\n";
|
||
echo " - Логи Nextcloud на предмет массовых удалений\n";
|
||
|
||
} catch (Exception $e) {
|
||
echo "ОШИБКА: " . $e->getMessage() . "\n";
|
||
echo "Trace: " . $e->getTraceAsString() . "\n";
|
||
}
|
||
|