<< 구조 >>
- 게시글에 이미지를 올린다.
- 그럼 프런트 단에서 이벤트 감지 기능으로 api를 호출한다.
(유효성 + 사이즈 검사를 진행 후 S3 임시 경로에 이미지들을 업로드한다.) - 임시 경로가 포함된 해당 이미지의 URL을 반환한다.
- 사용자가 게시글까지 작성 후에 [등록하기]를 누르면 게시글은 게시글 DB에 저장, 이미지는 임시경로 🔜 정식경로로 이동시킨 후에 정식 경로로 변경된 url을 다시 반환받는다.
public class imageService {
@Autowired
private AmazonS3 amazonS3;
private final AmazonS3Client amazonS3Client;
private final imageRepository repository;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
// MultipartFile을 전달받아 File로 전환한 후 S3에 업로드
@Transactional
public List<String> upload(List<MultipartFile> multipartFileList) throws IOException {
List<String> uploadedFileNames = new ArrayList<>();
for (MultipartFile multipartFile : multipartFileList) {
if (!multipartFile.isEmpty()) {
if (!isValidImage(multipartFile.getInputStream())) { //유효한 이미지인지 검사
throw new IllegalArgumentException("Invalid image format");
}
long maxSizeInBytes = 5 * 1024 * 1024; // 10MB
if (!isImageSizeValid(multipartFile, maxSizeInBytes)) { //용량체크
throw new IllegalArgumentException("Image size exceeds the limit");
}
File uploadFile = convert(multipartFile)
.orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File 전환 실패"));
log.info("변환된 이미지 파일 이름: " + uploadFile.getName());
String url = upload(uploadFile, "temporary"); //dirName = 이미지가 저장될 s3 폴더명
uploadedFileNames.add(url);
}
}
return uploadedFileNames;
}
private String upload(File uploadFile, String dirName) {
String fileName = dirName + "/" + UUID.randomUUID() + "." + uploadFile.getName(); //이름 중복 방지를 위한 렌덤 코드를 추가(UUID.randomUUID())
String uploadImageUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile);
return uploadImageUrl; //이미지 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();
}
public boolean isValidImage(InputStream inputStream) {
try {
BufferedImage image = ImageIO.read(inputStream);
if (image == null) {
return false; // 이미지가 올바르지 않을 경우
}
return true;
} catch (IOException e) {
return false;
}
}
public boolean isImageSizeValid(MultipartFile imageFile, long maxSizeInBytes) {
return imageFile.getSize() <= maxSizeInBytes;
}
private void removeNewFile(File targetFile) {
if(targetFile.delete()) {
log.info("파일이 삭제되었습니다.");
}else {
log.info("파일이 삭제되지 못했습니다.");
}
}
private Optional<File> convert(MultipartFile file) {
File convertFile = new File(file.getOriginalFilename());
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
} catch (IOException e) {
// 변환에 실패하면 예외 처리
convertFile.delete();
// 예외 메시지와 스택 트레이스를 로그로 출력
log.error("MultipartFile 변환에 실패했습니다: {}", e.getMessage(), e);
return Optional.empty();
}
return Optional.of(convertFile);
}
public void deleteFile(String fileName) {
DeleteObjectRequest request = new DeleteObjectRequest(bucket, fileName);
amazonS3Client.deleteObject(request);
}
//-------------------------------------------------------------------------------------
public List<String> moveImagesToFinalLocation(List<String> imageUrls, Long boardId) { //정식 폴더로 옮기는 메인 메소드
List<String> finalImageUrls = new ArrayList<>();
for (String imageUrl : imageUrls) {
String imageName = getImageNameFromUrl(imageUrl);
// 임시 폴더에서 이미지를 가져와서 정식 폴더로 이동
finalImageUrls.add(moveImageToFinalFolder(imageName));
}
saveImagesToDatabase(finalImageUrls, boardId);
return finalImageUrls;
}
public void saveImagesToDatabase(List<String> finalImageUrls, Long boardId) { //정식 폴더로 이동할 이미지들은 db에 저장
for (String imageUrl : finalImageUrls) {
Image imageEntity = new Image();
imageEntity.setImageUrl(imageUrl);
imageEntity.setBoardId(boardId);
repository.save(imageEntity);
}
}
private String getImageNameFromUrl(String imageUrl) { // 이미지 URL에서 파일 이름 추출
// 이미지 URL에서 파일 이름 추출
return imageUrl.substring(imageUrl.lastIndexOf("/") + 1);
}
private String moveImageToFinalFolder(String imageName) { // 이미지를 임시 폴더에서 정식 폴더로 이동하는 로직 구현
String sourceKey = "temporary/" + imageName;
String destinationKey = "final/" + imageName;
// Amazon S3의 copyObject 메소드를 활용하여 이미지를 임시 폴더에서 정식 폴더로 복사
amazonS3.copyObject(bucket, sourceKey, bucket, destinationKey);
// 복사가 완료되면 임시 폴더의 이미지 삭제
amazonS3.deleteObject(bucket, sourceKey);
// 정식 폴더에 저장된 이미지의 URL 생성 및 반환
String finalImageUrl = amazonS3.getUrl(bucket, destinationKey).toString();
return finalImageUrl;
}
}
핵심이 되는 동작 코드들은 다 이 클래스에 있다.
이거 성공하고 감동의 눈물을 쪼끔 흘릴 뻔했다..ㅎㅎ 엄청난 기능은 아닐지 몰라도 난 며칠을 꼬박 걸려 만든 소중한 기능이다...
<< 성공한 상황들 >>
💚최대 용량을 3MB로 제한해두고 3MB가 넘는 이미지를 업로드 했을 때 성공적으로 나타나는 오류 메세지~~
💚임시경로에서 정식경로로 바뀐 URL

'개발일지 > 2023_한이음' 카테고리의 다른 글
[spring boot] S3 게시판(이미지 처리) delete 기능 완성 (0) | 2023.08.29 |
---|---|
[post men] 415 error - Content-Type (0) | 2023.08.27 |
🤦🏻♀️고민하기 - 게시판에서 이미지 api에 대한 처리 방식 (0) | 2023.08.26 |
[aws] 프로젝트 build와 jar 파일 실행의 차이 (0) | 2023.08.19 |
[aws] 첫걸음 때기의 기쁨 (서버 배포 성공) (0) | 2023.08.18 |