개발일지/2023_한이음

[Spring boot] s3 이미지 업로드 구현(완성)

기억지기 개발자 2023. 8. 1. 15:58

https://grogrammer.tistory.com/66

 

Spring boot_s3 이미지 업로드 구현(초안)

🏕️기존 상황 이미 S3 버킷은 만들어져 있는 상황이었고 모든 권한을 가진 iam 유저도 1명 있는 상태였다. 그 상황에서 더 추가적으로 필요한 것들을 추가해서 개발하였다. 🔺S3연동을 위해 해

grogrammer.tistory.com

 

🏕️상황

  • 위에 있는 블로그에 쓰여있듯이 지난번에는 이미지가 S3 버킷에 잘 올라가는지 확인하는 용도였다.
    (아직 남은 개발 사항이 있는 상태)
  •  현재 우리 프로젝트에서 이미지를 다루는 부분은 게시판 부분인데,  게시판 쪽 코드와 합쳐서 게시글과 이미지가 하나가 되도록 만들어야 했다. 🔜 현재 완료
  • 이미지 1개만 업로드가 가능했는데 다중 이미지 업로드 기능을 구현해야 했다. 언젠가 이미지 1개만으로는 해결이 안 되는 날이 올 거 같아서 하는 김에 한 번에 다중 업로드까지 가능하도록 하였다. 🔜 현재 완료

 

🧡핵심 코드

public class imageService {
    private final AmazonS3Client amazonS3Client;
    private final imageRepository repository;

    @Value("${cloud.aws.s3.bucket}")
    private String bucket;

    // MultipartFile을 전달받아 File로 전환한 후 S3에 업로드
    public void upload(List<MultipartFile> multipartFileList, Long boardId) throws IOException {
        for(MultipartFile multipartFile : multipartFileList) {
            if(!multipartFile.isEmpty()){
                // 메타데이터 설정
                File uploadFile = convert(multipartFile)
                        .orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File 전환 실패"));
                log.info("변환된 이미지 파일 이름: " + uploadFile.getName());
                String url = upload(uploadFile, "static");
                Image img = new Image(boardId, url);
                repository.save(img);
            }
        }
    }

    private String upload(File uploadFile, String dirName) {
        String fileName = dirName + "/" + UUID.randomUUID() + "." + uploadFile.getName(); //이름 중복 방지를 위한 렌덤 코드를 추가
        String uploadImageUrl = putS3(uploadFile, fileName);
        removeNewFile(uploadFile);
        return uploadImageUrl;
    }

    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) {
        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 class BoardService {
    private final BoardRepository boardRepository;
    private final imageService imageService;

    // insert
    public ResponseDTO<?> create(final Board entity, final List<MultipartFile> imageList) {
        try {
            validate(entity);
            boardRepository.save(entity);
            if(imageList.size() != 0){
                imageService.upload(imageList, entity.getId());
            }
            return ResponseDTO.success(boardRepository.findById(entity.getId()));
        } catch (Exception e) {
            String error = e.getMessage();
            return ResponseDTO.fail("Error", error);
        }
    }

 

💘후기

  • 원래 board 쪽은 다른 팀원이 코드를 개발했다. 내가 맡은 코드를 개발하느라 board 쪽 코드를 들여다보지 못했는데 이번 기회를 통해 board 쪽 코드도 분석하고, 그 코드에 내가 새로 만든 Image 코드를 덧붙였다.
  • 그렇게 만들고 나니 뿌듯했다.  이렇게 성공했을 때의 순간을 보고 개발하는 게 아닌가~~ 싶다.
  • 이제 업로드 부분은 다 완성했으니 나머지 기능들도 멋지게 완성하고 싶다.
  • 코드라는 것도 확실히 보다 보니까 이해가 안 되고, 굉장히 낯선 코드들도 여러 번 반복해서 보니 확 친해진 느낌이 든다.