[Springboot] DALL-E API로 생성한 이미지 MultipartFile로 AWS S3에 업로드: base64 json을 MultipartFile로 변환

비타민·2023년 11월 2일
0

📌 구현 배경

진행 중인 프로젝트에서 DALL-E API를 호출하여 이미지를 생성하는 기능이 있다. 이때, DALL-E API를 호출하여 이미지를 생성하고, 생성한 이미지를 서버 측에서 AWS S3에 업로드하는 기능이 필요하여 MultipartFile S3 업로드를 구현하게 되었다.

1️⃣ DALL-E API는 이미지를 생성한다.

이 때, DALL-E API 호출시 Response의 Format을 ❗base64-encoded JSON❗ String으로 설정한다.

AIService.java

.responseFormat("b64_json")

(DALL-E API 호출에 대한 자세한 내용은 이전 게시글을 참고해주세요! (ᴗ͈ˬᴗ͈)ꕤ.゚)

Q: 왜 URL 형태의 Response Format을 사용하지 않았나요?

A: 만약 DALL-E가 이미지를 생성하고 URL의 형식으로 응답을 보낸다면, 이미지 파일을 S3 버킷에 업로드 하기 위해 해당 URL로 접근하는 과정이 필요하다.(S3는 '파일'을 업로드하는 형태인데, DALL-E가 생성하는 이미지에 대한 URL 응답의 경우 해당 URL을 접속해야만 이미지 '파일'을 확인할 수 있다.) DALL-E를 호출하고, DALL-E가 생성하는 이미지에 대한 URL을 응답으로 받아 해당 URL에 접근하여 파일을 확인한 뒤, 이를 S3에 업로드하는 일련의 과정을 단 한 번의 API 호출로, 클라이언트의 처리 없이 구현하는 것은 구글링 결과 레퍼런스도 거의 없고 거의 불가능에 가깝다고 판단하였다.

➡️ 따라서, 💡 DALL-E API 호출에 대한 응답을 base64 Json String으로 받아 서버 측에서 바로 MultipartFile의 형태로 S3 버킷에 DALL-E가 생성한 이미지를 업로드하는 로직 💡 을 고안하게 되었다.


이후, S3에 그림일기 이미지를 업로드하기 위해 다음과 같은 과정을 거쳤다:

2️⃣ DALL-E API를 호출한 응답값인 base64 Json String을 byte [](byte array)로 변환한다.

ImageGeneratorController.java

    import org.apache.commons.codec.binary.Base64;

    public class ImageGeneratorController {
            // (...생략)
    String img = aiService.generatePicture(naverTransService.getTransSentence(shortKoJour)); // papago를 거쳐 DALL-E를 통해 반환된 b64_json String
    byte[] image = Base64.decodeBase64(img); //b64 string -> byte[]
            // (...생략)
    }

➡️ Base64.decodeBase64() 이용!

3️⃣ byte []로 변환된 이미지를 MultipartFile로 변환한다.

💡 MultipartFile로 변환하는 이유는, S3에 해당 파일을(DALL-E가 생성한 이미지를) 업로드하기 위해서이다! S3는 멀티파트 형태의 파일 업로드도 허용한다.💡

ImageGeneratorController.java

    import com.hack.hack_server.Global.S3.S3Uploader;        
    import org.springframework.web.multipart.MultipartFile;
    import org.springframework.mock.web.MockMultipartFile;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;

    public class ImageGeneratorController {
            private final S3Uploader s3Uploader;

            // (...생략)
            int totalCnt = 1024;
            try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(totalCnt)) {
                int offset = 0;
                while (offset < image.length) {
                    int chunkSize = Math.min(totalCnt, image.length - offset);

                    byte[] byteArray = new byte[chunkSize];
                    System.arraycopy(image, offset, byteArray, 0, chunkSize);

                    byteArrayOutputStream.write(byteArray);
                    byteArrayOutputStream.flush();

                    offset += chunkSize;
                }

                // ByteArrayOutputStream -> ByteArrayInputStream
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());

                // MultipartFile 객체 생성
                MultipartFile multipartFile = new MockMultipartFile("파일명", byteArrayInputStream.readAllBytes());

                s3Uploader.saveFile(multipartFile); //s3에 멀티파트 파일로 직접 업로드
                } catch (IOException e) {
            }
    // (...생략)
    }

➡️ 이때 호출된 S3Uploader.java 속 saveFile()는 MultipartFile을 S3 버킷에 업로드하는 기능을 하는 함수이다.

이때, MockMultipartFile을 사용하기 위해 build.gradle의 dependencies에 아래 코드를 ❗꼭 추가해줘야한다❗

    testImplementation 'org.springframework.boot:spring-boot-starter-test'

유의사항

: testImplementation ✅ implementation ❌
➡️ 추가되는 코드가 implementation이 아닌, testImplementation이라는 점을 강조하기 위해 위와 같이 표시하였을 뿐, 기존 dependencies에 implementation 'org.springframework.boot:spring-boot-starter-test' 코드가 있다면 이를 삭제할 필요는 전혀 없다!
포스팅 참고) mock.web.MockMultipartFile: import 에러

4️⃣ MultipartFile로 변환된 이미지를 S3 버킷에 업로드한다.

S3 bucket에 이미지 저장용 폴더(디렉토리)를 따로 생성하여, 해당 폴더에 이미지가 업로드되게끔 구현하였다.

S3Uploader.java

    import com.amazonaws.services.s3.model.ObjectMetadata;
    import org.springframework.web.multipart.MultipartFile;

    public class S3Uploader {
            public String saveFile(MultipartFile multipartFile) throws IOException {
                    String originalFilename = multipartFile.getName();

                    ObjectMetadata metadata = new ObjectMetadata();
                    metadata.setContentLength(multipartFile.getSize());
                    metadata.setContentType(multipartFile.getContentType());

                    amazonS3Client.putObject(bucket + "/폴더명", multipartFile.getName(), multipartFile.getInputStream(), metadata);
                    return amazonS3Client.getUrl(bucket + "/폴더명", originalFilename).toString();
            }
    }
profile
☁️ 백엔드 개발, 클라우드, AI 분야에 관심이 많아요 ☁️

0개의 댓글

관련 채용 정보