Files
crm.clientright.ru/crm_extensions/file_storage/n8n_s3_migration_json.php

333 lines
14 KiB
PHP
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
/**
* n8n S3 Migration Endpoint (JSON Output)
*
* Этот скрипт предназначен для вызова из n8n через SSH
* для автоматической миграции новых файлов в S3
* Выводит только JSON для удобного парсинга
*/
ini_set('memory_limit', '512M');
set_time_limit(0);
date_default_timezone_set('Europe/Moscow');
// Параметры по умолчанию
$defaults = [
'limit' => 20,
'dry_run' => 0
];
// Получение параметров из командной строки или переменных окружения
$limit = isset($argv[1]) ? (int)$argv[1] : (int)($_ENV['LIMIT'] ?? $defaults['limit']);
$dryRun = isset($argv[2]) ? (int)$argv[2] : (int)($_ENV['DRY_RUN'] ?? $defaults['dry_run']);
// Валидация параметров
$limit = max(1, $limit); // Минимум 1 файл
$dryRun = $dryRun ? 1 : 0;
// Подключение к базе данных
require_once '/var/www/fastuser/data/www/crm.clientright.ru/config.inc.php';
$mysqli = new mysqli($dbconfig['db_server'], $dbconfig['db_username'], $dbconfig['db_password'], $dbconfig['db_name']);
if ($mysqli->connect_error) {
echo json_encode([
'status' => 'error',
'timestamp' => date('Y-m-d H:i:s'),
'error' => 'Database connection failed: ' . $mysqli->connect_error,
'exit_code' => 3
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
exit(3);
}
$mysqli->set_charset("utf8");
// Подключение S3 сервиса
require_once '/var/www/fastuser/data/www/crm.clientright.ru/include/Storage/S3StorageService.php';
$s3Service = new S3StorageService();
$s3Bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
// Поиск локальных файлов без S3 метаданных (исключая очищенные файлы)
$query = "SELECT n.notesid, n.filename, n.filesize, n.filetype, n.filelocationtype,
a.attachmentsid, a.path, a.storedname
FROM vtiger_notes n
INNER JOIN vtiger_seattachmentsrel s ON s.crmid = n.notesid
INNER JOIN vtiger_attachments a ON a.attachmentsid = s.attachmentsid
WHERE (n.s3_key IS NULL OR n.s3_key = '')
AND n.filelocationtype = 'I'
AND NOT (n.filename IS NULL OR n.filename = '')
AND NOT (n.filesize = 0)
AND NOT (n.filename LIKE 'file_15_%')
AND NOT (n.filename = '7 заявление потребителя')
ORDER BY n.notesid ASC
LIMIT ?";
$stmt = $mysqli->prepare($query);
if (!$stmt) {
echo json_encode([
'status' => 'error',
'timestamp' => date('Y-m-d H:i:s'),
'error' => 'Query prepare failed: ' . $mysqli->error,
'exit_code' => 3
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
exit(3);
}
$stmt->bind_param('i', $limit);
$stmt->execute();
$result = $stmt->get_result();
$totalProcessed = 0;
$successfullyMigrated = 0;
$failedMigrations = 0;
$skippedFiles = 0;
$migratedFiles = [];
$problematicFiles = [];
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$totalProcessed++;
$notesid = $row['notesid'];
$filename = $row['filename'];
$attachmentsid = $row['attachmentsid'];
$path = $row['path'];
$storedname = $row['storedname'];
// Поиск локальных файлов (используем данные из vtiger_attachments)
$storageDir = '/var/www/fastuser/data/www/crm.clientright.ru/';
$localFilePath = null;
// Основной путь из vtiger_attachments
$mainPath = $storageDir . $path . $attachmentsid . '_' . $storedname;
if (file_exists($mainPath) && is_readable($mainPath)) {
$localFilePath = $mainPath;
}
// Если не найден по основному пути, используем старую логику поиска
if ($localFilePath === null) {
// Основные пути для поиска (приоритетные)
$priorityPaths = [
$storageDir . 'storage/' . $filename, // Прямо в корне storage
$storageDir . 'storage/' . $notesid . '_' . $filename, // С префиксом notesid
];
// Дополнительный поиск по имени файла (без ID)
$cleanFilename = $filename;
if (preg_match('/^\d+_(.+)$/', $filename, $matches)) {
$cleanFilename = $matches[1];
$priorityPaths[] = $storageDir . 'storage/' . $cleanFilename;
}
// Проверяем приоритетные пути
foreach ($priorityPaths as $path) {
if (file_exists($path) && is_readable($path)) {
$localFilePath = $path;
break;
}
}
// Если не найден в приоритетных путях, ищем в структурированном хранилище
if ($localFilePath === null) {
// Поиск по годам (только текущий и предыдущий)
$currentYear = date('Y');
for ($year = $currentYear - 1; $year <= $currentYear; $year++) {
$yearPath = $storageDir . 'storage/' . $year . '/';
if (!is_dir($yearPath)) continue;
// Поиск по месяцам (только последние 3 месяца)
$months = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
$currentMonth = (int)date('n') - 1; // 0-based
for ($i = 0; $i < 3; $i++) {
$monthIndex = ($currentMonth - $i + 12) % 12;
$month = $months[$monthIndex];
$monthPath = $yearPath . $month . '/';
if (!is_dir($monthPath)) continue;
// Проверяем основные варианты в месяце
$monthPaths = [
$monthPath . $filename,
$monthPath . $notesid . '_' . $filename,
];
// Добавляем поиск по чистому имени файла
if (isset($cleanFilename) && $cleanFilename !== $filename) {
$monthPaths[] = $monthPath . $cleanFilename;
}
// Поиск по близким ID (±10 от текущего)
for ($idOffset = -10; $idOffset <= 10; $idOffset++) {
$searchId = $notesid + $idOffset;
$monthPaths[] = $monthPath . $searchId . '_' . $filename;
if (isset($cleanFilename) && $cleanFilename !== $filename) {
$monthPaths[] = $monthPath . $searchId . '_' . $cleanFilename;
}
}
foreach ($monthPaths as $path) {
if (file_exists($path) && is_readable($path)) {
$localFilePath = $path;
break 3; // Выходим из всех циклов
}
}
// Поиск по неделям (все 4 недели)
for ($week = 1; $week <= 4; $week++) {
$weekPath = $monthPath . 'week' . $week . '/';
if (!is_dir($weekPath)) continue;
$weekPaths = [
$weekPath . $filename,
$weekPath . $notesid . '_' . $filename,
];
foreach ($weekPaths as $path) {
if (file_exists($path) && is_readable($path)) {
$localFilePath = $path;
break 4; // Выходим из всех циклов
}
}
// Поиск по близким ID в неделях
for ($idOffset = -10; $idOffset <= 10; $idOffset++) {
$searchId = $notesid + $idOffset;
$closeIdPath = $weekPath . $searchId . '_' . $filename;
if (file_exists($closeIdPath) && is_readable($closeIdPath)) {
$localFilePath = $closeIdPath;
break 4;
}
}
}
}
}
}
}
// Проверка существования файла
if ($localFilePath === null) {
// Помечаем файл как проблемный
$problematic_filename = "[PROBLEM_" . date('Y-m-d_H-i-s') . "] " . $filename;
if (!$dryRun) {
$update_query = "UPDATE vtiger_notes SET filename = ? WHERE notesid = ?";
$update_stmt = $mysqli->prepare($update_query);
if ($update_stmt) {
$update_stmt->bind_param('si', $problematic_filename, $notesid);
if ($update_stmt->execute()) {
$problematicFiles[] = [
'notesid' => $notesid,
'original_filename' => $filename,
'problematic_filename' => $problematic_filename,
'reason' => 'Physical file not found'
];
$skippedFiles++;
} else {
$failedMigrations++;
}
$update_stmt->close();
} else {
$failedMigrations++;
}
} else {
$problematicFiles[] = [
'notesid' => $notesid,
'original_filename' => $filename,
'reason' => 'Physical file not found (dry run)'
];
$skippedFiles++;
}
continue;
}
if (!$dryRun) {
try {
// Загрузка в S3 (используем правильную сигнатуру)
$s3_result = $s3Service->put($localFilePath, $notesid, $filename);
if ($s3_result && isset($s3_result['url'])) {
$s3_etag = isset($s3_result['etag']) ? trim($s3_result['etag'], '"') : '';
$s3_url = $s3_result['url'];
$s3_key = $s3_result['key'];
// Обновление базы данных
$update_query = "UPDATE vtiger_notes SET
s3_key = ?,
s3_bucket = ?,
s3_etag = ?,
filename = ?,
filelocationtype = 'E'
WHERE notesid = ?";
$update_stmt = $mysqli->prepare($update_query);
if (!$update_stmt) {
$failedMigrations++;
continue;
}
$update_stmt->bind_param('ssssi', $s3_key, $s3Bucket, $s3_etag, $s3_url, $notesid);
if ($update_stmt->execute()) {
$migratedFiles[] = [
'notesid' => $notesid,
'filename' => $filename,
's3_url' => $s3_url,
's3_key' => $s3_key,
'local_file_deleted' => unlink($localFilePath)
];
$successfullyMigrated++;
} else {
$failedMigrations++;
}
$update_stmt->close();
} else {
$failedMigrations++;
}
} catch (Exception $e) {
$failedMigrations++;
}
} else {
$migratedFiles[] = [
'notesid' => $notesid,
'filename' => $filename,
'local_file_path' => $localFilePath,
'dry_run' => true
];
$successfullyMigrated++;
}
}
}
$stmt->close();
$mysqli->close();
// JSON вывод для n8n
$jsonOutput = [
'status' => 'success',
'timestamp' => date('Y-m-d H:i:s'),
'summary' => [
'total_processed' => $totalProcessed,
'successfully_migrated' => $successfullyMigrated,
'failed' => $failedMigrations,
'marked_problematic' => $skippedFiles,
'dry_run' => $dryRun ? true : false
],
'migrated_files' => $migratedFiles,
'problematic_files' => $problematicFiles,
'exit_code' => 0
];
// Определяем статус и код выхода
if ($failedMigrations > 0) {
$jsonOutput['status'] = 'partial_error';
$jsonOutput['exit_code'] = 2;
} elseif ($skippedFiles > 0) {
$jsonOutput['status'] = 'warning';
$jsonOutput['exit_code'] = 1;
}
// Выводим JSON
echo json_encode($jsonOutput, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
// Возвращаем код выхода для n8n
exit($jsonOutput['exit_code']);
?>