235 lines
8.3 KiB
PHP
235 lines
8.3 KiB
PHP
|
|
<?php
|
|||
|
|
/**
|
|||
|
|
* БЕЗОПАСНАЯ МИГРАЦИЯ ФАЙЛОВ ПРОЕКТА В НОВУЮ СТРУКТУРУ (v2)
|
|||
|
|
* ИСПРАВЛЕНИЕ: Декодирование URL-encoded путей
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
chdir('/var/www/fastuser/data/www/crm.clientright.ru');
|
|||
|
|
require_once 'include/utils/utils.php';
|
|||
|
|
require_once 'include/database/PearDatabase.php';
|
|||
|
|
require_once 'crm_extensions/vendor/autoload.php';
|
|||
|
|
|
|||
|
|
use Aws\S3\S3Client as AwsS3Client;
|
|||
|
|
|
|||
|
|
global $adb;
|
|||
|
|
|
|||
|
|
$options = getopt('', ['dry-run', 'project:', 'batch:', 'all']);
|
|||
|
|
|
|||
|
|
$dryRun = isset($options['dry-run']);
|
|||
|
|
$projectId = isset($options['project']) ? (int)$options['project'] : null;
|
|||
|
|
|
|||
|
|
$s3 = new AwsS3Client([
|
|||
|
|
'version' => 'latest',
|
|||
|
|
'region' => 'ru-1',
|
|||
|
|
'endpoint' => 'https://s3.twcstorage.ru',
|
|||
|
|
'use_path_style_endpoint' => true,
|
|||
|
|
'credentials' => [
|
|||
|
|
'key' => '2OMAK5ZNM900TAXM16J7',
|
|||
|
|
'secret' => 'f4ADllb5VZBAt2HdsyB8WcwVEU7U74MwFCa1DARG',
|
|||
|
|
],
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
$bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
|
|||
|
|
$logFile = __DIR__ . '/logs/migration_' . date('Y-m-d_H-i-s') . '.log';
|
|||
|
|
|
|||
|
|
if (!is_dir(__DIR__ . '/logs')) {
|
|||
|
|
mkdir(__DIR__ . '/logs', 0755, true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function writeLog($message, $toScreen = true) {
|
|||
|
|
global $logFile;
|
|||
|
|
$timestamp = date('Y-m-d H:i:s');
|
|||
|
|
$logMessage = "[$timestamp] $message\n";
|
|||
|
|
file_put_contents($logFile, $logMessage, FILE_APPEND);
|
|||
|
|
if ($toScreen) {
|
|||
|
|
echo $message . "\n";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function sanitizeFileName($name) {
|
|||
|
|
$name = str_replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|'], '_', $name);
|
|||
|
|
$name = preg_replace('/\s+/', ' ', $name);
|
|||
|
|
return trim($name);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function extractExtension($fileName) {
|
|||
|
|
$parts = explode('.', basename($fileName));
|
|||
|
|
return count($parts) > 1 ? array_pop($parts) : '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function migrateProject($projectId, $dryRun = false) {
|
|||
|
|
global $adb, $s3, $bucket;
|
|||
|
|
|
|||
|
|
writeLog("🔍 === МИГРАЦИЯ ПРОЕКТА $projectId ===");
|
|||
|
|
|
|||
|
|
if ($dryRun) {
|
|||
|
|
writeLog("⚠️ РЕЖИМ DRY-RUN - изменения НЕ будут применены");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$sql = "SELECT n.* FROM vtiger_notes n
|
|||
|
|
INNER JOIN vtiger_senotesrel r ON r.notesid = n.notesid
|
|||
|
|
WHERE r.crmid = ? AND n.filelocationtype = 'E'
|
|||
|
|
ORDER BY n.notesid";
|
|||
|
|
$result = $adb->pquery($sql, [$projectId]);
|
|||
|
|
|
|||
|
|
$count = $adb->num_rows($result);
|
|||
|
|
writeLog("📋 Найдено документов: $count");
|
|||
|
|
|
|||
|
|
if ($count === 0) {
|
|||
|
|
writeLog("⚠️ Нет документов для миграции");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$newFolderPath = "crm2/CRM_Active_Files/Documents/проекта_{$projectId}";
|
|||
|
|
writeLog("📁 Новая папка: $newFolderPath");
|
|||
|
|
|
|||
|
|
$stats = [
|
|||
|
|
'total' => $count,
|
|||
|
|
'success' => 0,
|
|||
|
|
'errors' => 0,
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
$usedNames = [];
|
|||
|
|
|
|||
|
|
for ($i = 0; $i < $count; $i++) {
|
|||
|
|
$doc = $adb->fetchByAssoc($result);
|
|||
|
|
$docId = $doc['notesid'];
|
|||
|
|
$title = sanitizeFileName($doc['title']);
|
|||
|
|
$oldFileName = $doc['filename'];
|
|||
|
|
|
|||
|
|
writeLog("\n📄 Документ $docId: {$doc['title']}");
|
|||
|
|
|
|||
|
|
// Извлекаем путь из URL и ДЕКОДИРУЕМ
|
|||
|
|
$oldS3Path = null;
|
|||
|
|
if (strpos($oldFileName, 'https://s3.twcstorage.ru/') === 0) {
|
|||
|
|
$oldS3Path = str_replace("https://s3.twcstorage.ru/$bucket/", '', $oldFileName);
|
|||
|
|
// ВАЖНО: Декодируем URL-encoded символы
|
|||
|
|
$oldS3Path = urldecode($oldS3Path);
|
|||
|
|
} elseif (strpos($oldFileName, 'crm2/') === 0) {
|
|||
|
|
$oldS3Path = urldecode($oldFileName);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!$oldS3Path) {
|
|||
|
|
writeLog(" ❌ Не удалось определить старый путь S3");
|
|||
|
|
$stats['errors']++;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
writeLog(" Старый S3 путь: $oldS3Path");
|
|||
|
|
|
|||
|
|
$extension = extractExtension($oldFileName);
|
|||
|
|
$baseNewName = $title ? "{$title}_{$docId}" : "document_{$docId}";
|
|||
|
|
$newFileName = $baseNewName . ($extension ? ".$extension" : '');
|
|||
|
|
|
|||
|
|
$counter = 1;
|
|||
|
|
$finalNewName = $newFileName;
|
|||
|
|
while (isset($usedNames[$finalNewName])) {
|
|||
|
|
$finalNewName = $baseNewName . "_{$counter}" . ($extension ? ".$extension" : '');
|
|||
|
|
$counter++;
|
|||
|
|
}
|
|||
|
|
$usedNames[$finalNewName] = true;
|
|||
|
|
|
|||
|
|
$newS3Path = "$newFolderPath/$finalNewName";
|
|||
|
|
writeLog(" Новый S3 путь: $newS3Path");
|
|||
|
|
|
|||
|
|
if ($dryRun) {
|
|||
|
|
writeLog(" [DRY-RUN] ✓ Будет скопировано");
|
|||
|
|
$stats['success']++;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// РЕАЛЬНАЯ МИГРАЦИЯ
|
|||
|
|
try {
|
|||
|
|
// Проверяем старый файл
|
|||
|
|
$headObject = $s3->headObject([
|
|||
|
|
'Bucket' => $bucket,
|
|||
|
|
'Key' => $oldS3Path,
|
|||
|
|
]);
|
|||
|
|
$oldSize = $headObject['ContentLength'];
|
|||
|
|
writeLog(" ✓ Старый файл найден, размер: " . number_format($oldSize / 1024, 2) . " KB");
|
|||
|
|
|
|||
|
|
// Копируем
|
|||
|
|
writeLog(" 📋 Копирую файл...");
|
|||
|
|
$s3->copyObject([
|
|||
|
|
'Bucket' => $bucket,
|
|||
|
|
'CopySource' => "$bucket/$oldS3Path",
|
|||
|
|
'Key' => $newS3Path,
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// Проверяем копию
|
|||
|
|
$headNewObject = $s3->headObject([
|
|||
|
|
'Bucket' => $bucket,
|
|||
|
|
'Key' => $newS3Path,
|
|||
|
|
]);
|
|||
|
|
$newSize = $headNewObject['ContentLength'];
|
|||
|
|
|
|||
|
|
if ($newSize !== $oldSize) {
|
|||
|
|
throw new Exception("Размер не совпадает! Старый: $oldSize, Новый: $newSize");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
writeLog(" ✅ Файл скопирован, размер совпадает: " . number_format($newSize / 1024, 2) . " KB");
|
|||
|
|
|
|||
|
|
// Обновляем БД
|
|||
|
|
$newUrl = "https://s3.twcstorage.ru/$bucket/$newS3Path";
|
|||
|
|
$updateSql = "UPDATE vtiger_notes SET filename = ? WHERE notesid = ?";
|
|||
|
|
$adb->pquery($updateSql, [$newUrl, $docId]);
|
|||
|
|
|
|||
|
|
writeLog(" ✅ База данных обновлена");
|
|||
|
|
writeLog(" ✅ УСПЕХ! Документ $docId мигрирован");
|
|||
|
|
|
|||
|
|
$stats['success']++;
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
writeLog(" ❌ ОШИБКА: " . $e->getMessage());
|
|||
|
|
$stats['errors']++;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$s3->deleteObject(['Bucket' => $bucket, 'Key' => $newS3Path]);
|
|||
|
|
writeLog(" 🗑️ Частичная копия удалена");
|
|||
|
|
} catch (Exception $cleanupError) {
|
|||
|
|
// Игнорируем
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
writeLog("\n📊 === СТАТИСТИКА МИГРАЦИИ ===");
|
|||
|
|
writeLog("Всего документов: {$stats['total']}");
|
|||
|
|
writeLog("Успешно: {$stats['success']}");
|
|||
|
|
writeLog("Ошибок: {$stats['errors']}");
|
|||
|
|
|
|||
|
|
return $stats;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
writeLog("🚀 === СТАРТ МИГРАЦИИ ФАЙЛОВ (v2) ===");
|
|||
|
|
writeLog("Время: " . date('Y-m-d H:i:s'));
|
|||
|
|
|
|||
|
|
if ($dryRun) {
|
|||
|
|
writeLog("\n⚠️⚠️⚠️ РЕЖИМ DRY-RUN - НИЧЕГО НЕ БУДЕТ ИЗМЕНЕНО ⚠️⚠️⚠️\n");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!$dryRun) {
|
|||
|
|
writeLog("\n💾 === СОЗДАНИЕ РЕЗЕРВНОЙ КОПИИ БД ===");
|
|||
|
|
$backupFile = "backup_before_migration_" . date('Y-m-d_H-i-s') . ".sql";
|
|||
|
|
$backupCmd = "mysqldump -u ci20465_72new -p'EcY979Rn' ci20465_72new vtiger_notes vtiger_senotesrel > $backupFile 2>&1";
|
|||
|
|
exec($backupCmd, $output, $returnCode);
|
|||
|
|
|
|||
|
|
if (file_exists($backupFile) && filesize($backupFile) > 0) {
|
|||
|
|
writeLog("✅ Резервная копия создана: $backupFile");
|
|||
|
|
} else {
|
|||
|
|
writeLog("❌ ОШИБКА создания резервной копии!");
|
|||
|
|
writeLog("🛑 МИГРАЦИЯ ОТМЕНЕНА!");
|
|||
|
|
exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($projectId) {
|
|||
|
|
writeLog("\n🎯 Миграция проекта: $projectId");
|
|||
|
|
migrateProject($projectId, $dryRun);
|
|||
|
|
} else {
|
|||
|
|
writeLog("\n❌ Укажите --project=ID");
|
|||
|
|
exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
writeLog("\n✅ === МИГРАЦИЯ ЗАВЕРШЕНА ===");
|
|||
|
|
writeLog("Лог: $logFile");
|