Files
sam-kd/voice_ai_cnslt/GCP_storage_dev.md
hskwon aca1767eb9 초기 커밋: 5130 레거시 시스템
- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경
- DB 연결 하드코딩 → .env 기반으로 변경
- MySQL strict mode DATE 오류 수정
2025-12-10 20:14:31 +09:00

24 KiB

GCP Storage 파일 저장 로직 개발 문서

개요

이 문서는 voice_ai_cnslt 모듈에서 Google Cloud Storage (GCS)를 사용하여 오디오 파일을 저장하고 관리하는 로직에 대한 개발 가이드입니다.

목차

  1. 시스템 아키텍처
  2. 필수 설정
  3. GCS 업로드 로직
  4. GCS 삭제 로직
  5. 인증 메커니즘
  6. 사용 시나리오
  7. 에러 처리
  8. 코드 예제

시스템 아키텍처

파일 저장 흐름

[클라이언트] 
    ↓ (오디오 파일 업로드)
[process_consult.php]
    ↓ (로컬 서버에 임시 저장)
[/uploads/consults/{tenant_id}/]
    ↓ (Google STT API 요청 시)
[GCS 업로드 필요 여부 확인]
    ↓ (필요한 경우)
[uploadToGCS() 함수 호출]
    ↓
[Google Cloud Storage]
    ↓ (GCS URI 반환)
[Google Speech-to-Text API]

주요 파일 구조

  • voice_ai_cnslt/index.php: 프론트엔드 UI 및 녹음 기능
  • voice_ai_cnslt/process_consult.php: 오디오 처리 및 GCS 업로드 로직
  • voice_ai_cnslt/delete_consult.php: 파일 삭제 및 GCS 삭제 로직
  • apikey/gcs_config.txt: GCS 버킷 설정 파일
  • apikey/google_service_account.json: Google 서비스 계정 인증 정보

필수 설정

1. GCS 버킷 설정 파일

파일 경로: /apikey/gcs_config.txt

형식:

bucket_name=your-bucket-name

예시:

bucket_name=codebridge-speech-audio-files

2. Google 서비스 계정 JSON 파일

파일 경로: /apikey/google_service_account.json

필수 필드:

  • client_email: 서비스 계정 이메일
  • private_key: RSA 개인 키 (PEM 형식)
  • project_id: GCP 프로젝트 ID

예시 구조:

{
  "type": "service_account",
  "project_id": "your-project-id",
  "private_key_id": "...",
  "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
  "client_email": "your-service-account@your-project.iam.gserviceaccount.com",
  ...
}

3. 서비스 계정 권한

GCS 버킷에 대한 다음 권한이 필요합니다:

  • Storage Object Admin 또는
  • Storage Admin

GCS 업로드 로직

함수 시그니처

function uploadToGCS($file_path, $bucket_name, $object_name, $service_account_path)

매개변수

매개변수 타입 설명
$file_path string 업로드할 로컬 파일 경로
$bucket_name string GCS 버킷 이름
$object_name string GCS에 저장될 객체 이름 (경로 포함)
$service_account_path string 서비스 계정 JSON 파일 경로

반환값

  • 성공: gs://{bucket_name}/{object_name} 형식의 GCS URI 문자열
  • 실패: false

업로드 프로세스

  1. 서비스 계정 파일 검증

    if (!file_exists($service_account_path)) {
        error_log('GCS 업로드 실패: 서비스 계정 파일 없음');
        return false;
    }
    
  2. OAuth 2.0 토큰 생성

    • JWT (JSON Web Token) 생성
    • 서비스 계정 개인 키로 서명
    • Google OAuth 2.0 엔드포인트에 토큰 요청
  3. 파일 업로드

    • 파일 내용 읽기
    • MIME 타입 자동 감지
    • GCS REST API를 통한 업로드

사용 예시

// process_consult.php에서 사용
$gcs_object_name = 'consults/' . $tenant_id . '/' . basename($file_path);
$gcs_uri = uploadToGCS(
    $file_path, 
    $bucket_name, 
    $gcs_object_name, 
    $googleServiceAccountFile
);

if ($gcs_uri) {
    // GCS URI를 Google STT API에 전달
    $requestBody['audio'] = ['uri' => $gcs_uri];
} else {
    // 업로드 실패 처리
    echo json_encode(['ok' => false, 'error' => 'GCS 업로드 실패']);
}

업로드 트리거 조건

GCS 업로드는 다음 조건에서 자동으로 트리거됩니다:

  1. Google STT API 오류 발생 시

    • 오류 메시지에 "GCS URI" 또는 "duration limit" 키워드 포함
    • 오류 메시지에 "Inline audio exceeds" 포함
  2. 파일 크기 제한

    • 인라인 오디오 제한 (약 10MB) 초과 시

GCS 삭제 로직

함수 시그니처

function deleteFromGCS($bucket_name, $object_name, $service_account_path = null)

매개변수

매개변수 타입 설명
$bucket_name string GCS 버킷 이름
$object_name string 삭제할 객체 이름 (경로 포함)
$service_account_path string 서비스 계정 JSON 파일 경로 (기본값: /apikey/google_service_account.json)

반환값

  • 성공: true
  • 실패: false

삭제 프로세스

  1. GCS URI 파싱

    // DB에 저장된 경로가 gs:// 형식인지 확인
    if (strpos($consult['audio_file_path'], 'gs://') === 0) {
        if (preg_match('#gs://([^/]+)/(.+)#', $gcs_uri, $matches)) {
            $bucket_name = $matches[1];
            $object_name = $matches[2];
        }
    }
    
  2. OAuth 2.0 토큰 생성 (업로드와 동일)

  3. GCS REST API를 통한 삭제

    • DELETE 메서드 사용
    • HTTP 204 (No Content) 또는 404 (Not Found) 응답 시 성공으로 간주

사용 예시

// delete_consult.php에서 사용
if (strpos($consult['audio_file_path'], 'gs://') === 0) {
    $gcs_uri = $consult['audio_file_path'];
    if (preg_match('#gs://([^/]+)/(.+)#', $gcs_uri, $matches)) {
        $bucket_name = $matches[1];
        $object_name = $matches[2];
        
        $gcs_deleted = deleteFromGCS($bucket_name, $object_name);
        if (!$gcs_deleted) {
            error_log('GCS 파일 삭제 실패: ' . $gcs_uri);
        }
    }
}

인증 메커니즘

OAuth 2.0 JWT Bearer 토큰

GCS API 접근을 위해 OAuth 2.0 JWT Bearer 토큰 방식을 사용합니다.

JWT 생성 단계

  1. JWT 헤더 생성

    $jwtHeader = base64_encode(json_encode([
        'alg' => 'RS256',
        'typ' => 'JWT'
    ]));
    
  2. 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,  // 1시간 유효
        'iat' => $now
    ]));
    
  3. 서명 생성

    $privateKey = openssl_pkey_get_private($serviceAccount['private_key']);
    openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
    $jwt = $jwtHeader . '.' . $jwtClaim . '.' . base64_encode($signature);
    
  4. 액세스 토큰 요청

    curl_setopt($tokenCh, CURLOPT_POSTFIELDS, http_build_query([
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion' => $jwt
    ]));
    

필요한 스코프

  • 업로드/삭제: https://www.googleapis.com/auth/devstorage.full_control
  • 읽기 전용: https://www.googleapis.com/auth/devstorage.read_only

사용 시나리오

시나리오 1: 일반적인 오디오 업로드

  1. 사용자가 오디오 파일을 녹음
  2. process_consult.php로 파일 업로드
  3. 로컬 서버에 임시 저장
  4. Google STT API에 인라인 오디오로 요청
  5. 성공: 텍스트 변환 완료
  6. 실패 (GCS 필요): GCS에 업로드 후 재시도

시나리오 2: 대용량 오디오 파일

  1. 오디오 파일이 10MB 이상이거나 긴 녹음
  2. Google STT API가 "GCS URI required" 오류 반환
  3. uploadToGCS() 함수 자동 호출
  4. GCS에 업로드 후 URI 획득
  5. GCS URI로 STT API 재요청

시나리오 3: 파일 삭제

  1. 사용자가 업무협의록 삭제 요청
  2. delete_consult.php 실행
  3. DB에서 audio_file_path 조회
  4. 경로가 gs://로 시작하면 GCS 파일 삭제
  5. 로컬 서버 파일 삭제
  6. DB 레코드 삭제

에러 처리

일반적인 오류 및 해결 방법

1. 서비스 계정 파일 없음

오류: GCS 업로드 실패: 서비스 계정 파일 없음
해결: /apikey/google_service_account.json 파일이 존재하는지 확인

2. OAuth 토큰 요청 실패

오류: GCS 업로드 실패: OAuth 토큰 요청 실패 (HTTP 401)
해결: 
- 서비스 계정 JSON 파일 형식 확인
- 개인 키가 올바른지 확인
- 서비스 계정이 활성화되어 있는지 확인

3. 버킷 접근 권한 없음

오류: GCS 업로드 실패 (HTTP 403)
해결: 
- 서비스 계정에 Storage Object Admin 권한 부여
- 버킷 이름이 올바른지 확인

4. 버킷이 존재하지 않음

오류: GCS 업로드 실패 (HTTP 404)
해결: 
- GCS 버킷이 생성되어 있는지 확인
- 버킷 이름이 올바른지 확인

로깅

모든 GCS 관련 오류는 error_log() 함수를 통해 기록됩니다:

error_log('GCS 업로드 실패: 서비스 계정 파일 없음');
error_log('GCS 업로드 실패 (HTTP ' . $code . '): ' . $response);

코드 예제

전체 업로드 플로우 예제

<?php
// 1. 설정 파일 읽기
$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;
}

// 2. GCS 업로드 필요 여부 확인
if ($bucket_name && $needs_gcs) {
    // 3. GCS 객체 이름 생성
    $gcs_object_name = 'consults/' . $tenant_id . '/' . basename($file_path);
    
    // 4. GCS 업로드 실행
    $gcs_uri = uploadToGCS(
        $file_path, 
        $bucket_name, 
        $gcs_object_name, 
        $googleServiceAccountFile
    );
    
    // 5. 업로드 결과 확인
    if ($gcs_uri) {
        // 성공: GCS URI 사용
        $requestBody['audio'] = ['uri' => $gcs_uri];
    } else {
        // 실패: 오류 응답
        echo json_encode([
            'ok' => false, 
            'error' => 'GCS 업로드 실패',
            'details' => 'GCS 설정을 확인해주세요.'
        ]);
        exit;
    }
}
?>

전체 삭제 플로우 예제

<?php
// 1. DB에서 파일 경로 조회
$sql = "SELECT audio_file_path FROM consult_logs WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$consult_id]);
$consult = $stmt->fetch(PDO::FETCH_ASSOC);

// 2. 로컬 파일 삭제
if (!empty($consult['audio_file_path'])) {
    $file_path = $_SERVER['DOCUMENT_ROOT'] . $consult['audio_file_path'];
    if (file_exists($file_path)) {
        @unlink($file_path);
    }
    
    // 3. GCS 파일 삭제 (GCS URI인 경우)
    if (strpos($consult['audio_file_path'], 'gs://') === 0) {
        $gcs_uri = $consult['audio_file_path'];
        if (preg_match('#gs://([^/]+)/(.+)#', $gcs_uri, $matches)) {
            $bucket_name = $matches[1];
            $object_name = $matches[2];
            
            $gcs_deleted = deleteFromGCS($bucket_name, $object_name);
            if (!$gcs_deleted) {
                error_log('GCS 파일 삭제 실패: ' . $gcs_uri);
            }
        }
    }
}

// 4. DB 레코드 삭제
$delete_sql = "DELETE FROM consult_logs WHERE id = ?";
$delete_stmt = $pdo->prepare($delete_sql);
$delete_stmt->execute([$consult_id]);
?>

GCS 객체 이름 규칙

경로 구조

consults/{tenant_id}/{filename}

예시:

consults/user123/20241201_143022_abc123def456.webm

파일명 형식

$file_name = date('Ymd_His') . "_" . uniqid() . ".webm";
  • Ymd_His: 년월일_시분초 (예: 20241201_143022)
  • uniqid(): 고유 ID
  • 확장자: .webm (기본값)

실전 적용 예제: 매니저 상담 음성 저장 로직

이 섹션에서는 문서의 내용을 기반으로 매니저들의 상담 음성을 저장하는 로직을 개발하는 방법을 설명합니다.

개발 전략

기존 voice_ai_cnslt 모듈의 로직을 재사용하되, 다음 항목만 변경하면 됩니다:

  1. 업로드 디렉토리 경로
  2. GCS 객체 경로 구조
  3. DB 테이블명 및 필드
  4. 파일명 접두사 (선택사항)

1. 파일 구조 설계

업로드 디렉토리

// 기존: /uploads/consults/{tenant_id}/
// 매니저 상담: /uploads/manager_consultations/{manager_id}/
$upload_dir = $_SERVER['DOCUMENT_ROOT'] . "/uploads/manager_consultations/" . $manager_id . "/";

GCS 객체 경로

// 기존: consults/{tenant_id}/{filename}
// 매니저 상담: manager_consultations/{manager_id}/{filename}
$gcs_object_name = 'manager_consultations/' . $manager_id . '/' . basename($file_path);

2. 완전한 구현 예제

process_manager_consultation.php 파일

<?php
// 출력 버퍼링 시작 및 에러 리포팅 비활성화
error_reporting(0);
ob_start();

require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");

// ===== GCS 업로드 함수 (기존과 동일) =====
function uploadToGCS($file_path, $bucket_name, $object_name, $service_account_path) {
    // ... (기존 uploadToGCS 함수 코드 그대로 사용)
    // 문서의 "GCS 업로드 로직" 섹션 참고
}

// 출력 버퍼 비우기
ob_clean();
header('Content-Type: application/json; charset=utf-8');

// 1. 권한 및 세션 체크
if (!isset($user_id) || $level > 5) {
    echo json_encode(['ok' => false, 'error' => '접근 권한이 없습니다.']);
    exit;
}

$manager_id = $user_id; // 또는 별도의 매니저 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(['ok' => false, 'error' => '오디오 파일이 없습니다.']);
    exit;
}

// 파일 크기 확인
if ($_FILES['audio_file']['size'] == 0) {
    echo json_encode(['ok' => false, 'error' => '오디오 파일이 비어있습니다.']);
    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(['ok' => false, 'error' => '파일 저장 실패']);
    exit;
}

// 3. Google STT 변환 (기존 로직과 동일)
$googleServiceAccountFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/google_service_account.json';
$accessToken = null;

// ... (OAuth 토큰 생성 로직 - 기존과 동일)
// ... (STT API 호출 로직 - 기존과 동일)

// 4. GCS 업로드 (필요한 경우)
$file_size = filesize($file_path);
$max_inline_size = 10 * 1024 * 1024; // 10MB

// STT API 오류 발생 시 GCS 업로드
if ($needs_gcs && $operation_code !== 200) {
    $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) {
        // ⭐ 매니저 상담용 GCS 경로
        $gcs_object_name = 'manager_consultations/' . $manager_id . '/' . basename($file_path);
        $gcs_uri = uploadToGCS($file_path, $bucket_name, $gcs_object_name, $googleServiceAccountFile);
        
        if ($gcs_uri) {
            $requestBody['audio'] = ['uri' => $gcs_uri];
            // STT API 재시도...
        }
    }
}

// 5. STT 결과 처리 (기존과 동일)
// ... (폴링 로직, 텍스트 추출 등)

// 6. DB 저장 (매니저 상담 테이블)
try {
    $pdo = db_connect();
    if (!$pdo) {
        throw new Exception('데이터베이스 연결 실패');
    }
    
    $expiry_date = date('Y-m-d H:i:s', strtotime('+7 days'));
    
    // ⭐ 로컬 경로 또는 GCS URI 저장
    $web_path = "/uploads/manager_consultations/" . $manager_id . "/" . $file_name;
    // 또는 GCS URI가 있으면: $web_path = $gcs_uri;
    
    // ⭐ 매니저 상담 테이블에 저장
    $sql = "INSERT INTO manager_consultations 
            (manager_id, title, audio_file_path, transcript_text, summary_text, file_expiry_date) 
            VALUES (?, ?, ?, ?, ?, ?)";
    $stmt = $pdo->prepare($sql);
    
    $executeResult = $stmt->execute([
        $manager_id, 
        $title, 
        $web_path,  // 또는 $gcs_uri
        $transcript, 
        $summary, 
        $expiry_date
    ]);
    
    $insertId = $pdo->lastInsertId();
    
    echo json_encode([
        'ok' => true,
        'id' => $insertId,
        'title' => $title,
        'message' => '매니저 상담 음성이 성공적으로 저장되었습니다.'
    ]);
} catch (Exception $e) {
    error_log('DB 저장 오류: ' . $e->getMessage());
    echo json_encode(['ok' => false, 'error' => '데이터베이스 저장 실패']);
}
?>

delete_manager_consultation.php 파일

<?php
ob_start();
error_reporting(0);

require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");

// ===== GCS 삭제 함수 (기존과 동일) =====
function deleteFromGCS($bucket_name, $object_name, $service_account_path = null) {
    // ... (기존 deleteFromGCS 함수 코드 그대로 사용)
    // 문서의 "GCS 삭제 로직" 섹션 참고
}

ob_clean();
header('Content-Type: application/json; charset=utf-8');

// 권한 체크
if (!isset($user_id) || $level > 5) {
    echo json_encode(['ok' => false, 'error' => '접근 권한이 없습니다.']);
    exit;
}

$consultation_id = isset($_POST['id']) ? intval($_POST['id']) : 0;
$manager_id = $user_id;

if ($consultation_id <= 0) {
    echo json_encode(['ok' => false, 'error' => '잘못된 요청입니다.']);
    exit;
}

try {
    $pdo = db_connect();
    
    // ⭐ 매니저 상담 테이블에서 조회
    $sql = "SELECT audio_file_path FROM manager_consultations WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$consultation_id]);
    $consultation = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if (!$consultation) {
        echo json_encode(['ok' => false, 'error' => '상담 기록을 찾을 수 없습니다.']);
        exit;
    }
    
    // 1. 로컬 파일 삭제
    if (!empty($consultation['audio_file_path'])) {
        $file_path = $_SERVER['DOCUMENT_ROOT'] . $consultation['audio_file_path'];
        if (file_exists($file_path)) {
            @unlink($file_path);
        }
        
        // 2. GCS 파일 삭제 (GCS URI인 경우)
        if (strpos($consultation['audio_file_path'], 'gs://') === 0) {
            $gcs_uri = $consultation['audio_file_path'];
            if (preg_match('#gs://([^/]+)/(.+)#', $gcs_uri, $matches)) {
                $bucket_name = $matches[1];
                $object_name = $matches[2];
                
                $gcs_deleted = deleteFromGCS($bucket_name, $object_name);
                if (!$gcs_deleted) {
                    error_log('GCS 파일 삭제 실패: ' . $gcs_uri);
                }
            }
        }
    }
    
    // 3. DB에서 삭제
    $delete_sql = "DELETE FROM manager_consultations WHERE id = ?";
    $delete_stmt = $pdo->prepare($delete_sql);
    $delete_stmt->execute([$consultation_id]);
    
    echo json_encode(['ok' => true, 'message' => '상담 기록이 삭제되었습니다.']);
    
} catch (Exception $e) {
    error_log('삭제 오류: ' . $e->getMessage());
    echo json_encode(['ok' => false, 'error' => '삭제 중 오류가 발생했습니다.']);
}
?>

3. DB 스키마 예제

-- 매니저 상담 테이블 생성
CREATE TABLE IF NOT EXISTS manager_consultations (
    id INT AUTO_INCREMENT PRIMARY KEY,
    manager_id VARCHAR(50) NOT NULL,
    title VARCHAR(100) NOT NULL,
    audio_file_path TEXT,
    transcript_text TEXT,
    summary_text TEXT,
    file_expiry_date DATETIME,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_manager_id (manager_id),
    INDEX idx_created_at (created_at),
    INDEX idx_expiry_date (file_expiry_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

4. 주요 변경 사항 체크리스트

매니저 상담 음성 저장 로직을 개발할 때 다음 항목을 변경/확인하세요:

  • 업로드 디렉토리: /uploads/manager_consultations/{manager_id}/
  • GCS 객체 경로: manager_consultations/{manager_id}/{filename}
  • DB 테이블명: manager_consultations
  • DB 필드명: manager_id (기존 tenant_id 대신)
  • 파일명 접두사: 필요시 manager_ 접두사 추가
  • 권한 체크: 매니저 권한 레벨 확인
  • 만료 정책: 보관 기간 설정 (예: 7일, 30일 등)

5. 재사용 가능한 함수

다음 함수들은 그대로 재사용 가능합니다:

  1. uploadToGCS() - GCS 업로드 함수
  2. deleteFromGCS() - GCS 삭제 함수
  3. OAuth 2.0 토큰 생성 로직
  4. Google STT API 호출 로직
  5. 에러 처리 로직

변경이 필요한 부분만:

  • 디렉토리 경로
  • GCS 객체 경로
  • DB 테이블/필드명
  • 비즈니스 로직 (제목 생성, 요약 등)

6. 개발 순서

  1. DB 테이블 생성 (manager_consultations)
  2. 업로드 디렉토리 생성 (/uploads/manager_consultations/)
  3. uploadToGCS() 함수 복사 (기존 코드 그대로)
  4. deleteFromGCS() 함수 복사 (기존 코드 그대로)
  5. process_manager_consultation.php 작성 (경로만 변경)
  6. delete_manager_consultation.php 작성 (경로만 변경)
  7. 프론트엔드 연동 (기존 index.php 참고)

7. 테스트 체크리스트

  • 작은 파일 (< 10MB) 업로드 테스트
  • 큰 파일 (> 10MB) GCS 업로드 테스트
  • STT 변환 성공 확인
  • DB 저장 확인
  • GCS URI 저장 확인
  • 파일 삭제 (로컬 + GCS) 확인
  • 만료 파일 정리 확인

보안 고려사항

1. 서비스 계정 파일 보안

  • 서비스 계정 JSON 파일은 웹에서 직접 접근 불가능한 위치에 저장
  • 파일 권한 설정: chmod 600 google_service_account.json
  • .htaccess 또는 웹 서버 설정으로 /apikey/ 디렉토리 접근 차단

2. 버킷 접근 제어

  • 최소 권한 원칙 적용
  • 필요한 경우 버킷 레벨 IAM 정책 설정
  • 객체 레벨 ACL 설정 고려

3. 파일 경로 검증

  • 사용자 입력값 검증
  • 경로 조작 공격 방지
  • SQL 인젝션 방지 (PDO Prepared Statement 사용)

성능 최적화

1. 토큰 캐싱

현재 구현은 매번 새 토큰을 생성합니다. 성능 최적화를 위해 토큰 캐싱을 고려할 수 있습니다:

// 토큰 캐싱 예시 (구현 필요)
$token_cache_file = sys_get_temp_dir() . '/gcs_token_' . md5($service_account_path) . '.json';
if (file_exists($token_cache_file)) {
    $cached_token = json_decode(file_get_contents($token_cache_file), true);
    if ($cached_token['expires_at'] > time()) {
        $accessToken = $cached_token['access_token'];
    }
}

2. 비동기 업로드

대용량 파일의 경우 비동기 업로드를 고려할 수 있습니다.

3. 청크 업로드

매우 큰 파일의 경우 멀티파트 업로드를 사용할 수 있습니다.


테스트 방법

1. 로컬 테스트

// 테스트 스크립트
$test_file = '/path/to/test.webm';
$bucket_name = 'test-bucket';
$object_name = 'test/upload.webm';
$service_account = '/path/to/google_service_account.json';

$result = uploadToGCS($test_file, $bucket_name, $object_name, $service_account);
if ($result) {
    echo "업로드 성공: " . $result . "\n";
} else {
    echo "업로드 실패\n";
}

2. GCS 콘솔 확인

  • Google Cloud Console에서 업로드된 파일 확인
  • 객체 메타데이터 및 접근 권한 확인

참고 자료


변경 이력

날짜 버전 변경 내용 작성자
2024-12-01 1.0 초기 문서 작성 -

문의 및 지원

GCS 저장 로직 관련 문의사항이나 개선 제안은 개발팀에 문의해주세요.