From fec2a4455c3a3565045f487dc3ad06b48175364c Mon Sep 17 00:00:00 2001 From: caadiq Date: Wed, 21 Jan 2026 15:59:14 +0900 Subject: [PATCH] =?UTF-8?q?refactor(backend):=2018=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B2=98=EB=A6=AC=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94=20-=20=EB=A9=94=ED=83=80=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=A4=91=EB=B3=B5=20=EC=A1=B0=ED=9A=8C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - processImage: includeMetadata 옵션 추가 - processImage: sharp 인스턴스 재사용 (clone() 사용) - uploadAlbumPhoto: 중복 sharp(originalBuffer).metadata() 제거 Co-Authored-By: Claude Opus 4.5 --- backend/src/services/image.js | 25 ++++++++++++++++++------- docs/refactoring.md | 13 +++++++------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/backend/src/services/image.js b/backend/src/services/image.js index b012548..897557c 100644 --- a/backend/src/services/image.js +++ b/backend/src/services/image.js @@ -24,21 +24,33 @@ const { medium, thumb } = config.image; /** * 이미지를 3가지 해상도로 변환 + * @param {Buffer} buffer - 원본 이미지 버퍼 + * @param {boolean} includeMetadata - 메타데이터 포함 여부 + * @returns {Promise<{originalBuffer, mediumBuffer, thumbBuffer, metadata?}>} */ -async function processImage(buffer) { +async function processImage(buffer, includeMetadata = false) { + const image = sharp(buffer); + + // 메타데이터가 필요한 경우 먼저 조회 (중복 sharp 인스턴스 생성 방지) + const metadata = includeMetadata ? await image.metadata() : null; + const [originalBuffer, mediumBuffer, thumbBuffer] = await Promise.all([ - sharp(buffer).webp({ lossless: true }).toBuffer(), - sharp(buffer) + image.clone().webp({ lossless: true }).toBuffer(), + image.clone() .resize(medium.width, null, { withoutEnlargement: true }) .webp({ quality: medium.quality }) .toBuffer(), - sharp(buffer) + image.clone() .resize(thumb.width, null, { withoutEnlargement: true }) .webp({ quality: thumb.quality }) .toBuffer(), ]); - return { originalBuffer, mediumBuffer, thumbBuffer }; + const result = { originalBuffer, mediumBuffer, thumbBuffer }; + if (metadata) { + result.metadata = metadata; + } + return result; } /** @@ -146,8 +158,7 @@ export async function deleteAlbumCover(folderName) { * @returns {Promise<{originalUrl: string, mediumUrl: string, thumbUrl: string, metadata: object}>} */ export async function uploadAlbumPhoto(folderName, subFolder, filename, buffer) { - const { originalBuffer, mediumBuffer, thumbBuffer } = await processImage(buffer); - const metadata = await sharp(originalBuffer).metadata(); + const { originalBuffer, mediumBuffer, thumbBuffer, metadata } = await processImage(buffer, true); const basePath = `album/${folderName}/${subFolder}`; diff --git a/docs/refactoring.md b/docs/refactoring.md index e29d4da..c35855f 100644 --- a/docs/refactoring.md +++ b/docs/refactoring.md @@ -186,12 +186,13 @@ --- -### 18단계: 이미지 처리 최적화 🔄 진행 예정 -- [ ] 이미지 메타데이터 중복 처리 제거 -- [ ] processImage에서 메타데이터 함께 반환 +### 18단계: 이미지 처리 최적화 ✅ 완료 +- [x] 이미지 메타데이터 중복 처리 제거 +- [x] processImage에서 메타데이터 함께 반환 +- [x] sharp 인스턴스 재사용 (clone() 사용) -**대상 파일:** -- `src/services/image.js` - processImage 함수 개선 +**수정된 파일:** +- `src/services/image.js` - processImage 함수 개선, uploadAlbumPhoto 중복 조회 제거 --- @@ -245,7 +246,7 @@ | 15단계 | 스키마 파일 분리 | ✅ 완료 | | 16단계 | 에러 처리 일관성 | ✅ 완료 | | 17단계 | 중복 코드 제거 (멤버 조회) | ✅ 완료 | -| 18단계 | 이미지 처리 최적화 | 🔄 진행 예정 | +| 18단계 | 이미지 처리 최적화 | ✅ 완료 | | 19단계 | Redis 캐시 확대 | 🔄 진행 예정 | | 20단계 | 서비스 레이어 확대 | 🔄 진행 예정 | | 21단계 | 검색 페이징 최적화 | 🔄 진행 예정 |