Skip to content

Refactor/#126 파일 저장로직 리팩토링#129

Open
sjmoon00 wants to merge 16 commits intodevelopfrom
Refactor/#126-파일_저장로직_리팩토링

Hidden character warning

The head ref may contain hidden characters: "Refactor/#126-\ud30c\uc77c_\uc800\uc7a5\ub85c\uc9c1_\ub9ac\ud329\ud1a0\ub9c1"
Open

Refactor/#126 파일 저장로직 리팩토링#129
sjmoon00 wants to merge 16 commits intodevelopfrom
Refactor/#126-파일_저장로직_리팩토링

Conversation

@sjmoon00
Copy link
Copy Markdown
Contributor

@sjmoon00 sjmoon00 commented Apr 10, 2026

🔥 연관된 이슈

close: #126

📜 작업 내용

파일 모듈 구조 리팩토링

  • global/util/FileStorageUtil, FileEncodingUtil 및 전역 파일 예외 3개 삭제
  • modules/file 하위에 FileCommandService, FileQueryService, AsyncImageProcessingService 로 책임 분리
  • FileStorage 인터페이스 + LocalFileStorage 구현체 도입 — 스토리지 교체 시 구현체만 변경하면 됨 + 테스트에 모킹도 가능한데 이건 적용하지 않음(적용할까요?)
  • ImageProcessor 인터페이스 + WebpImageProcessor 구현체 도입

비동기 실패 대응

  • AsyncImageProcessingService에 실패 시 cleanupFailedFile() 추가 — WebP 변환/저장 실패 시 물리 파일과 DB 레코드를 즉시 정리하여 좀비 레코드 방지

보안 및 안정성 개선

  • Path Traversal 방어: LocalFileStorage에 resolveSafely() 추가 — normalize 후 basePath 범위 이탈 검증
  • ThreadPool Rejection 대응: CallerRunsPolicy 설정 — 큐 포화 시 500 에러 대신 호출자 스레드에서 동기 실행
  • Fail-fast 초기화: 스토리지 디렉토리 생성 실패 시 예외 발생해서 어플리케이션 실행 실패처리

💬 리뷰 요구사항

  • 비동기 트랜잭션 설계 (AsyncImageProcessingService.java) — flush() 타이밍과 catch 내부 commit 보장 로직이 의도대로 동작하는지 확인 부탁드립니다

sjmoon00 added 10 commits April 8, 2026 18:33
- 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으로 통합)
@sjmoon00 sjmoon00 requested review from JJimini, myeowon and pykido April 10, 2026 13:48
@sjmoon00 sjmoon00 self-assigned this Apr 10, 2026
Copilot AI review requested due to automatic review settings April 10, 2026 13:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 FileStorageUtilFileQueryService로 테스트 더블 교체 및 반환 타입 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

@sjmoon00 sjmoon00 added the 🛠️ 리팩토링 기능 개선 및 최적화 작업(기능 변경 X) label Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🛠️ 리팩토링 기능 개선 및 최적화 작업(기능 변경 X)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 파일 저장로직 리팩토링

2 participants