feat:음성 녹음 GCS 업로드 및 다운로드 기능 추가
- GoogleCloudStorageService 생성 (레거시 방식 JWT 인증) - 10MB 이상 파일은 Google Cloud Storage에 백업 (본사 연구용) - 오디오/파일 다운로드 라우트 추가 - voice-recorder.blade.php 인라인 x-data로 변경 (HTMX 호환) - SalesConsultation 모델에 gcs_uri 필드 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,10 @@
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Sales\SalesConsultation;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use App\Services\GoogleCloudStorageService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\View\View;
|
||||
|
||||
@@ -88,8 +90,10 @@ public function destroy(int $consultationId, Request $request): JsonResponse
|
||||
|
||||
/**
|
||||
* 음성 파일 업로드
|
||||
*
|
||||
* 10MB 이상 파일은 Google Cloud Storage에 업로드하여 본사 연구용으로 보관합니다.
|
||||
*/
|
||||
public function uploadAudio(Request $request): JsonResponse
|
||||
public function uploadAudio(Request $request, GoogleCloudStorageService $gcs): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'tenant_id' => 'required|integer|exists:tenants,id',
|
||||
@@ -109,18 +113,30 @@ public function uploadAudio(Request $request): JsonResponse
|
||||
// 파일 저장
|
||||
$file = $request->file('audio');
|
||||
$fileName = 'audio_' . now()->format('Ymd_His') . '_' . uniqid() . '.' . $file->getClientOriginalExtension();
|
||||
$path = $file->storeAs("tenant/consultations/{$tenantId}", $fileName, 'local');
|
||||
$localPath = $file->storeAs("tenant/consultations/{$tenantId}", $fileName, 'local');
|
||||
$fileSize = $file->getSize();
|
||||
|
||||
// 10MB 이상 파일은 GCS에도 업로드 (본사 연구용)
|
||||
$gcsUri = null;
|
||||
$maxLocalSize = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
if ($fileSize > $maxLocalSize && $gcs->isAvailable()) {
|
||||
$gcsObjectName = "consultations/{$tenantId}/{$scenarioType}/{$fileName}";
|
||||
$localFullPath = Storage::disk('local')->path($localPath);
|
||||
$gcsUri = $gcs->upload($localFullPath, $gcsObjectName);
|
||||
}
|
||||
|
||||
// DB에 저장
|
||||
$consultation = SalesConsultation::createAudio(
|
||||
$tenantId,
|
||||
$scenarioType,
|
||||
$stepId,
|
||||
$path,
|
||||
$localPath,
|
||||
$fileName,
|
||||
$file->getSize(),
|
||||
$fileSize,
|
||||
$transcript,
|
||||
$duration
|
||||
$duration,
|
||||
$gcsUri
|
||||
);
|
||||
|
||||
$consultation->load('creator');
|
||||
@@ -136,6 +152,7 @@ public function uploadAudio(Request $request): JsonResponse
|
||||
'formatted_duration' => $consultation->formatted_duration,
|
||||
'created_by_name' => $consultation->creator->name,
|
||||
'created_at' => $consultation->created_at->format('Y-m-d H:i'),
|
||||
'has_gcs' => !empty($gcsUri),
|
||||
],
|
||||
]);
|
||||
}
|
||||
@@ -196,4 +213,69 @@ public function deleteFile(int $fileId, Request $request): JsonResponse
|
||||
{
|
||||
return $this->destroy($fileId, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 오디오 파일 다운로드
|
||||
*/
|
||||
public function downloadAudio(int $consultationId, GoogleCloudStorageService $gcs): Response
|
||||
{
|
||||
$consultation = SalesConsultation::findOrFail($consultationId);
|
||||
|
||||
if ($consultation->consultation_type !== 'audio') {
|
||||
abort(400, '오디오 파일이 아닙니다.');
|
||||
}
|
||||
|
||||
// GCS에 저장된 경우 서명된 URL로 리다이렉트
|
||||
if ($consultation->gcs_uri) {
|
||||
$objectName = str_replace('gs://' . $gcs->getBucketName() . '/', '', $consultation->gcs_uri);
|
||||
$signedUrl = $gcs->getSignedUrl($objectName, 60);
|
||||
|
||||
if ($signedUrl) {
|
||||
return response()->redirectTo($signedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// 로컬 파일 다운로드
|
||||
$localPath = Storage::disk('local')->path($consultation->file_path);
|
||||
|
||||
if (!file_exists($localPath)) {
|
||||
abort(404, '파일을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$extension = pathinfo($consultation->file_name, PATHINFO_EXTENSION) ?: 'webm';
|
||||
$mimeTypes = [
|
||||
'webm' => 'audio/webm',
|
||||
'wav' => 'audio/wav',
|
||||
'mp3' => 'audio/mpeg',
|
||||
'ogg' => 'audio/ogg',
|
||||
'm4a' => 'audio/mp4'
|
||||
];
|
||||
$contentType = $mimeTypes[$extension] ?? 'audio/webm';
|
||||
|
||||
$downloadFileName = '상담녹음_' . $consultation->created_at->format('Ymd_His') . '.' . $extension;
|
||||
|
||||
return response()->download($localPath, $downloadFileName, [
|
||||
'Content-Type' => $contentType,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 첨부파일 다운로드
|
||||
*/
|
||||
public function downloadFile(int $consultationId): Response
|
||||
{
|
||||
$consultation = SalesConsultation::findOrFail($consultationId);
|
||||
|
||||
if ($consultation->consultation_type !== 'file') {
|
||||
abort(400, '첨부파일이 아닙니다.');
|
||||
}
|
||||
|
||||
$localPath = Storage::disk('local')->path($consultation->file_path);
|
||||
|
||||
if (!file_exists($localPath)) {
|
||||
abort(404, '파일을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
return response()->download($localPath, $consultation->file_name);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user