Refactor/#126 파일 저장로직 리팩토링#129
Open
Conversation
- FileStorage 인터페이스 및 LocalFileStorage 구현체 추가 - ImageProcessor 인터페이스 및 WebpImageProcessor 구현체 추가 - FilePathGenerator 추가 - application.yml에 파일 저장 경로 설정 외부화
- FileCommandService (이미지 저장/삭제 오케스트레이션) - FileQueryService (파일 조회/기본 썸네일) - AsyncImageProcessingService (비동기 이미지 변환) - FileExceptionType에 SAVE_FAILED, DELETE_FAILED, NOT_FOUND, EMPTY_FILE 추가
- 7개 서비스에서 FileStorageUtil 의존성을 FileCommandService/FileQueryService로 교체 - IntegrationTest 및 5개 테스트의 Mock 대상 변경 - FileStorageUtil, FileEncodingUtil 삭제 - global/error 파일 예외 클래스 3개 삭제 (FileExceptionType으로 통합)
Contributor
There was a problem hiding this comment.
Pull request overview
파일 저장/조회 로직을 global/util 중심 구조에서 modules/file 하위의 서비스/인프라 계층으로 재구성하여, 스토리지 교체 가능성과 비동기 이미지 처리 안정성을 강화하려는 PR입니다.
Changes:
FileCommandService/FileQueryService로 책임 분리 및 기존FileStorageUtil/FileEncodingUtil제거FileStorage(Local 구현) +ImageProcessor(WebP 구현) 도입, 저장 경로를 설정(file.storage.local.base-path)으로 분리- 비동기 이미지 처리(
AsyncImageProcessingService) 및 스레드풀 정책(CallerRunsPolicy) 적용, 기존 서비스/테스트 전반 리팩토링
Reviewed changes
Copilot reviewed 30 out of 30 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/java/com/opus/opus/team/application/TeamQueryServiceTest.java | FileStorageUtil → FileQueryService로 테스트 더블 교체 및 반환 타입 FileResource 적용 |
| src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java | 삭제/저장 검증 대상을 FileCommandService로 변경 (replaceImageFile 등) |
| src/test/java/com/opus/opus/modules/contest/application/ContestTrackCommandServiceTest.java | 트랙 썸네일 저장/삭제 검증을 FileCommandService 기반으로 변경 |
| src/test/java/com/opus/opus/member/application/MemberQueryServiceTest.java | 프로필 이미지 조회 테스트를 FileQueryService + FileResource로 변경 |
| src/test/java/com/opus/opus/member/application/MemberCommandServiceTest.java | 프로필 이미지 수정/삭제 검증을 FileCommandService로 변경 |
| src/test/java/com/opus/opus/helper/IntegrationTest.java | 통합 테스트 공통 목을 FileCommandService/FileQueryService로 분리 |
| src/main/resources/application.yml | 로컬 스토리지 base-path 설정 키 추가 |
| src/main/java/com/opus/opus/modules/team/application/TeamQueryService.java | 이미지 조회를 FileQueryService로 이동하고 FileResource 사용 |
| src/main/java/com/opus/opus/modules/team/application/TeamCommandService.java | 이미지 저장/삭제를 FileCommandService로 위임 (storeImageFile/replaceImageFile/deleteFile) |
| src/main/java/com/opus/opus/modules/member/application/MemberQueryService.java | 프로필 이미지 조회를 FileQueryService로 변경 |
| src/main/java/com/opus/opus/modules/member/application/MemberCommandService.java | 프로필 이미지 저장/삭제를 FileCommandService로 변경 |
| src/main/java/com/opus/opus/modules/file/infrastructure/storage/LocalFileStorage.java | 로컬 디스크 저장소 구현 및 경로 정규화 기반 path traversal 방어 추가 |
| src/main/java/com/opus/opus/modules/file/infrastructure/storage/FileStorage.java | 스토리지 교체를 위한 인터페이스 추가 |
| src/main/java/com/opus/opus/modules/file/infrastructure/processor/WebpImageProcessor.java | WebP 변환 구현체 추가 |
| src/main/java/com/opus/opus/modules/file/infrastructure/processor/ImageProcessor.java | 이미지 처리 추상화 인터페이스 추가 |
| src/main/java/com/opus/opus/modules/file/infrastructure/FilePathGenerator.java | 날짜/UUID 기반 상대 경로 생성기 추가 |
| src/main/java/com/opus/opus/modules/file/exception/FileExceptionType.java | 파일 저장/삭제/조회 관련 예외 타입 확장 |
| src/main/java/com/opus/opus/modules/file/application/FileQueryService.java | 파일 로드 + MIME 타입 반환 책임 분리 |
| src/main/java/com/opus/opus/modules/file/application/FileCommandService.java | 파일 메타 저장/삭제 및 비동기 처리 트리거 담당 |
| src/main/java/com/opus/opus/modules/file/application/dto/FileResource.java | Resource + mimeType 반환용 DTO(record) 추가 |
| src/main/java/com/opus/opus/modules/file/application/AsyncImageProcessingService.java | 비동기 변환/저장 및 실패 시 정리 로직 추가 |
| src/main/java/com/opus/opus/modules/contest/application/ContestTrackCommandService.java | 트랙 썸네일 저장/삭제를 FileCommandService로 변경 |
| src/main/java/com/opus/opus/modules/contest/application/ContestQueryService.java | 배너 조회를 FileQueryService로 변경 |
| src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java | 배너 저장/삭제를 FileCommandService로 변경 |
| src/main/java/com/opus/opus/global/util/FileStorageUtil.java | 기존 파일 오케스트레이션 유틸 제거 |
| src/main/java/com/opus/opus/global/util/FileEncodingUtil.java | 기존 WebP 비동기 변환 유틸 제거 |
| src/main/java/com/opus/opus/global/error/FileSaveFailedException.java | 전역 파일 예외 제거 (모듈 예외로 통합) |
| src/main/java/com/opus/opus/global/error/FileNotFoundException.java | 전역 파일 예외 제거 (모듈 예외로 통합) |
| src/main/java/com/opus/opus/global/error/FileDeleteFailedException.java | 전역 파일 예외 제거 (모듈 예외로 통합) |
| src/main/java/com/opus/opus/global/config/AsyncConfig.java | 이미지 비동기 executor에 CallerRunsPolicy 적용 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+34
to
+47
| try { | ||
| final String relativePath = filePathGenerator.generate(imageProcessor.getOutputExtension()); | ||
|
|
||
| final File savedFile = fileRepository.save(File.builder() | ||
| .name(multipartFile.getOriginalFilename()) | ||
| .filePath(relativePath) | ||
| .referenceId(referenceId) | ||
| .referenceType(referenceType) | ||
| .imageType(imageType) | ||
| .build()); | ||
| fileRepository.flush(); | ||
|
|
||
| asyncImageProcessingService.processAndStore(multipartFile.getBytes(), relativePath, savedFile.getId()); | ||
|
|
Comment on lines
+15
to
+23
| public byte[] process(final byte[] imageBytes) { | ||
| try { | ||
| final ImmutableImage image = ImmutableImage.loader().fromBytes(imageBytes); | ||
| final WebpWriter writer = WebpWriter.DEFAULT.withQ(WEBP_QUALITY); | ||
| return image.bytes(writer); | ||
| } catch (Exception e) { | ||
| log.error("WebP 이미지 변환 실패", e); | ||
| return imageBytes; | ||
| } |
Comment on lines
+35
to
+44
| private void cleanupFailedFile(final String relativePath, final Long fileId) { | ||
| try { | ||
| fileStorage.delete(relativePath); | ||
| } catch (Exception ignored) { | ||
| } | ||
| try { | ||
| fileRepository.deleteById(fileId); | ||
| } catch (Exception e) { | ||
| log.error("이미지 처리 실패 후 DB 레코드 정리 실패 [fileId={}]: {}", fileId, e.getMessage(), e); | ||
| } |
Comment on lines
+29
to
+50
| @Override | ||
| public void store(final byte[] content, final String relativePath) { | ||
| final Path fullPath = resolveSafely(relativePath); | ||
| try { | ||
| Files.createDirectories(fullPath.getParent()); | ||
| Files.write(fullPath, content); | ||
| } catch (IOException e) { | ||
| throw new FileException(FileExceptionType.SAVE_FAILED, "로컬 디스크에 파일을 저장하는 중 오류가 발생했습니다."); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public byte[] load(final String relativePath) { | ||
| final Path fullPath = resolveSafely(relativePath); | ||
| if (!Files.exists(fullPath)) { | ||
| throw new FileException(FileExceptionType.NOT_EXISTS_PHYSICAL_FILE); | ||
| } | ||
| try { | ||
| return Files.readAllBytes(fullPath); | ||
| } catch (IOException e) { | ||
| throw new FileException(FileExceptionType.NOT_EXISTS_PHYSICAL_FILE); | ||
| } |
Comment on lines
+23
to
+27
| file: | ||
| storage: | ||
| local: | ||
| base-path: src/main/resources/opus_files | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔥 연관된 이슈
close: #126
📜 작업 내용
파일 모듈 구조 리팩토링
비동기 실패 대응
보안 및 안정성 개선
💬 리뷰 요구사항