진행 중인 프로젝트에서 DALL-E API를 호출하여 이미지를 생성하는 기능이 있다. 이때, DALL-E API를 호출하여 이미지를 생성하고, 생성한 이미지를 서버 측에서 AWS S3에 업로드하는 기능이 필요하여 MultipartFile S3 업로드를 구현하게 되었다.
이 때, DALL-E API 호출시 Response의 Format을 ❗base64-encoded JSON❗ String으로 설정한다.
.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에 그림일기 이미지를 업로드하기 위해 다음과 같은 과정을 거쳤다:
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()
이용!
💡 MultipartFile로 변환하는 이유는, S3에 해당 파일을(DALL-E가 생성한 이미지를) 업로드하기 위해서이다! S3는 멀티파트 형태의 파일 업로드도 허용한다.💡
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 에러
S3 bucket에 이미지 저장용 폴더(디렉토리)를 따로 생성하여, 해당 폴더에 이미지가 업로드되게끔 구현하였다.
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();
}
}