feat:AI 음성녹음 GCS 파일 다운로드 엔드포인트 추가

- GoogleCloudService에 downloadFromStorage 메서드 추가 (GCS REST API 사용)
- AiVoiceRecordingController에 download 메서드 추가 (스트림 응답)
- 다운로드 라우트 추가 (GET /{id}/download)
- 파일명은 제목 기반으로 생성, Content-Disposition 헤더 설정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-07 13:51:09 +09:00
parent d121a319b3
commit fa14a9fbec
3 changed files with 85 additions and 0 deletions

View File

@@ -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.'"');
}
}

View File

@@ -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;
}
}
/**
* 서비스 사용 가능 여부
*/

View File

@@ -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