참고: AWS S3 이미지 업로드 - S3 버킷 만들기
S3 버킷을 만들어서 이미지를 업로드해서 연결을 확인했고,
이번엔 서버를 열어서 해당 서버로 이미지를 업로드할 것이다.
(Postman 으로 해당 API 로 요청을 보내면, 미리 생성해둔 버킷에 이미지가 업로드된 것을 확인 할 수 있음)
// 액세스 키, 비밀 액세스 키
cloud.aws.credentials.accessKey=AWS 액세스 키
cloud.aws.credentials.secretKey=AWS 비밀 액세스 키
// 버킷 이름
cloud.aws.s3.bucket=버킷 이름
// 버킷 생성시 선택한 AWS 리전
cloud.aws.region.static=ap-northeast-2
// 설정한 CloudFormation 이 없으면 프로젝트 시작이 안되니, 해당 내용을 사용하지 않도록 false 를 등록
cloud.aws.stack.auto=false
// 파일 업로드 크기 설정
spring.servlet.multipart.max-file-size=20MB // 파일 하나당 크기
spring.servlet.multipart.max-request-size=20MB // 전송하려는 총 파일들의 크기
// MySQL 설정
spring.datasource.url=jdbc:mysql://엔드포인트:3306/초기 데이터베이스 이름
spring.datasource.username=마스터 사용자 이름
spring.datasource.password=마스터 암호
환경변수를 설정해준다.
application.properties 외에 application-aws.properties 파일을 만들어, 깃에 올라가지 않도록 했다.
참고: 깃허브에 개인정보가 올라간 경우 (+ 캐시 삭제)
액세스 키, 비밀 액세스 키 생성 방법
참고: AWS 에서 액세스 키, 비밀 액세스 키 만들기
// aws
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.3.1'
// 이걸로 해도 무방
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
Spring-Cloud-AWS 의존성을 추가해준다.
@Configuration
public class AwsConfig {
// IAM 계정의 액세스 키
@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
// IAM 계정의 비밀 키
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
// AWS 리전 이름
@Value("${cloud.aws.region.static}")
private String region;
// AmazonS3Client 빈을 생성
@Bean
public AmazonS3Client amazonS3Client() {
// BasicAWSCredentials : accessKey 와 secretKey 를 기반으로 인증 정보를 생성
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard() // AmazonS3ClientBuilder.standard() : S3 클라이언트를 구성하기 위한 빌더 객체를 생성
.withRegion(region) // 클라이언트가 작업할 AWS 지역을 설정
.withCredentials(new AWSStaticCredentialsProvider(awsCreds)) // withCredentials() : 액세스 키 및 비밀 키를 제공하는 AWSStaticCredentialsProvider 설정
.build(); // 이 메서드를 호출하여 최종적으로 AmazonS3Client 인스턴스를 생성하고 반환
}
}
AWS 액세스 키, 비밀 키, 버킷, 지역(region)에 대한 설정을 담은 클래스다.
@Value 를 통해, application-aws.properties 파일에서 설정 값을 주입받는다
// 게시글 작성
@PostMapping
public ResponseEntity<PostResponseDto> createPost(@RequestPart(value = "image", required = false) MultipartFile multipartFile, @RequestPart PostRequestDto requestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(postService.createPost(multipartFile, requestDto, userDetails.getUser()));
}
게시글 작성에 이미지를 포함시킬 수 있다.
@RequestPart 를 통해, MultipartFile 과 json 타입의 데이터(PostRequestDto) 를 하나의 API 로 함께 보낼 수 있다.
required = false
아래 코드에서 @ModelAttribute 사용 이유?
// 게시글 작성
@PostMapping
public ResponseEntity<PostResponseDto> createPost(@RequestPart(value = "image", required = false) MultipartFile multipartFile, @ModelAttribute PostRequestDto requestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(postService.createPost(multipartFile, requestDto, userDetails.getUser()));
}
@Service
@RequiredArgsConstructor
public class PostService {
private final UserService userService;
private final PostRepository postRepository;
private final PostLikeRepository postLikeRepository;
private final AmazonS3 amazonS3;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
// 게시글 작성
public PostResponseDto createPost(MultipartFile multipartFile, PostRequestDto requestDto, User user) {
// 이미지 s3 업로드 후에 image url 반환
String image = null;
if (multipartFile != null) {
image = uploadImage(multipartFile);
}
Post post = new Post(requestDto, image, user);
postRepository.save(post);
return new PostResponseDto(post);
}
...
// 게시글 삭제
public MsgResponseDto deletePost(Long post_id, User user) {
// 게시글이 있는지 & 사용자의 권한 확인
Post post = userService.findByPostIdAndUser(post_id, user);
// 이미지 s3 삭제
if (post.getImage() != null) {
amazonS3.deleteObject(bucket, post.getImage().substring(58));
}
postRepository.delete(post);
return new MsgResponseDto("게시글을 삭제했습니다.", HttpStatus.OK.value());
}
...
}
deleteObject(버킷 이름, 객체 키)
...
// 이미지 업로드
public String uploadImage(MultipartFile multipartFile) {
String fileName = createFileName(multipartFile.getOriginalFilename());
ObjectMetadata objectMetadata = new ObjectMetadata(); // ObjectMetadata 를 통해 파일에 대한 정보를 추가
objectMetadata.setContentLength(multipartFile.getSize()); // multipartFil 의 크기 설정 (byte)
objectMetadata.setContentType(multipartFile.getContentType()); // multipartFil 의 컨텐츠 유형 설정
try(InputStream inputStream = multipartFile.getInputStream()) {
amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) // 객체를 S3에 업로드
.withCannedAcl(CannedAccessControlList.PublicRead)); // 업로드된 객체에 대한 공개 읽기 권한을 설정
} catch(IOException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "이미지 업로드에 실패했습니다.");
}
return amazonS3.getUrl(bucket, fileName).toString(); // 업로드된 객체(사진)의 URL을 반환
}
// 파일 이름 생성
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 e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "잘못된 형식의 파일(" + fileName + ") 입니다.");
}
}
}
이미지 업로드
getOriginalFilename()
업로드된 이미지는 공개 읽기 권한이 설정된 상태로 저장
업로드된 이미지의 URL을 반환하여, 사용자가 이미지에 접근할 수 있다.
InputStream
파일 이름 생성
주어진 파일 이름을 기반으로 고유한 파일 이름을 생성
UUID.randomUUID().toString()
문자열1.concat(문자열2)
getFileExtension(파일 이름)
파일 확장자
(요약하자면) 다음 과정을 거쳐서 배포를 진행해준다.
RDS 구매 > RDS 포트 열기 > EC2 구매 및 접속 > build > Filezilla 로 업로드 > SpringBoot 작동시키기
생성해둔 버킷으로 들어가기 > 속성 > 정적 웹 사이트 호스팅 > 편집
정적 웹 사이트 호스팅
: 활성화 로 체크
인덱스 문서
: 프로젝트 > resources > templates > index.html 을 생성해주고, index.html 을 입력해서 이를 기본 페이지로 설정해준다
생성해둔 버킷으로 들어가기 > 속성 > 정적 웹 사이트 호스팅 > 버킷 웹 사이트 엔드포인트 로 들어가면
배포해둔 사이트로 연결이 된다.
게시글 작성 요청을 보내면
버킷에 사진이 업로드되어 있다.
단, form-data 형식으로 json 데이터를 전송하기 위해서는 json 타입인 것을 명시해줘야 한다.(application/json)
참고: Springboot로 S3 파일 업로드하기
참고: S3로 정적서버 배포
참고: 📁 AWS 정적 웹페이지 배포하기 - S3, CloudFront
참고: [Spring] Json with MultipartFile
참고: [Spring] Spring Boot AWS S3 사진 업로드 하는 법
참고: [SpringBoot] SpringBoot를 이용한 AWS S3에 여러 파일 업로드 및 삭제 구현하기