-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(S3PreSignedUrl): S3 파일 업로드 방식을 PreSigned URL로 구현 #159
base: develop
Are you sure you want to change the base?
Conversation
S3 직접 업로드 방식을 PreSigned URL 방식으로 변경하여 서버 부하 감소 - PreSigned URL 생성 기능 구현 - 폴더 경로별 URL 생성 기능 구현 - 파일 업로드 완료 확인 프로세스 추가 변경사항: - S3PreSignedUrlUtils 유틸리티 클래스 생성 - PreSigned URL 생성 로직 구현 - PostFileService 로직 변경 - 파일 업로드 프로세스 2단계로 분리 - Request/Response DTO 추가 관련 컴포넌트: - S3PreSignedUrlUtils - PostFileService - FileUploadRequest - FileRequest - PreSignedUrlResponse - FileUploadConfirmRequest BREAKING CHANGES: - 파일 업로드 API 인터페이스 변경 - S3 직접 업로드 로직 제거 - 클라이언트 측 파일 업로드 로직 수정 필요
List<Map<String, String>> imageUrls = s3PreSignedUrlUtils.generatePreSignedUrlWithPath( | ||
userId, | ||
boardCode, | ||
images.stream().map(FileRequest::fileName).collect(Collectors.toList()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기 어차피 우리 자바17이어서 바로 toList() 어때여
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 stream.toList()로 하면 수정 불가능한 리스트를 반환하고
Collectors.toList()로 하면 수정 가능한 리스트를 반환한다고 하네요!
수정이 필요 없다면 toList()를 사용하는게 좋을 것 같습니다!
private String bucket; | ||
|
||
public Map<String, String> generatePreSignedUrl(String fileName, String contentType) { | ||
// URL 만료 시간 설정 (예: 5분) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기보면 최대 7일로 두긴 하는데 5분은 일단 정해둔건가욥???
.map(PostFile::getUrl) | ||
.orElse(null); | ||
|
||
AtomicInteger index = new AtomicInteger(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thread safe!!👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AtomicInteger는 멀티스레드 환경을 위한 것인데, 여기서는 불필요한 오버헤드일 수 있다는 생각이 들어서,
IntStream으로 처리했습니다.
"0부터 리스트 크기만큼 순회하면서 매핑한다"는 의도로 작성했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AtomicInteger가 멀티스레드 환경에서 유용하다는 점에는 동의합니다. 하지만, 현재 코드에서는 단일 스레드 환경에서만 동작하므로 오버헤드가 발생할 수 있는 점을 고려해, 말씀하신 IntStream 방식으로 처리하는 것이 저도 더 적합하다고 생각이 드네요..
// URL 만료 시간 설정 (예: 5분) | ||
Date expiration = new Date(); | ||
long expTimeMillis = expiration.getTime(); | ||
expTimeMillis += 1000 * 60 * 5; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기 만료시간을 상수나 application.yml 로 관리하는건 어떻게 생각해?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만료시간 5분은 딱 적당하다고 생각!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yml로 관리하는게 좋아보이네요!
@Value("${cloud.aws.s3.bucket}") | ||
private String bucket; | ||
|
||
public Map<String, String> generatePreSignedUrl(String fileName, String contentType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금 클라이언트로 부터 content-type을 받는데, 이걸
// Content-Type 검증
List<String> allowedContentTypes = List.of("image/png", "image/jpeg", "application/pdf");
if (!allowedContentTypes.contains(contentType)) {
throw new IllegalArgumentException("Unsupported Content-Type");
}
}
뭐 대충 이런식으로 contentType을 조금 지정하는건 어떻게 생각해, 뭐 저것도 상수로 빼거나 해서
의도치 않은 Content-Type이 전달될 가능성을 방지하기 위해서??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contentType를 지정하는 방법 차용해서 enum과 Validator를 조합하는 방식으로 수정했습니다!
아래와 같은 장점이 있을 것 같아요.
- 타입 안전성(enum)과 유연한 검증 로직(Validator) 모두 확보
- 커스텀 예외를 통한 더 명확한 에러 처리
- 검증 로직 재사용 가능
- 새로운 타입 추가가 용이하면서도 안전
- 테스트 작성이 쉬움
import java.util.Map; | ||
|
||
public record PreSignedUrlResponse( | ||
List<Map<String, String>> preSignedUrls, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 Map<String,String> 대신 명확한 의미를 가진 record 사용하면 타입 안정성과 가독성을 향상시킬 수 있을 것 같은데 어떻게 생각해???
public record PreSignedUrlDetail(String preSignedUrl, String fileUrl) {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
명시적 도메인 객체 생성해서 관리하도록 수정하는게 좋을 것 같네요!!
|
||
@Transactional | ||
public PostFileListResponse confirmUpload(Long userId, List<FileUploadConfirmRequest> confirmRequests) { | ||
List<PostFile> postFiles = confirmRequests.stream() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
S3PreSignedUrlUtils.java 클래스에
// S3 객체 존재 여부 확인 메서드
public boolean doesObjectExist(String fileUrl) {
String fileKey = extractKeyFromUrl(fileUrl);
return amazonS3.doesObjectExist(bucket, fileKey);
}
// S3 URL에서 객체 키를 추출하는 유틸리티 메서드
private String extractKeyFromUrl(String fileUrl) {
// fileUrl에서 S3 객체 키 추출 (버킷 이름 이후의 경로)
return fileUrl.substring(fileUrl.indexOf(bucket) + bucket.length() + 1);
}
이 메소드를 추가해서 ImageService의 confirmUpload 메소드에서 builder 들어가기전에, 한번 S3 객체 존재 여부를 확인하는건 어떻게 생각하시나여?
-
S3 객체 존재 여부 확인 메서드 추가
- doesObjectExist 메서드를 통해 S3 객체가 실제로 존재하는지 확인하고
- fileUrl에서 S3 키를 추출하는 extractKeyFromUrl 메서드로 URL 기반 키 처리를 안정적으로 수행하는 거!
-
ImageService 클래스에서 검증 적용
- confirmUpload 메서드에서 S3 객체가 존재하지 않을 경우 예외 발생
- 클라이언트가 잘못된 URL을 전달하거나 파일이 삭제된 경우를 방지
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public boolean doesObjectExist(String fileUrl) {
String fileKey = extractKeyFromUrl(fileUrl);
return amazonS3.doesObjectExist(bucket, fileKey);
}
이 방식에서 더 나아가서
존재 여부 확인 메서드 같은 경우에는 네크워크 요청이기에 다수의 파일일때 블로킹으로 인한 자원 낭비가 있기 때문에,
현재 시스템이 작더라도 확장성을 고려했을때 비동기처리로 구현해놓으면 훨씬 효율적이라고 생각합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그렇네요! doesObjectExist 메서드는 S3 파일 존재 여부를 확인하는 간단한 방식으로 구현되어 있지만, 코멘트 주신 대로 다수의 파일을 처리할 때는 네트워크 요청이 블로킹 방식으로 동작하여 비효율적일 수 있을 것 같네여!!;;
말씀하신 시스템이 작더라도 확장성과 효율성을 고려해 비동기 방식(CompletableFuture)으로 리팩토링하여 네트워크 요청의 병렬 처리를 지원하는 것은 좋은 방식인 것 같습니다!!
Java 17 스타일 및 함수형 프로그래밍 적용으로 코드 품질 향상 - Collectors.toList()를 Stream.toList()로 변경 - null & empty 체크를 CollectionUtils로 개선 - AtomicInteger를 IntStream으로 대체하여 함수형 스타일 적용 변경사항: - Stream 종단 연산자 toList() 적용 - Spring CollectionUtils 도입 - IntStream.range() 활용한 인덱스 처리 관련 컴포넌트: - ImageService - PostFileResponse - FileRequest BREAKING CHANGES: - 반환되는 List가 불변 객체로 변경됨
PreSigned URL 만료시간을 설정 파일로 분리하고 Content-Type 검증 로직 추가 - 만료 시간 설정 외부화로 환경별 유연한 설정 가능 - Content-Type enum 기반 검증으로 타입 안전성 확보 변경사항: - cloud.aws.s3.presigned-url.expiration 설정 추가 - AllowedContentType enum 추가 - ContentTypeValidator 컴포넌트 구현 - UnsupportedContentTypeException 추가 - S3PreSignedUrlUtils에 ContentTypeValidator 주입 관련 컴포넌트: - S3PreSignedUrlUtils - ContentTypeValidator - AllowedContentType - UnsupportedContentTypeException - application.yml BREAKING CHANGES: - application.yml에 presigned-url.expiration 설정 필요 - Content-Type이 enum에 정의된 타입과 일치하지 않을 경우 예외 발생
Map 대신 명시적 도메인 객체를 사용하여 타입 안전성과 가독성 향상 - PreSignedUrlInfo 도메인 객체 기반으로 로직 재구성 - URL 만료 시간 정보 추가 - 함수형 스타일 적용 변경사항: - Map<String, String> → PreSignedUrlInfo 변경 - for 루프를 IntStream으로 개선 - URL 만료 시간 관리 추가 - originalFileName 처리 로직 통합 관련 컴포넌트: - ImageService - S3PreSignedUrlUtils - PreSignedUrlResponse - PreSignedUrlInfo BREAKING CHANGES: - PreSignedUrlResponse 구조 변경 - S3PreSignedUrlUtils 반환 타입 변경
S3 파일 검증 성능 개선을 위한 비동기 처리 구현 - CompletableFuture를 활용한 비동기 파일 검증 - S3 관련 예외 처리 코드 추가 변경사항: - doesObjectExistAsync 메서드 추가 - S3 에러코드 추가 (S3_004, S3_005) - confirmUpload 메서드에 비동기 검증 로직 적용 관련 컴포넌트: - S3PreSignedUrlUtils - ImageService - ErrorCode (S3 관련 에러 코드 추가) 성능 개선: - 다수 파일 검증 시 병렬 처리로 성능 향상 - S3 API 호출 시 블로킹 최소화 BREAKING CHANGES: - 파일 존재하지 않을 경우 FILE_NOT_FOUND 예외 발생 - 파일 검증 실패 시 S3_VALIDATION_ERROR 예외 발생
✅ PR 유형
어떤 변경 사항이 있었나요?
📝 작업 내용
이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)
기존 프로세스:
새로운 PreSigned URL 프로세스:
✏️ 관련 이슈(선택 사항)
코드 리뷰 받고 싶은 부분
📝 작업 내용 (추가)
비동기 처리를 통한 파일 업로드 성능 개선
상세한 예외 처리 체계 구현
파일 컨텐츠 타입 검증 기능 추가
🔍 기술적 구현 상세
S3PreSignedUrlUtils 클래스 주요 기능:
파일 업로드 확인 프로세스 개선:
🎯 기대 효과
시스템 성능 개선:
시스템 안정성 강화:
💡 후속 계획
코드 리뷰 받고 싶은 부분 (추가)