diff --git a/app/Http/Controllers/System/AiVoiceRecordingController.php b/app/Http/Controllers/System/AiVoiceRecordingController.php index 991ffb07..62bf0b18 100644 --- a/app/Http/Controllers/System/AiVoiceRecordingController.php +++ b/app/Http/Controllers/System/AiVoiceRecordingController.php @@ -6,8 +6,10 @@ use App\Models\AiVoiceRecording; use App\Models\Interview\InterviewCategory; use App\Services\AiVoiceRecordingService; +use App\Services\GoogleCloudService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Str; use Illuminate\View\View; use Symfony\Component\HttpFoundation\Response; @@ -223,4 +225,39 @@ public function status(int $id): JsonResponse ], ]); } + + /** + * GCS 음성파일 다운로드 + */ + public function download(int $id): Response|JsonResponse + { + $recording = AiVoiceRecording::find($id); + + if (! $recording || ! $recording->audio_file_path) { + return response()->json([ + 'success' => false, + 'message' => '파일을 찾을 수 없습니다.', + ], 404); + } + + $googleCloudService = app(GoogleCloudService::class); + $content = $googleCloudService->downloadFromStorage($recording->audio_file_path); + + if (! $content) { + return response()->json([ + 'success' => false, + 'message' => '파일 다운로드에 실패했습니다.', + ], 500); + } + + // 파일 확장자 추출 + $extension = pathinfo($recording->audio_file_path, PATHINFO_EXTENSION) ?: 'webm'; + + // 파일명 생성 (제목 기반, URL 안전하게) + $filename = Str::slug($recording->title ?: 'recording').'.'. $extension; + + return response($content) + ->header('Content-Type', 'audio/'.$extension) + ->header('Content-Disposition', 'attachment; filename="'.$filename.'"'); + } } diff --git a/app/Services/GoogleCloudService.php b/app/Services/GoogleCloudService.php index d570976f..ed8fd35e 100644 --- a/app/Services/GoogleCloudService.php +++ b/app/Services/GoogleCloudService.php @@ -317,6 +317,53 @@ public function deleteFromStorage(string $objectName): bool } } + /** + * GCS에서 파일 다운로드 (스트림) + */ + public function downloadFromStorage(string $objectName): ?string + { + $token = $this->getAccessToken(); + if (! $token) { + Log::error('Google Cloud: 다운로드 토큰 획득 실패'); + + return null; + } + + $bucket = config('services.google.storage_bucket'); + if (! $bucket) { + Log::error('Google Cloud: Storage 버킷 설정 없음'); + + return null; + } + + try { + $url = 'https://storage.googleapis.com/storage/v1/b/'. + urlencode($bucket).'/o/'.urlencode($objectName).'?alt=media'; + + $response = Http::withToken($token)->get($url); + + if ($response->successful()) { + Log::info('Google Cloud: Storage 다운로드 성공', [ + 'object' => $objectName, + 'size' => strlen($response->body()), + ]); + + return $response->body(); + } + + Log::error('Google Cloud: Storage 다운로드 실패', [ + 'status' => $response->status(), + 'response' => $response->body(), + ]); + + return null; + } catch (\Exception $e) { + Log::error('Google Cloud: Storage 다운로드 예외', ['error' => $e->getMessage()]); + + return null; + } + } + /** * 서비스 사용 가능 여부 */ diff --git a/routes/web.php b/routes/web.php index 9c8fd95b..9de04a5f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -423,6 +423,7 @@ Route::post('/{id}/process', [AiVoiceRecordingController::class, 'processAudio'])->name('process'); Route::delete('/{id}', [AiVoiceRecordingController::class, 'destroy'])->name('destroy'); Route::get('/{id}/status', [AiVoiceRecordingController::class, 'status'])->name('status'); + Route::get('/{id}/download', [AiVoiceRecordingController::class, 'download'])->name('download'); }); // 명함 OCR API