전체적인 흐름
- S3 티어 선택 후 S3 버킷 생성
- 액세스 권한을 얻기 위한 IAM 생성
- 스프링 빈에 AWS S3 등록
- Image API 구현 및 테스트
S3 티어 선택 및 S3 버킷 생성
S3 티어(스토리지 클래스)
어떤 종류의 데이터를 관리하는지, 얼마나 자주 그 데이터에 접근해야하는지에 따라 분류, 사용자는 그에 맞는 티어를 선택
- Standard
- 가장 보편적으로 사용되는 스토리지 타입
- IA
- 자주 접근되지는 않으나 접근시 빠른 접근, standard보다 비용은 저렴하나 데이터를 불러올때마다 추가 비용
- One Zone IA
- IA와 같지만 하나의 AZ에만 데이터 저장 → 가용영역 문제 생길경우 데이터가 날라갈수도 있음
- Intelligent Tiering
- 머신러능을 통한 자동으로 파일의 티어를 변경하는 서비스 ex. 접근 많으면 스탠다드, 빈도 낮으면 IA
- Glacier
- 거의 접근하지 않을 데이터, 데이터 접근시 분~시간 소요 ex. 쓸모 없는 법적 데이터
- Glacier Instant Retrieval
- 밀리초 단위의 검색시간, 분기당 한번 액세스에 적합 한마디로 빠르게 데이터를 가져와야하는데 자주 액세스하지않은 데이터에 적합
- Glacier Flexible Retrieval, Glacier Deep Archive
- 더 깊어지는 빙하(glacier)검색 시간 상당히 소요
클래스 간 전환
쉽게 말하면 standard → standard-IA는 가능하지만 반대로 standard-IA → standard는 불가능하다
S3 수명주기
- 의의
- 무한으로 증가되는 S3 버킷 용량을 주기적으로 정리하는데 목적, 각 버킷별로 수명주기 생성 가능
- 사용하지 않을 예정
- 문제 자체가 수명이 있는것이 아니고 쓰이지 않는 문제(2016년 이전 기출문제 등)가 존재하면 직접 지우는게 맞다고 판단
S3 버킷 생성 및 권한 설정
- 버킷 생성 및 정책 변환
- 버킷 생성
- 버킷 정책 변경을 위해서 퍼블릭 액세스 비활성화
- 버킷 정책 추가
- 버킷 정책 편집 - 정책 생성기를 통해 아래 3가지 체크 및 principal: *(전체 적용) 입력
- GetObject
- PutObject
- DeleteObject
- 정책 생성기를 통해 생성된 코드를 아래 버킷 정책 페이지로 돌아와서 붙여준다
- 버킷 정책 편집 - 정책 생성기를 통해 아래 3가지 체크 및 principal: *(전체 적용) 입력
{
"Version": "2012-10-17",
"Id": "",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
],
"Resource": ""
}
]
}
IAM 사용자 추가
- IAM 사용자 생성 클릭 및 이름 설정
- 권한 설정에서 AmazonS3FullAccess 추가
액세스 생성 및 키, 비번 별도로 저장
코드
S3 관련 Gradle 추가
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
S3 Config
- 인터페이스인 AmazonsS3 빈 등록
@Configuration
public class S3Config {
@Value("${aws.s3.region}")
private String region;
@Value("${aws.s3.accessKeyId}")
private String accessKeyId;
@Value("${aws.s3.secretKey}")
private String secretKey;
@Bean
public AmazonS3 s3Client() {
AWSCredentials credentials = new BasicAWSCredentials(accessKeyId, secretKey);
return AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region).build();
}
}
QuestionsController
- 이미지 추가, 삭제 API구현
//이미지 관련 API
@PostMapping("image")
public List<String> saveImages(@ModelAttribute ImageSaveDto imageSaveDto) {
return questionsService.saveImages(imageSaveDto);
}
@DeleteMapping("image/delete")
public ResponseEntity<String> deleteImages(){
questionsService.deleteImages();
return ResponseEntity.ok("DeleteAll");
}
QuestionsServiceImpl
- ImageService로 책임 전가
//Image관련 Service로직은 ImageService에 위임 - 추후 문제 데이터 추가시에 내용도 추가됨에 따라 QuestionsService 로직이 담당하는 부분이 많을 것으로 예상 -> 분산필요
@Override
public List<String> saveImages(ImageSaveDto imageSaveDto){
return imageService.saveImagesInS3(imageSaveDto);
}
@Override
public void deleteImages(){
imageService.deleteImagesInFolder();
}
ImageServiceImpl
@Service
@RequiredArgsConstructor
public class ImageServiceImpl implements ImageService{
private static String bucketName = "examlab-image";
private static String folderName = "test_images/"; //저장 폴더명 임시 부여
private final AmazonS3 amazonS3;
//이미지 저장 로직
@Transactional
public List<String> saveImagesInS3(ImageSaveDto saveDto) {
List<String> resultList = new ArrayList<>();
for (int i = 0; i < saveDto.getImages().size(); i++) {
String value = saveImage(saveDto.getImages().get(i));
resultList.add(value);
}
return resultList;
}
@Transactional
public String saveImage(MultipartFile multipartFile) {
String originalName = multipartFile.getOriginalFilename();
String accessUrl = ""; //반환 URL저장
String filename = folderName+originalName;
try {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(multipartFile.getContentType());
objectMetadata.setContentLength(multipartFile.getInputStream().available());
amazonS3.putObject(bucketName, filename, multipartFile.getInputStream(), objectMetadata);
accessUrl = amazonS3.getUrl(bucketName, filename).toString();
} catch(IOException e) {
}
return accessUrl;
}
//이미지 삭제 로직
public void deleteImagesInFolder() {
// 폴더 내 객체 리스트를 가져옴
List<S3ObjectSummary> objectSummaries = amazonS3.listObjects(bucketName, folderName).getObjectSummaries();
// 각 객체를 삭제
for (S3ObjectSummary objectSummary : objectSummaries) {
amazonS3.deleteObject(new DeleteObjectRequest(bucketName, objectSummary.getKey()));
}
}
테스트
테스트를 위해서 스프링 실행 후 Postman에 이미지 4개 첨부 및 실행을 해본다. 그러면 다음과 같이 저장된 이미지의 url과 함께
해당 S3버킷 폴더에 이미지가 저장된것을 확인 할 수 있다
출처
'스프링' 카테고리의 다른 글
[Spring] Validation 종류 및 적용 (0) | 2024.03.15 |
---|---|
[스프링] 스프링 3.1이후에 디버그 로그가 안뜰경우, 로그레벨 조정 방법 (0) | 2024.01.29 |
[스프링] 혼자 구현하는 웹 서비스1: 프로젝트 세팅 (0) | 2024.01.08 |
Google Drive API를 활용한 이미지 업로드 및 Url반환(스프링 서비스단계 구현) (0) | 2023.08.26 |