S3 이미지 업로드를 복습해보자.
일단 S3에서 버킷을 생성한다.
객체 소유권 > ACL 활성화됨 > 모든 퍼블릭 엑세스 차단 헤제
그리고 이 S3를 사용하기 위한 전용 IAM 사용자를 추가해야한다.
사용자 추가 > console 사용자 액세스 권한 제공 > IAM 사용자를 생성하고 싶음 > 나머지는 기본 설정 > 권한 옵션 > 직접 정책 연결 > AmazonS3FullAccess 선택 > 생성
IAM > 사용자 > 보안 자격 증명 > 액세스 키 만들기 > csv 파일 저장
# application.properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
cloud.aws.credentials.access-key=${AWS_S3_ACCESS_KEY}
cloud.aws.credentials.secret-key=${AWS_S3_SECRET_KEY}
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto=false
cloud.aws.s3.bucket= S3 버킷 이름
// build.gradle
// AWS
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
@Configuration
public class AwsS3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.build();
}
}
@Slf4j
@RequiredArgsConstructor // final 멤버변수가 있으면 생성자 항목에 포함시킴
@Service
public class S3Uploader {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
// MultipartFile을 전달받아 File로 전환한 후 S3에 업로드
public String upload(MultipartFile multipartFile, String dirName) throws IOException {
if (multipartFile.isEmpty()) {
return "https://dtogram.s3.ap-northeast-2.amazonaws.com/postFile/%EA%B8%B0%EB%B3%B8%20%EC%9D%B4%EB%AF%B8%EC%A7%80.png";
} else {
File uploadFile = convert(multipartFile)
.orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File 전환 실패"));
return upload(uploadFile, dirName);
}
}
private String upload(File uploadFile, String dirName) {
String fileName = dirName + "/" + uploadFile.getName();
String uploadImageUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile); // 로컬에 생성된 File 삭제 (MultipartFile -> File 전환 하며 로컬에 파일 생성됨)
return uploadImageUrl; // 업로드된 파일의 S3 URL 주소 반환
}
private String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(
new PutObjectRequest(bucket, fileName, uploadFile)
.withCannedAcl(CannedAccessControlList.PublicRead) // PublicRead 권한으로 업로드 됨
);
return amazonS3Client.getUrl(bucket, fileName).toString();
}
private void removeNewFile(File targetFile) {
if(targetFile.delete()) {
log.info("파일이 삭제되었습니다.");
}else {
log.info("파일이 삭제되지 못했습니다.");
}
}
private Optional<File> convert(MultipartFile file) throws IOException {
File convertFile = new File(file.getOriginalFilename());
if(convertFile.createNewFile()) {
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}
}
테스트를 위해 postman에서 지난 프로젝트와 동일한 방식으로 진행했으나 postman에서 다음과 같은 오류메시지가 출력됐다.
{
"type": "about:blank",
"title": "Unsupported Media Type",
"status": 415,
"detail": "Content-Type 'application/octet-stream' is not supported.",
"instance": "/api/blog"
}
구글링을 해보니
@RequestPart("requestDto") BlogRequestDto requestDto
이와 같이 request형식이 아니라
@RequestPart String title, @Request String contents
처럼 각 요소를 따로 분리하면 해결된다고 한다. 하지만 이렇게 하면 controller부터 service, serviceImpl, Blog까지 전부 수정사항이 생겨 다른 방법을 알아보려고 한다.
에러가 났던 이유는 postman에서 requestDto의 content type을 application/json로 지정하는 것을 깜빡해서 생긴 이슈였다.
하지만 이렇게 고친 뒤에도 어째선지 RuntimeError가 발생했다.
log를 추가하며 확인한 결과 디렉토리 문제임을 알게 됐다. S3 버킷을 새로 만들어서 해결했다.
참고한 블로그