Amazon S3 (Simple Storage Service)
어디서나 원하는 양의 데이터를 검색할 수 있도록 구축된 key 기반의 객체 스토리지이며 데이터를 저장 및 검색하는데 사용할 수 있는 고유한 객체 키를 할당한다.
파일 서버의 역할을 하는 서비스입니다. 트래픽이 증가하면 장비를 증설해야 하지만 S3이 이를 대신해 주기 때문에 트래픽에 따른 시스템적인 문제는 걱정 할 필요가 없다. 파일에 접근 권한도 지정할 수 있다!
모든 종류의 데이터를 원하는 형식으로 저장할 수 있다.
Amazon S3 기능
https://aws.amazon.com/ko/s3/features/
Amazon S3의 용어
- 객체 : 저장되는 데이터(파일)
- 버킷 : 객체를 그룹핑한 최상위 디렉토리. 버킷 단위로 region을 지정하고 인증과 접속 제한을 걸 수 있다.
- 버전관리: 객체들의 변화를 저장. 삭제하거나 변경해도 변화를 모두 저장합니다.
- RSS : Reduced Redundancy Stroage. 데이터가 손실될 확률이 높은 형태의 저장 방식이지만 가격이 저렴하여 복원이 가능한 데이터를 저장하는 것이 적합합니다.
- Glacier - 매우 저렴한 가격으로 데이터를 저장할 수 있다.
AWS S3 버킷 생성
아래 글을 참고하여 버킷을 생성합니다.
https://dev.classmethod.jp/articles/for-beginner-s3-explanation/
환경 셋팅
build.gradle
- dependency에 spring-cloud-aws를 추가합니다.
AWS 설정
application.yml
cloud:
aws:
credentials:
accessKey: IAM 계정 access key
secretKey: IAM 계정 secret access key
s3:
bucket: 버킷명
region:
static: 버킷의 resion // 서울은 ap-northeast-2
stack:
auto: false
- accessKey, secretKey : AWS 계정에 부여된 key값 (절대 노출되면 안됩니다. 꼭 .gitignore 설정 해주기)
- bucket : S3 서비스에 생성한 버킷 이름
- region.static : S3를 서비스 할 regioin (서울은 ap-northeast-2)
- stack.auto : CloudFormation(서버 구성 자동화) 자동 실행 여부
https://docs.aws.amazon.com/ko_kr/general/latest/gr/rande.html
구현
controller
@Slf4j
@Controller
@RequestMapping("/profile")
@RequiredArgsConstructor
public class ProfileController {
private ProfileService profileService;
@PostMapping("/{memberId}/edit")
public String wrtieProfile(@PathVariable Long memberId, Authentication authentication, MultipartFile multipartFile) {
String userName = authentication.getName();
profileService.update(memberId, userName, multipartFile);
return "redirect:/profile/detail/{memberId}";
}
- MultipartFile : 파일 객체를 받기 위해서 사용합니다.
- IOException : S3에 파일을 업로드 할 때 IOEXception 예외를 던져줍니다.
service
@Service
@RequiredArgsConstructor
@Slf4j
public class ProfileService {
private final AmazonS3Client amazonS3Client;
private final MemberRepository memberRepository;
private final MemberReviewRepository memberReviewRepository;
private final ProfileRepository profileRepository;
// ========== 유효성 검사 ==========
public Member checkMemberId(Long targetMemberId) {
Member member = memberRepository.findById(targetMemberId)
.orElseThrow(() -> new ApplicationException(ErrorCode.USERNAME_NOT_FOUNDED));
return member;
}
public Member checkMemberName(String userName) {
Member member = memberRepository.findByUserName(userName)
.orElseThrow(() -> new ApplicationException(ErrorCode.USERNAME_NOT_FOUNDED));
return member;
}
// ========== 프로필 사진 업로드 ==========
@Value("${cloud.aws.s3.bucket}")
private String uploadFolder;
@Transactional
public ProfileResponse update(Long memberId, String userName, MultipartFile multipartFile) throws IOException {
// 업로드 된 file의 존재 유무 확인
if (multipartFile.isEmpty()) {
throw new ApplicationException(ErrorCode.FILE_NOT_EXISTS);
}
// 존재하는 member인지 확인
Member member = checkMemberId(memberId);
// AmazonS3 라이브러리 ObjectMetadata 활용
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(multipartFile.getContentType());
objectMetadata.setContentLength(multipartFile.getSize());
// 사용자가 올린 파일 이름
String uploadFileName = multipartFile.getOriginalFilename();
int index;
try {
index = uploadFileName.lastIndexOf(".");
} catch (StringIndexOutOfBoundsException e) {
throw new ApplicationException(ErrorCode.WRONG_FILE_FORMAT);
}
if (!member.getUserName().equals(userName)) {
new ApplicationException(ErrorCode.INVALID_PERMISSION);
}
String ext = uploadFileName.substring(index + 1);
// UUID로 랜덤한 id와 파일 이름을 합쳐서 awsS3FileName 변수 생성 (중복 방지)
String awsS3FileName = UUID.randomUUID() + "." + ext;
String key = "profileImage/" + awsS3FileName;
try (InputStream inputStream = multipartFile.getInputStream()) {
amazonS3Client.putObject(new PutObjectRequest(uploadFolder, key, inputStream, objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
} catch (IOException e) {
throw new ApplicationException(ErrorCode.FILE_UPLOAD_ERROR);
}
// db에 저장하기
String fileUrl = amazonS3Client.getUrl(uploadFolder, key).toString();
log.info("🔵 amazonS3Client.getUrl = {} ", fileUrl );
// PostFile postFile = PostFile.save(uploadFileName, fileUrl, post);
Profile profile = Profile.save(uploadFileName, key, member);
profileRepository.save(profile);
log.info("🔵 파일 등록 완료 ");
return ProfileResponse.updateProfileImage(uploadFileName, awsS3FileName);
}
참고
https://victorydntmd.tistory.com/334
https://victorydntmd.tistory.com/323
https://jane514.tistory.com/10
'Project > Team Project' 카테고리의 다른 글
[Git] 팀프로젝트 깃 충돌 해결하기 (0) | 2023.02.14 |
---|---|
[Git] 팀프로젝트 GitLab 협업하기(local, remote main, branch) (0) | 2023.02.14 |
[Gitlab] Git 사용 방법 정리 (commit, push, branch, pull) (0) | 2023.01.19 |