이미지는 다양하게 처리할 수 있다.
1. 프론트는 리엑트를 사용하니까 JS에서 S3와 통신하는 방식
2. 백엔드는 스프링부트를 사용하니까 Java로 S3와 통신하는 방식
3. S3를 사용하기로 했으니 다른 방식은 아래 링크에서 !
웹에서 이미지 관리하는 방법 - 두의 개발
두 가지 방법의 특징을 비교하고 하나를 선택해서 개발해야겠다.
💡 서버와의 통신이 길어져 사용자의 불변함이 생길지 몰라도 보안이 우수한게 사용자와 서비스 사이의 신뢰를 보존하고 법적 문제도 일으키지 않을 수 있다 !
멀티파트(form-data)를 이용한 POST 요청
첫 번째
<input type="file" /> // img 파일을 input 태그를 통하여 데이터를 받는다.
두 번째
const formData = new FormData();
formData.append('file', imageFile);
세 번째
fetch('/api/upload', { // Content-Type 헤더를 'multipart/form-data'로
설정하여 멀티파트 데이터를 전송
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
// 프로필 이미지
@PostMapping("file/profile")
public String uploadProfileImage(@RequestParam("image") MultipartFile multipartFile) throws IOException {
return userService.uploadProfile(multipartFile);
}
컨트롤러에서 front에서 받은 이미지를 MultipartFile 객체로 받을 수 있다.
public String uploadProfile(MultipartFile multipartFile) throws IOException {
String url = s3Uploader.upload(multipartFile, "static/profile");
System.out.println(url);
return url;
}
@Component
@RequiredArgsConstructor
public class S3Uploader {
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
public String bucket;
//List 로 받을 경우
public List<String> upload(List<MultipartFile> multipartFiles, String dirName) throws IOException{
List<String> urlList = new ArrayList<>();
for(MultipartFile multipartFile : multipartFiles) {
File uploadFile = convert(multipartFile).orElseThrow(() -> new IllegalArgumentException("파일 전환 실패"));
urlList.add(upload(uploadFile, dirName));
}
return urlList;
}
public String upload(MultipartFile multipartFile, String dirName) throws IOException{
File uploadFile = convert(multipartFile).orElseThrow(() -> new IllegalArgumentException("파일 전환 실패"));
return upload(uploadFile, dirName);
}
// S3로 파일 업로드하기
private String upload(File uploadFile, String dirName) {
String fileName = dirName + "/" + UUID.randomUUID() + uploadFile.getName(); // S3에 저장된 파일 이름
String uploadImageUrl = putS3(uploadFile, fileName); // s3로 업로드
removeNewFile(uploadFile);
return uploadImageUrl;
}
// S3로 업로드
private String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}
// 로컬에 저장된 이미지 지우기
private void removeNewFile(File targetFile) {
if (targetFile.delete()) {
log.info("File delete success");
return;
}
log.info("File delete fail");
}
private Optional<File> convert(MultipartFile multipartFile) throws IOException{
File convertFile = new File(System.getProperty("user.dir") + "/" + multipartFile.getOriginalFilename());
// 바로 위에서 지정한 경로에 File이 생성됨 (경로가 잘못되었다면 생성 불가능)
if (convertFile.createNewFile()) {
try (FileOutputStream fos = new FileOutputStream(convertFile)) { // FileOutputStream 데이터를 파일에 바이트 스트림으로 저장하기 위함
fos.write(multipartFile.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}
}
@Value 어노테이션은 yaml이나 properties 파일에 중요한 정보를 저장했을 때 사용하는 방법이다. 필자는 버켓 주소를 yaml파일에 저장해서 사용하였다.
그리고 AmazonS3Client 클래스는 Config파일로써 S3와의 자격증명을 해주는 클래스이다.

postman으로 테스트를 진행해보면, s3에 저장된 url 주소가 반환된다 !!! 이로써 front에서 받은 이미지 파일 s3에 저장하기 성공하였다.
다음은 여러개의 파일을 받았을 때, 어떻게 업로드 하는지에 대해 포스팅 하도록 하겠습니다 !
주의사항)
1.s3에서는 파일 이름이 동일할 경우 파일이 올라가지 않기 때문에 uuid를 통해 고유 키를 나눠준다.
2.파일확장자가 jpeg,png,jpg가 아닐경우에도 파일이 올라가지기 때문에 if문을 걸어 예외처리해준다.
3.파일업로드가 완료되어 204를 받았을 경우 axios로 서버에 location 정보를 보내주어야한다. 언제 보내줄지 처리하는게 관건이고 성공적으로 유저 프로필을 변경하였을 경우 s3에 남아있는 기존 파일을 삭제시켜야한다. (이 과정이 생각보다 굉장히 복잡하다)
4.만약 취소를 누를경우 file.type 이 undefined 되기 때문에 if(file)로 핸들링해줘야함
5.파일사이즈가 1mb 이하일때만 서버에 보내야함
6.파일선택창에서 취소를 누를경우 file을 초기화해줘야함
7.images가 아닌 파일들을 선택하지 못하도록 accept="image/*"로 이미지만 선택
8. delete로 이전 이미지 삭제하기 위해선 버킷 cors정책에서 delete 추가해야합니다.
9. 로컬스토리지 업데이트할때 setitem으로 기존 걸 구조분해해서 업데이트해야합니다..
10. delete할때 파일이름만 적어야하는데 앞에 주소까지 같이 적혀서 split으로 잘라야 한다.
11. 처음 가입한 회원일 경우 /images/user.jpeg 라는 파일로 지정되어 있어서 s3에서 삭제하면 안된다. if로 예외처리 해줘야한다
출처 여기