'RS256', 'typ' => 'JWT'])); $jwtClaim = base64_encode(json_encode([ 'iss' => $serviceAccount['client_email'], 'scope' => 'https://www.googleapis.com/auth/devstorage.full_control', 'aud' => 'https://oauth2.googleapis.com/token', 'exp' => $now + 3600, 'iat' => $now ])); $privateKey = openssl_pkey_get_private($serviceAccount['private_key']); if (!$privateKey) { error_log('GCS 업로드 실패: 개인 키 읽기 오류'); return false; } openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); openssl_free_key($privateKey); $jwt = $jwtHeader . '.' . $jwtClaim . '.' . base64_encode($signature); // OAuth 토큰 요청 $tokenCh = curl_init('https://oauth2.googleapis.com/token'); curl_setopt($tokenCh, CURLOPT_POST, true); curl_setopt($tokenCh, CURLOPT_POSTFIELDS, http_build_query([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion' => $jwt ])); curl_setopt($tokenCh, CURLOPT_RETURNTRANSFER, true); curl_setopt($tokenCh, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']); $tokenResponse = curl_exec($tokenCh); $tokenCode = curl_getinfo($tokenCh, CURLINFO_HTTP_CODE); curl_close($tokenCh); if ($tokenCode !== 200) { error_log('GCS 업로드 실패: OAuth 토큰 요청 실패 (HTTP ' . $tokenCode . ')'); return false; } $tokenData = json_decode($tokenResponse, true); if (!isset($tokenData['access_token'])) { error_log('GCS 업로드 실패: OAuth 토큰 없음'); return false; } $accessToken = $tokenData['access_token']; // GCS에 파일 업로드 $file_content = file_get_contents($file_path); $mime_type = mime_content_type($file_path) ?: 'audio/webm'; $upload_url = 'https://storage.googleapis.com/upload/storage/v1/b/' . urlencode($bucket_name) . '/o?uploadType=media&name=' . urlencode($object_name); $ch = curl_init($upload_url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $accessToken, 'Content-Type: ' . $mime_type, 'Content-Length: ' . strlen($file_content) ]); curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code === 200) { return 'gs://' . $bucket_name . '/' . $object_name; } else { error_log('GCS 업로드 실패 (HTTP ' . $code . '): ' . $response); return false; } } // 출력 버퍼 비우기 ob_clean(); header('Content-Type: application/json; charset=utf-8'); // 1. 권한 및 세션 체크 if (!isset($user_id) || $level > 5) { echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']); exit; } $manager_id = $user_id; // session.php에서 user_id를 manager_id로 사용 $upload_dir = $_SERVER['DOCUMENT_ROOT'] . "/uploads/manager_consultations/" . $manager_id . "/"; // 2. 파일 업로드 처리 if (!file_exists($upload_dir)) mkdir($upload_dir, 0777, true); if (!isset($_FILES['audio_file'])) { echo json_encode(['success' => false, 'message' => '오디오 파일이 없습니다.']); exit; } // 파일 크기 확인 if ($_FILES['audio_file']['size'] == 0) { echo json_encode(['success' => false, 'message' => '오디오 파일이 비어있습니다.']); exit; } $file_name = date('Ymd_His') . "_" . uniqid() . ".webm"; $file_path = $upload_dir . $file_name; if (!move_uploaded_file($_FILES['audio_file']['tmp_name'], $file_path)) { echo json_encode(['success' => false, 'message' => '파일 저장 실패']); exit; } // 3. GCS 업로드 (선택사항 - 파일이 큰 경우) $gcs_uri = null; $file_size = filesize($file_path); $max_local_size = 10 * 1024 * 1024; // 10MB if ($file_size > $max_local_size) { // GCS 설정 확인 $gcs_config_file = $_SERVER['DOCUMENT_ROOT'] . '/apikey/gcs_config.txt'; $bucket_name = null; if (file_exists($gcs_config_file)) { $gcs_config = parse_ini_file($gcs_config_file); $bucket_name = isset($gcs_config['bucket_name']) ? $gcs_config['bucket_name'] : null; } if ($bucket_name) { $googleServiceAccountFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/google_service_account.json'; $gcs_object_name = 'manager_consultations/' . $manager_id . '/' . basename($file_path); $gcs_uri = uploadToGCS($file_path, $bucket_name, $gcs_object_name, $googleServiceAccountFile); if ($gcs_uri) { // GCS 업로드 성공 시 로컬 파일 삭제 (선택사항) // @unlink($file_path); } } } // 4. DB 저장 $transcript = isset($_POST['transcript']) ? trim($_POST['transcript']) : ''; $step_id = isset($_POST['step_id']) ? intval($_POST['step_id']) : 2; $web_path = "/uploads/manager_consultations/" . $manager_id . "/" . $file_name; $file_path_to_store = $gcs_uri ? $gcs_uri : $web_path; try { $pdo = db_connect(); if (!$pdo) { throw new Exception('데이터베이스 연결 실패'); } $expiry_date = date('Y-m-d H:i:s', strtotime('+30 days')); // 30일 보관 // manager_consultations 테이블에 저장 (테이블이 없으면 생성 필요) $sql = "INSERT INTO manager_consultations (manager_id, step_id, audio_file_path, transcript_text, file_expiry_date, created_at) VALUES (?, ?, ?, ?, ?, NOW())"; $stmt = $pdo->prepare($sql); if (!$stmt) { throw new Exception('SQL 준비 실패'); } $executeResult = $stmt->execute([ $manager_id, $step_id, $file_path_to_store, $transcript, $expiry_date ]); if (!$executeResult) { throw new Exception('SQL 실행 실패'); } $insertId = $pdo->lastInsertId(); echo json_encode([ 'success' => true, 'message' => '녹음 파일이 저장되었습니다.', 'id' => $insertId, 'file_path' => $file_path_to_store, 'gcs_uri' => $gcs_uri ]); } catch (Exception $e) { error_log('DB 저장 오류: ' . $e->getMessage()); echo json_encode([ 'success' => false, 'message' => '데이터베이스 저장 실패: ' . $e->getMessage() ]); } ?>