개발 환경
- Build : Gradle
- SpringBoot : 2.7.5
- Java : 11
- OS : Mac
요구사항
client로부터 form-data의 이미지 파일을 요청받으면 해당 이미지를 S3 버킷에 업로드한 뒤 DB에 버킷의 경로를 저장하려고 함.
이미지는 1개의 이미지를 요청 및 업로드함.
구현 - AWS 관련(버킷 생성 등) 설정은 프로젝트가 끝난 뒤 정리할 예정
의존성 추가하기
https://github.com/awspring/spring-cloud-aws
// AWS S3 Upload
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.2'
S3 관련 환경변수 설정하기(yml)
cloud:
aws:
credentials:
accessKey: # Access Key
secretKey: # Secret Key
s3:
bucket: # 버킷 이름
region:
static: s3 버킷의 Region
stack:
auto: false
spring:
servlet:
multipart:
max-file-size: # 파일 크기 ex) 20MB
AWS - EC2에서 Spring Cloud 프로젝트를 실행시키면 기본적으로 CloudFormation 구성을 시작한다.
설정한 CloudFormation이 없을 경우 프로젝트 실행이 안되니 사용하지 않도록 false로 설정한다.
cloud 관련 설정은 절대 공개가 되면 안되기 때문에 시스템 환경 변수 처리 또는 gitignore 처리를 하도록 한다!
AWS S3 Config
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3 amazonS3Client() {
AWSCredentials credentials =
new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}
}
AWS S3 Service
@RequiredArgsConstructor
@Service
public class AwsS3Service {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
public String uploadFile(MultipartFile multipartFile) {
String fileName = createFileName(multipartFile.getOriginalFilename());
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(multipartFile.getSize());
metadata.setContentType(multipartFile.getContentType());
try (InputStream inputStream = multipartFile.getInputStream()) {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, inputStream, metadata));
} catch (IOException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "이미지 업로드에 실패했습니다.");
}
return amazonS3Client.getUrl(bucket, fileName).toString();
}
// 이미지 수정으로 인해 기존 이미지 삭제 메소드
public void deleteImage(String fileUrl) {
String splitStr = ".com/";
String fileName = fileUrl.substring(fileUrl.lastIndexOf(splitStr) + splitStr.length());
amazonS3Client.deleteObject(new DeleteObjectRequest(bucket, fileName));
}
private String createFileName(String fileName) {
return UUID.randomUUID().toString().concat(getFileExtension(fileName));
}
private String getFileExtension(String fileName) {
try {
return fileName.substring(fileName.lastIndexOf("."));
} catch (StringIndexOutOfBoundsException se) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "잘못된 형식의 파일(" + fileName + ") 입니다.");
}
}
}
uploadFile(MultipartFile multipartFile)
DB에 저장될 형식에 따라 리턴 값을 변경하면 되는데, 내 기준으로는 DB에 이미지 전체 URL을 저장하기로 해서 일단 위와 같이 코드를 작성했다. 위와 같이 저장할 경우 리턴 값은 https://"bucket-name"."region".amazonaws.com/"파일 이름.확장자" 형식으로 저장됨.
"파일 이름.확장자"만 필요할 경우 return fileName; 으로 수정하면 된다.
deleteImage(String fileUrl)
위에서도 말했듯이 DB에 저장되는 이미지 경로가 URL( https://"bucket-name"."region".amazonaws.com/"파일 이름.확장자")이기 때문에 ".com/"기준으로 자르는 과정을 따로 넣었다.
S3 파일을 삭제할 때 넘기는 값은 S3에 저장되어 있는 파일명.확장자 로 전달을 하면 된다.
createFileName(String fileName)
저장될 이름이 중복되지 않도록 UUID 클래스를 이용하여 파일명 앞 부분을 고유 값으로 넣어준다.
ArtworkService
S3에 업로드가 필요한 Service 단에서 위에서 작성한 S3Service를 DI 받은 후 각 로직에 적용시킨다.
@Service
@RequiredArgsConstructor
@Transactional
public class ArtworkService {
...
private final AwsS3Service awsS3Service;
// S3에 이미지 등록(저장)
public void createArtwork(long memberId, long galleryId, ArtworkRequestDto requestDto) {
...
// 요청받은 multipartFile(이미지)를 DI받은 awsS3Service - uploadFile()에 넘긴다.
String imageRoot = awsS3Service.uploadFile("MultipartFile");
...
// 저장된 경로를 DB에 저장하는 것이므로 무시해도 됩니다.
artwork.setImagePath(imageRoot);
artworkRepository.save(artwork);
}
// S3에 이미지 저장 및 기존 이미지 삭제
public ArtworkResponseDto updateArtwork(long memberId, long galleryId, long artworkId, ArtworkRequestDto request) {
...
// 받은 요청에 image가 존재할 경우
if (image.isPresent()) {
// 기존 DB에 저장되어 있는 url을 넘겨 S3에서 삭제함.
awsS3Service.deleteImage(findArtwork.getImagePath());
// 신규 이미지를 S3에 저장함.
String s3Path = awsS3Service.uploadFile(image.get());
artwork.setImagePath(s3Path);
}
...
}
}
테스트
팀원분께 부탁해서 기존 이미지를 삭제하였을 때 정상적으로 삭제하는 것을 확인할 수 있었다.
프로젝트가 끝나면 내 aws 계정으로 테스트하며 파일 추가 및 삭제되는 것을 추가적으로 작성할 예정...
Reference
https://jane514.tistory.com/10
'SpringBoot' 카테고리의 다른 글
[Trouble Shooting] Web server failed to start. Port 8080 was already in use (0) | 2022.12.02 |
---|---|
[SpringBoot] MockMvc - multipart() POST외 다른 HTTPMethod 사용하기 (0) | 2022.11.26 |
[TroubleShooting] SpringBoot Controller Test - MockMvc 302 Found, 403 Forbidden (0) | 2022.11.24 |
[SpringDataJPA] 쿼리메서드 참조 객체의 필드 사용 (0) | 2022.11.17 |
[Trouble Shooting] MaxUploadSizeExceededException (0) | 2022.11.03 |