фикс загрузки больших файлов с s3

This commit is contained in:
2026-03-22 01:22:07 +04:00
parent b123030cfa
commit 573e2afbbf
2 changed files with 18 additions and 9 deletions
@@ -6,7 +6,7 @@ use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\StreamedResponse;
class LauncherController extends Controller class LauncherController extends Controller
{ {
@@ -72,8 +72,8 @@ class LauncherController extends Controller
#[OA\Get( #[OA\Get(
path: '/api/launcher/download/{path}', path: '/api/launcher/download/{path}',
summary: 'Скачать файл клиента', summary: 'Получить ссылку на скачивание файла',
description: 'Стримит файл из S3 по относительному пути из манифеста. Путь может содержать вложенные директории (например `Data/ruRU/patch-ruRU-3.MPQ`).', description: 'Возвращает временную presigned-ссылку на файл в S3. Лаунчер скачивает файл напрямую из хранилища по этой ссылке. Путь может содержать вложенные директории (например `Data/ruRU/patch-ruRU-3.MPQ`).',
tags: ['Launcher'], tags: ['Launcher'],
security: [['bearerAuth' => []]], security: [['bearerAuth' => []]],
parameters: [ parameters: [
@@ -89,10 +89,12 @@ class LauncherController extends Controller
responses: [ responses: [
new OA\Response( new OA\Response(
response: 200, response: 200,
description: 'Бинарное содержимое файла', description: 'Временная ссылка на скачивание',
content: new OA\MediaType( content: new OA\JsonContent(
mediaType: 'application/octet-stream', properties: [
schema: new OA\Schema(type: 'string', format: 'binary'), new OA\Property(property: 'url', type: 'string', format: 'uri', description: 'Presigned URL для скачивания'),
new OA\Property(property: 'expires_in', type: 'integer', description: 'Время жизни ссылки в секундах', example: 3600),
],
), ),
), ),
new OA\Response( new OA\Response(
@@ -115,7 +117,7 @@ class LauncherController extends Controller
), ),
], ],
)] )]
public function download(string $path): StreamedResponse|JsonResponse public function download(string $path): JsonResponse
{ {
$disk = Storage::disk($this->disk()); $disk = Storage::disk($this->disk());
$fullPath = $this->basePath().'/'.$path; $fullPath = $this->basePath().'/'.$path;
@@ -124,7 +126,13 @@ class LauncherController extends Controller
return response()->json(['message' => 'Файл не найден.'], 404); return response()->json(['message' => 'Файл не найден.'], 404);
} }
return $disk->download($fullPath); $ttlMinutes = config('moonwell.launcher.download_ttl', 60);
$url = $disk->temporaryUrl($fullPath, now()->addMinutes($ttlMinutes));
return response()->json([
'url' => $url,
'expires_in' => $ttlMinutes * 60,
]);
} }
private function disk(): string private function disk(): string
+1
View File
@@ -28,6 +28,7 @@ return [
'disk' => env('MOONWELL_LAUNCHER_DISK', 's3'), 'disk' => env('MOONWELL_LAUNCHER_DISK', 's3'),
'base_path' => env('MOONWELL_LAUNCHER_BASE_PATH', 'World of Warcraft'), 'base_path' => env('MOONWELL_LAUNCHER_BASE_PATH', 'World of Warcraft'),
'manifest_key' => env('MOONWELL_LAUNCHER_MANIFEST_KEY', 'manifest.json'), 'manifest_key' => env('MOONWELL_LAUNCHER_MANIFEST_KEY', 'manifest.json'),
'download_ttl' => (int) env('MOONWELL_LAUNCHER_DOWNLOAD_TTL', 60),
], ],
'realm' => [ 'realm' => [