1. S3 버킷 생성하기
ACL 활성화, 모든 퍼블릭 액세스 차단 해제
2. 의존성 주입
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.741'
3. application 설정
aws:
access:
key: 액세스 키
secret:
key: 보안 액세스 키
s3:
bucket: S3 버킷명
4. config 설정
@Configuration
public class S3Config {
@Value("${aws.access.key}")
private String accessKey;
@Value("${aws.secret.key}")
private String secretKey;
@Bean
public AmazonS3 s3Client() {
BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
.withRegion(Regions.AP_NORTHEAST_2)
.build();
}
}
5. S3 Service 구현
@RequiredArgsConstructor
@Service
public class S3Service {
private final AmazonS3 s3Client;
@Value("${aws.s3.bucket}")
private String bucket;
public void uploadFile(String keyName, MultipartFile file) throws IOException {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(file.getSize());
PutObjectRequest request = new PutObjectRequest(bucket, keyName, file.getInputStream(), objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead);
s3Client.putObject(request);
}
public void deleteFile(String keyName) {
DeleteObjectRequest request = new DeleteObjectRequest(bucket, keyName);
s3Client.deleteObject(request);
}
}
6. 알아서 요리조리 재량껏 구현
@PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<CreatePostResponseDto> create(
@Valid @RequestPart CreatePostRequestDto requestDto,
@RequestPart(required = false) List<MultipartFile> multipartFiles,
Authentication authentication
) {
String email = ((UserDetailsImpl) authentication.getPrincipal()).getUsername();
CreatePostResponseDto responseDto = postService.create(email, requestDto, multipartFiles);
return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
}
@Transactional
public CreatePostResponseDto create(String email, CreatePostRequestDto requestDto, List<MultipartFile> multipartFiles) {
MemberEntity memberEntity = validateMemberEntity(email);
PostEntity postEntity = postRepository.save(requestDto.create(memberEntity));
if (multipartFiles != null && !multipartFiles.isEmpty()) {
handlePostImages(multipartFiles, postEntity);
}
updateThumbnail(postEntity);
return new CreatePostResponseDto(postEntity);
}
private void handlePostImages(List<MultipartFile> multipartFiles, PostEntity postEntity) {
if (multipartFiles.size() > MAX_UPLOAD_IMAGE_COUNT) {
throw new CustomException(ErrorCode.TOO_MANY_POST_IMAGE);
}
for (MultipartFile file : multipartFiles) {
if (file.getSize() > MAX_UPLOAD_IMAGE_SIZE) {
throw new CustomException(ErrorCode.TOO_LONG_POST_IMAGE_SIZE);
}
if (!UPLOAD_IMAGE_TYPE.contains(file.getContentType())) {
throw new CustomException(ErrorCode.INVALID_POST_IMAGE_TYPE);
}
try {
String originalFilename = file.getOriginalFilename();
String extension = Objects.requireNonNull(originalFilename).substring(originalFilename.lastIndexOf("."));
String randomImageUrl = UUID.randomUUID() + extension;
PostImageEntity postImageEntity = PostImageEntity.builder()
.imageUrl(randomImageUrl)
.postEntity(postEntity)
.build();
postImageRepository.save(postImageEntity);
s3Service.uploadFile(randomImageUrl, file);
} catch (IOException e) {
log.error(e.getMessage());
throw new CustomException(ErrorCode.UNKNOWN_POST_IMAGE_UPLOAD);
}
}
}
private void updateThumbnail(PostEntity postEntity) {
postImageRepository.findFirstByPostEntity(postEntity)
.ifPresent(imageEntity -> postEntity.updateThumbnail(imageEntity.getImageUrl()));
}
참고
https://medium.com/@mertcakmak2/object-storage-with-spring-boot-and-aws-s3-64448c91018f