From 861cff4481dfca7d45452bf3e1f65347518d76d9 Mon Sep 17 00:00:00 2001 From: c5ln Date: Thu, 28 Aug 2025 03:19:32 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=9A=94=EC=B2=AD=20timeout=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/avatar/image/service/ImageProcessingService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java b/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java index 1f5f8d0c..bc71c094 100644 --- a/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java +++ b/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java @@ -131,8 +131,8 @@ public String processImageWithAiWithSpecificContentType(MultipartFile imageFile) new CustomApiException(ErrorCode.AI_AVATAR_FAILED)); })) .bodyToMono(byte[].class) - .timeout(Duration.ofSeconds(30)) - .block(Duration.ofSeconds(30)); + .timeout(Duration.ofSeconds(300)) + .block(Duration.ofSeconds(300)); if (result == null || result.length == 0) { log.error("AI 서버에서 유효한 이미지 바이트를 받지 못했습니다 (null/empty)."); From 9cf9333c9fd86c09d0a577fe1ffb665360e903d0 Mon Sep 17 00:00:00 2001 From: c5ln Date: Thu, 28 Aug 2025 03:58:33 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=8F=BC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../image/service/ImageProcessingService.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java b/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java index bc71c094..5406a7d3 100644 --- a/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java +++ b/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java @@ -13,6 +13,8 @@ import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; @@ -52,16 +54,17 @@ public class ImageProcessingService { */ public String processImageWithAi(MultipartFile imageFile) { try { - // 1. MultipartFile을 byte 배열로 변환 - byte[] imageBytes = imageFile.getBytes(); + // 1. MultipartFile을 MultiValueMap으로 변환하여 multipart-form-data 생성 + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("image_file", imageFile.getResource()); // FastAPI의 인자명과 동일하게 설정 - // 2. WebClient를 사용하여 바이너리 데이터로 FastAPI 서버에 전송 + // 2. WebClient를 사용하여 multipart/form-data 형식으로 전송 byte[] result = webClient .post() .uri(fastapiServerUrl + "/process-image") - .contentType(MediaType.APPLICATION_OCTET_STREAM) // 바이너리 데이터로 설정 - .body(BodyInserters.fromValue(imageBytes)) // 바이트 배열 직접 전송 + .contentType(MediaType.MULTIPART_FORM_DATA) // multipart/form-data로 설정 + .body(BodyInserters.fromMultipartData(body)) // MultiValueMap 전송 .retrieve() .onStatus( HttpStatusCode::isError, From f547d3a37100a0f779183a425f525883238b37db Mon Sep 17 00:00:00 2001 From: c5ln Date: Thu, 28 Aug 2025 06:41:56 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix=20:=20url=20=EC=9D=91=EB=8B=B5=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/avatar/image/AiResponseDTO.java | 8 +++++ .../image/service/ImageProcessingService.java | 32 +++++++++++-------- 2 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/example/cp_main_be/domain/avatar/image/AiResponseDTO.java diff --git a/src/main/java/com/example/cp_main_be/domain/avatar/image/AiResponseDTO.java b/src/main/java/com/example/cp_main_be/domain/avatar/image/AiResponseDTO.java new file mode 100644 index 00000000..523606d5 --- /dev/null +++ b/src/main/java/com/example/cp_main_be/domain/avatar/image/AiResponseDTO.java @@ -0,0 +1,8 @@ +package com.example.cp_main_be.domain.avatar.image; + +import lombok.Getter; + +@Getter +public class AiResponseDTO { + String imageUrl; +} diff --git a/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java b/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java index 5406a7d3..da3dbccf 100644 --- a/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java +++ b/src/main/java/com/example/cp_main_be/domain/avatar/image/service/ImageProcessingService.java @@ -1,5 +1,6 @@ package com.example.cp_main_be.domain.avatar.image.service; +import com.example.cp_main_be.domain.avatar.image.AiResponseDTO; import com.example.cp_main_be.global.common.CustomApiException; import com.example.cp_main_be.global.common.ErrorCode; import com.example.cp_main_be.global.infra.StorageService; @@ -10,8 +11,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ByteArrayResource; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; +import org.springframework.http.client.MultipartBodyBuilder; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -54,17 +57,19 @@ public class ImageProcessingService { */ public String processImageWithAi(MultipartFile imageFile) { try { - // 1. MultipartFile을 MultiValueMap으로 변환하여 multipart-form-data 생성 - MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("image_file", imageFile.getResource()); // FastAPI의 인자명과 동일하게 설정 + MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder(); + bodyBuilder.part("image_file", new ByteArrayResource(imageFile.getBytes())) + .filename(imageFile.getOriginalFilename()) // 원래 파일명 사용 + .contentType(MediaType.parseMediaType(imageFile.getContentType())); // 원본 Content-Type 유지 +// WebClient 요청 시 // 2. WebClient를 사용하여 multipart/form-data 형식으로 전송 - byte[] result = + AiResponseDTO result = webClient .post() .uri(fastapiServerUrl + "/process-image") .contentType(MediaType.MULTIPART_FORM_DATA) // multipart/form-data로 설정 - .body(BodyInserters.fromMultipartData(body)) // MultiValueMap 전송 + .body(BodyInserters.fromMultipartData(bodyBuilder.build())) .retrieve() .onStatus( HttpStatusCode::isError, @@ -80,20 +85,21 @@ public String processImageWithAi(MultipartFile imageFile) { return Mono.error( new CustomApiException(ErrorCode.AI_AVATAR_FAILED)); })) - .bodyToMono(byte[].class) - .timeout(Duration.ofSeconds(30)) - .block(Duration.ofSeconds(30)); +// .bodyToMono(byte[].class) + .bodyToMono(AiResponseDTO.class) + .timeout(Duration.ofSeconds(300)) + .block(Duration.ofSeconds(300)); - if (result == null || result.length == 0) { - log.error("AI 서버에서 유효한 이미지 바이트를 받지 못했습니다 (null/empty)."); + if (result == null || result.getImageUrl().isEmpty()) { + log.error("AI 서버에서 유효한 이미지 링크를 받지 못했습니다 (null/empty)."); throw new CustomApiException(ErrorCode.AI_AVATAR_FAILED); } // 3. 받은 byte 배열을 스토리지에 업로드하고 URL을 받음 - String imageUrl = - storageService.uploadFile(result, "avatars/", imageFile.getOriginalFilename()); +// String imageUrl = +// storageService.uploadFile(result, "avatars/", imageFile.getOriginalFilename()); - return imageUrl; + return result.getImageUrl(); } catch (Exception e) { // 실패 시 폴백 URL 리스트에서 랜덤으로 하나를 선택하여 반환