From 978f6b3cd887c9b159c2fbe66d585f49298e6b82 Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Fri, 23 Jan 2026 22:24:11 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B1=D0=B8=D1=82=D1=8B=D0=B9=20=D0=B0=D1=80?= =?UTF-8?q?=D1=85=D0=B8=D0=B2=20=D0=B1=D1=8D=D0=BA=D0=B0=D0=BF=D0=B0=20-?= =?UTF-8?q?=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shell_exec некорректно работает с бинарными данными. Теперь mysqldump пишет во временный файл, который потом стримится клиенту. Co-Authored-By: Claude Opus 4.5 --- public/index.php | 27 +++++++++++++++++++++------ src/BackupService.php | 39 +++++++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/public/index.php b/public/index.php index ba5c11a..ccb5315 100644 --- a/public/index.php +++ b/public/index.php @@ -363,14 +363,22 @@ $app->get('/api/backup/all', function (Request $request, Response $response) use try { $pdo = $container->get('db'); $backup = new \App\BackupService($_SESSION['db_user'], $_SESSION['db_pass']); - $data = $backup->dumpAllDatabases($pdo); + $tempFile = $backup->dumpAllDatabases($pdo); $filename = 'backup_all_' . date('Y-m-d_H-i-s') . '.sql.gz'; - $response->getBody()->write($data); + $stream = fopen($tempFile, 'rb'); + $filesize = filesize($tempFile); + + // Удаляем временный файл после отправки + register_shutdown_function(function() use ($tempFile) { + if (file_exists($tempFile)) unlink($tempFile); + }); + return $response ->withHeader('Content-Type', 'application/gzip') ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') - ->withHeader('Content-Length', strlen($data)); + ->withHeader('Content-Length', $filesize) + ->withBody(new \Slim\Psr7\Stream($stream)); } catch (\Exception $e) { $response->getBody()->write(json_encode(['error' => $e->getMessage()])); return $response->withHeader('Content-Type', 'application/json')->withStatus(500); @@ -382,14 +390,21 @@ $app->get('/api/backup/database/{name}', function (Request $request, Response $r try { $database = $args['name']; $backup = new \App\BackupService($_SESSION['db_user'], $_SESSION['db_pass']); - $data = $backup->dumpDatabase($database); + $tempFile = $backup->dumpDatabase($database); $filename = $database . '_' . date('Y-m-d_H-i-s') . '.sql.gz'; - $response->getBody()->write($data); + $stream = fopen($tempFile, 'rb'); + $filesize = filesize($tempFile); + + register_shutdown_function(function() use ($tempFile) { + if (file_exists($tempFile)) unlink($tempFile); + }); + return $response ->withHeader('Content-Type', 'application/gzip') ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') - ->withHeader('Content-Length', strlen($data)); + ->withHeader('Content-Length', $filesize) + ->withBody(new \Slim\Psr7\Stream($stream)); } catch (\Exception $e) { $response->getBody()->write(json_encode(['error' => $e->getMessage()])); return $response->withHeader('Content-Type', 'application/json')->withStatus(500); diff --git a/src/BackupService.php b/src/BackupService.php index 39e1a3f..62f7482 100644 --- a/src/BackupService.php +++ b/src/BackupService.php @@ -23,30 +23,36 @@ class BackupService } /** - * Создать дамп и вернуть содержимое (gzip сжатое) + * Создать дамп и вернуть путь к временному файлу */ public function dumpDatabase(string $database): string { + $tempFile = sys_get_temp_dir() . '/backup_' . $database . '_' . uniqid() . '.sql.gz'; + $cmd = sprintf( - 'mysqldump --host=%s --port=%s --user=%s --password=%s --single-transaction --routines --triggers %s 2>/dev/null | gzip', + 'mysqldump --host=%s --port=%s --user=%s --password=%s --single-transaction --routines --triggers %s 2>&1 | gzip > %s', escapeshellarg($this->host), escapeshellarg($this->port), escapeshellarg($this->user), escapeshellarg($this->pass), - escapeshellarg($database) + escapeshellarg($database), + escapeshellarg($tempFile) ); - $output = shell_exec($cmd); + exec($cmd, $output, $returnCode); - if ($output === null || $output === '') { - throw new \RuntimeException("Failed to create dump for database: $database"); + if ($returnCode !== 0 || !file_exists($tempFile) || filesize($tempFile) < 100) { + if (file_exists($tempFile)) { + unlink($tempFile); + } + throw new \RuntimeException("mysqldump failed: " . implode("\n", $output)); } - return $output; + return $tempFile; } /** - * Создать дамп всех баз данных (один файл) + * Создать дамп всех баз данных */ public function dumpAllDatabases(\PDO $pdo): string { @@ -56,23 +62,28 @@ class BackupService throw new \RuntimeException("No databases available for backup"); } + $tempFile = sys_get_temp_dir() . '/backup_all_' . uniqid() . '.sql.gz'; $dbList = implode(' ', array_map('escapeshellarg', $databases)); $cmd = sprintf( - 'mysqldump --host=%s --port=%s --user=%s --password=%s --single-transaction --routines --triggers --databases %s 2>/dev/null | gzip', + 'mysqldump --host=%s --port=%s --user=%s --password=%s --single-transaction --routines --triggers --databases %s 2>&1 | gzip > %s', escapeshellarg($this->host), escapeshellarg($this->port), escapeshellarg($this->user), escapeshellarg($this->pass), - $dbList + $dbList, + escapeshellarg($tempFile) ); - $output = shell_exec($cmd); + exec($cmd, $output, $returnCode); - if ($output === null || $output === '') { - throw new \RuntimeException("Failed to create dump"); + if ($returnCode !== 0 || !file_exists($tempFile) || filesize($tempFile) < 100) { + if (file_exists($tempFile)) { + unlink($tempFile); + } + throw new \RuntimeException("mysqldump failed: " . implode("\n", $output)); } - return $output; + return $tempFile; } }