s3를 적용하기 위해 글들을 찾아보던 와중 버킷명과 접근키 등의 정보를 yml에 담아야 한다고 해서 해당 파일을 찾아보았는데, 글들이 소개하는 위치에는 properties밖에 없어서 임의로 만들어서 정보를 담거나 하는 등 여러 방법을 사용해도 인식을 못 해 알아보니, 보통 properties나 yml 하나만 사용하고 같이 사용할 경우 오버라이딩 순서에 따라 결과가 달라질 수있어 properties에 정보를 담고 yml 파일은 삭제하기로 했다.
@Variable로 properties에 있는 변수들을 인식하지 못하는 일이 있었는데, lombok에 있는 에너테이션을 사용해서 생긴 문제였다. 아래를 import하니 해결됐다.
import org.springframework.beans.factory.annotation.Value;
이제 postcontroller를 만들어 제대로 AWS에 파일이 업로드 되는지 확인하고, get으로 가져와지는지 확인만 하면 된다.
이미지를 업로드하기 위해 만든 메서드인데 블로그에서 긁어 온 코드라 내용을 제대로 숙지하진 못했다. 시간이 날 때 제대로 읽어봐야겠다.
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;
@Slf4j
@RequiredArgsConstructor // final 멤버변수가 있으면 생성자 항목에 포함시킴
@Component
@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 {
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();
}
}
컨트롤러에서 dto와 함께 image를 받을 때 계속해서 생긴 오류가 몇 개 있는데,
1. java.lang.IllegalArgumentException: MultipartFile -> File 전환 실패
2.com.amazonaws.services.s3.model.AmazonS3Exception: The bucket does not allow ACLs (Service: Amazon S3; Status Code: 400; Error Code: AccessControlListNotSupported; Request ID: G2V1HKAPTZW5JRVP; S3 Extended Request ID: T9MANpF8l8yQ6Eso5Zmf1s9X+HYkgfclEQmLbc7U+izon5Ahg6k/N0YCLJSE0NXmlNiTgCzQk/EreAtYo3i4ZQ==; Proxy: null)
3. 파일 업로드 실패
1번은 어째서인지 png가 아닌 jpg형식의 이미지만 업로드가 됐다. 이거는 이미지 형식을 jpg만 받는 것으로 임시로 해결했으나 정확한 이유는 아직도 알지 못한다.
2번은 버킷 > 권한 > 객체 소유권 > ACL 활성화됨을 통해 해결했다.
3번은 dto는 body로, image는 param으로 받는 것이 안돼서 아래와 같이 수정하니 해결됐다.
editProfile(@AuthenticationPrincipal UserDetailsImpl userDetails, @RequestPart ProfileRequestDto requestDto, @RequestPart MultipartFile image)