219 lines
7.3 KiB
PHP
219 lines
7.3 KiB
PHP
|
|
<?php
|
||
|
|
// 출력 버퍼링 시작 및 에러 리포팅 비활성화
|
||
|
|
error_reporting(0);
|
||
|
|
ob_start();
|
||
|
|
|
||
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
||
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||
|
|
|
||
|
|
// GCS 업로드 함수 (GCP_storage_dev.md 참고)
|
||
|
|
function uploadToGCS($file_path, $bucket_name, $object_name, $service_account_path) {
|
||
|
|
if (!file_exists($service_account_path)) {
|
||
|
|
error_log('GCS 업로드 실패: 서비스 계정 파일 없음');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$serviceAccount = json_decode(file_get_contents($service_account_path), true);
|
||
|
|
if (!$serviceAccount) {
|
||
|
|
error_log('GCS 업로드 실패: 서비스 계정 JSON 파싱 오류');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// OAuth 2.0 토큰 생성
|
||
|
|
$now = time();
|
||
|
|
$jwtHeader = base64_encode(json_encode(['alg' => '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()
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
|