user(); try { // 파일 정보 조회 (권한 체크 포함) $fileInfo = $this->attachmentService->getFileInfo($hash, $user); if (! $fileInfo) { $attachment = $this->attachmentService->findByHash($hash); if (! $attachment) { return $this->notFound('attachment.not_found'); } return $this->forbidden('attachment.access_denied'); } // 이미지 파일은 캐싱 헤더와 함께 응답 (환경설정 레이아웃 캐시 TTL 사용, 기본 24시간) // 행 disk 를 따르는 스토리지 스트림 — 로컬 경로 전제 fileResponse 는 S3 행에서 성립하지 않는다 (#99) if (str_starts_with($fileInfo['mime_type'], 'image/')) { return $this->streamedFileResponse( $fileInfo['response'], $fileInfo['etag_source'], (int) g7_core_settings('cache.layout_ttl', 86400) ); } // 이미지가 아닌 파일은 기존 다운로드 방식 유지 $response = $this->attachmentService->download($hash, $user); if (! $response) { return $this->forbidden('attachment.access_denied'); } return $response; } catch (AuthorizationException) { return $this->forbidden('attachment.access_denied'); } } }