diff --git a/src/main/java/com/example/Centralthon/domain/menu/entity/enums/Concept.java b/src/main/java/com/example/Centralthon/domain/menu/entity/enums/Concept.java new file mode 100644 index 0000000..2223747 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/menu/entity/enums/Concept.java @@ -0,0 +1,33 @@ +package com.example.Centralthon.domain.menu.entity.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Concept { + DIET("diet"), + KETO("keto"), + LOW_SODIUM("low_sodium"), + GLYCEMIC("glycemic"), + BULKING("bulking"); + + private final String value; + + @JsonValue + public String getValue(){ + return value; + } + + @JsonCreator + public static Concept fromValue(String value){ + for (Concept concept : Concept.values()){ + if (concept.getValue().equals(value)){ + return concept; + } + } + throw new IllegalArgumentException(); + } +} diff --git a/src/main/java/com/example/Centralthon/domain/menu/service/MenuService.java b/src/main/java/com/example/Centralthon/domain/menu/service/MenuService.java index 812fe1b..a944d55 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/service/MenuService.java +++ b/src/main/java/com/example/Centralthon/domain/menu/service/MenuService.java @@ -3,6 +3,10 @@ import com.example.Centralthon.domain.menu.web.dto.*; import com.example.Centralthon.domain.menu.web.dto.NearbyMenusRes; import com.example.Centralthon.domain.menu.web.dto.StoresByMenuRes; +import com.example.Centralthon.domain.menu.web.dto.GetRecommendedMenusReq; +import com.example.Centralthon.global.external.ai.web.dto.GetTipReq; +import com.example.Centralthon.global.external.ai.web.dto.GetTipRes; + import java.util.List; public interface MenuService { @@ -11,4 +15,8 @@ public interface MenuService { List storesByMenu(String name, double lat, double lng); List details(MenuIdsReq menus); + + List getRecommendedMenus(GetRecommendedMenusReq getRecommendedMenusReq); + + List getTips(GetTipReq getTipReq); } diff --git a/src/main/java/com/example/Centralthon/domain/menu/service/MenuServiceImpl.java b/src/main/java/com/example/Centralthon/domain/menu/service/MenuServiceImpl.java index 25eca1e..24fb7d3 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/service/MenuServiceImpl.java +++ b/src/main/java/com/example/Centralthon/domain/menu/service/MenuServiceImpl.java @@ -4,28 +4,31 @@ import com.example.Centralthon.domain.menu.exception.MenuNotFoundException; import com.example.Centralthon.domain.menu.repository.MenuRepository; import com.example.Centralthon.domain.menu.web.dto.*; -import com.example.Centralthon.domain.store.entity.Store; +import com.example.Centralthon.domain.menu.web.dto.GetRecommendedMenusReq; +import com.example.Centralthon.global.external.ai.service.AiService; +import com.example.Centralthon.global.external.ai.web.dto.GetMenusByConceptReq; +import com.example.Centralthon.global.external.ai.web.dto.GetMenusByConceptRes; +import com.example.Centralthon.global.external.ai.web.dto.GetTipReq; +import com.example.Centralthon.global.external.ai.web.dto.GetTipRes; +import com.example.Centralthon.global.external.exception.AiCommunicationFailedException; import com.example.Centralthon.global.util.geo.BoundingBox; import com.example.Centralthon.global.util.geo.GeoUtils; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; import static com.example.Centralthon.global.util.geo.GeoUtils.calculateBoundingBox; -import static com.example.Centralthon.global.util.geo.GeoUtils.calculateDistance; @Service +@Slf4j @RequiredArgsConstructor public class MenuServiceImpl implements MenuService { private final MenuRepository menuRepository; + private final AiService aiService; // 주어진 위도, 경도를 중심으로 반경 2km 이내의 메뉴 조회 private List findMenusWithinRadius(double latitude, double longitude) { @@ -89,4 +92,44 @@ public List details(MenuIdsReq menus) { .toList(); } + @Override + public List getRecommendedMenus(GetRecommendedMenusReq getRecommendedMenusReq){ + // 2km 이내 메뉴 조회 + List menus = findMenusWithinRadius(getRecommendedMenusReq.getLatitude(), getRecommendedMenusReq.getLongitude()); + + // 중복 제거한 메뉴 목록 + Map uniqueMenus = new LinkedHashMap<>(); + for (Menu menu : menus) { + uniqueMenus.putIfAbsent(menu.getName(), menu); + } + + List menuList = new ArrayList<>(uniqueMenus.keySet()); + List getMenusByConceptResList = aiService.getMenuByConceptFromAi( + GetMenusByConceptReq.builder() + .concept(getRecommendedMenusReq.getConcept()) + .count(getRecommendedMenusReq.getCount()) + .items(menuList) + .build() + ); + + return getMenusByConceptResList.stream() + .map(res -> { + Menu matchedMenu = uniqueMenus.get(res.inputMenu()); + return NearbyMenusRes.from(matchedMenu); + }) + .toList(); + } + + @Override + public List getTips(GetTipReq getTipReq) { + try{ + return aiService.getTipFromAi(getTipReq); + } catch (Exception e){ + log.error("AI 서버 호출 실패 {}", e.getMessage()); + throw new AiCommunicationFailedException(); + } + } + + + } diff --git a/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuApi.java b/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuApi.java index 95c51b4..c9797ae 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuApi.java +++ b/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuApi.java @@ -1,9 +1,8 @@ package com.example.Centralthon.domain.menu.web.controller; -import com.example.Centralthon.domain.menu.web.dto.MenuDetailsRes; -import com.example.Centralthon.domain.menu.web.dto.MenuIdsReq; -import com.example.Centralthon.domain.menu.web.dto.NearbyMenusRes; -import com.example.Centralthon.domain.menu.web.dto.StoresByMenuRes; +import com.example.Centralthon.domain.menu.web.dto.*; +import com.example.Centralthon.global.external.ai.web.dto.GetTipReq; +import com.example.Centralthon.global.external.ai.web.dto.GetTipRes; import com.example.Centralthon.global.response.SuccessResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -155,4 +154,115 @@ ResponseEntity>> storesByMenu( ) ) ResponseEntity>> details(@RequestBody @Valid MenuIdsReq menus); + + + + @Operation( + summary = "메뉴 Tip 조회", + description = "입력한 메뉴 이름 배열을 기반으로 AI 추천 Tip(활용법/조리법 등)을 반환합니다.
" + + "{title, content} 리스트를 반환합니다." + ) + @ApiResponse( + responseCode = "200", + description = "메뉴 Tip 조회 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = SuccessResponse.class), + examples = @ExampleObject( + name = "SUCCESS_200", + value = """ + { + "timestamp": "2025-08-22 03:11:13", + "code": "SUCCESS_200", + "httpStatus": 200, + "message": "호출에 성공하였습니다.", + "data": [ + { + "title": "콩나물의 변신, 국물 한 스푼", + "content": "콩나물무침을 육수에 넣고 끓이면 시원한 콩나물국이 됩니다." + }, + { + "title": "감자볶음, 크리스피한 감자튀김으로", + "content": "감자볶음을 잘게 썰어 튀김가루에 묻혀서 튀기면 바삭한 감자튀김이 됩니다." + }, + { + "title": "애호박 볶음, 달콤한 애호박전으로", + "content": "애호박볶음을 반죽에 섞어 팬에 부치면 맛있는 애호박전이 됩니다." + } + ], + "isSuccess": true + } + """ + ) + ) + ) + ResponseEntity>> getTips( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "메뉴 이름 배열 요청", + required = true, + content = @Content( + schema = @Schema(implementation = GetTipReq.class), + examples = @ExampleObject( + value = """ + { + "menus": ["콩나물무침", "감자볶음", "애호박볶음"] + } + """ + ) + ) + ) + @Valid @RequestBody GetTipReq getTipReq); + + @Operation( + summary = "메뉴 추천", + description = "사용자 위치(latitude, longitude)와 컨셉(concept)을 기반으로 맞춤 메뉴를 추천합니다.
" + + "concept는 다음 중 하나만 선택할 수 있습니다: `diet`, `keto`, `low_sodium`, `bulking`, `glycemic`" + ) + @ApiResponse( + responseCode = "200", + description = "메뉴 추천 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = SuccessResponse.class), + examples = @ExampleObject( + name = "SUCCESS_200", + value = """ + { + "timestamp": "2025-08-22 14:36:04", + "code": "SUCCESS_200", + "httpStatus": 200, + "message": "호출에 성공하였습니다.", + "data": [ + { + "name": "돼지 목살 스테이크", + "category": "STIR_FRY" + }, + ... + ], + "isSuccess": true + } + """ + ) + ) + ) + ResponseEntity>> recommend( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "사용자 위치와 추천 컨셉 요청", + required = true, + content = @Content( + schema = @Schema(implementation = GetRecommendedMenusReq.class), + examples = @ExampleObject( + value = """ + { + "latitude": 37.4752, + "longitude": 127.050, + "concept": "keto", + "count": 15 + } + """ + ) + ) + ) + @Valid @RequestBody GetRecommendedMenusReq getRecommendedMenusReq); + } diff --git a/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuController.java b/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuController.java index 3598235..7f068ab 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuController.java +++ b/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuController.java @@ -3,13 +3,14 @@ import com.example.Centralthon.domain.menu.service.MenuService; import com.example.Centralthon.domain.menu.web.dto.*; -import com.example.Centralthon.domain.order.web.controller.OrderApi; +import com.example.Centralthon.domain.menu.web.dto.GetRecommendedMenusReq; +import com.example.Centralthon.global.external.ai.web.dto.GetTipReq; +import com.example.Centralthon.global.external.ai.web.dto.GetTipRes; import com.example.Centralthon.global.response.SuccessResponse; import jakarta.validation.Valid; import com.example.Centralthon.domain.menu.web.dto.NearbyMenusRes; import com.example.Centralthon.domain.menu.web.dto.StoresByMenuRes; -import com.example.Centralthon.global.response.SuccessResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -17,7 +18,6 @@ import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/api/menus") @@ -58,4 +58,20 @@ public ResponseEntity>> details(@RequestBod return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(menuList)); } + + // 컨셉별 메뉴 추천 + @PostMapping("/recommend") + @Override + public ResponseEntity>> recommend(@RequestBody @Valid GetRecommendedMenusReq getRecommendedMenusReq){ + List menusList = menuService.getRecommendedMenus(getRecommendedMenusReq); + return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(menusList)); + } + + // 알뜰 반찬 팁 조회 + @PostMapping("/tips") + @Override + public ResponseEntity>> getTips(@RequestBody @Valid GetTipReq getTipReq){ + List tips = menuService.getTips(getTipReq); + return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(tips)); + } } diff --git a/src/main/java/com/example/Centralthon/domain/menu/web/dto/GetRecommendedMenusReq.java b/src/main/java/com/example/Centralthon/domain/menu/web/dto/GetRecommendedMenusReq.java new file mode 100644 index 0000000..2161ba2 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/menu/web/dto/GetRecommendedMenusReq.java @@ -0,0 +1,28 @@ +package com.example.Centralthon.domain.menu.web.dto; + +import com.example.Centralthon.domain.menu.entity.enums.Concept; +import jakarta.validation.constraints.*; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GetRecommendedMenusReq { + @DecimalMin(value = "-90.0", message = "위도는 -90 이상이어야 합니다.") + @DecimalMax(value = "90.0", message = "위도는 90 이하이어야 합니다.") + @NotNull(message = "위도는 필수값입니다.") + double latitude; + + @DecimalMin(value = "-180.0", message = "경도는 -180 이상이어야 합니다.") + @DecimalMax(value = "180.0", message = "경도는 180 이하이어야 합니다.") + @NotNull(message = "경도는 필수값입니다.") + double longitude; + + @NotNull(message = "컨셉은 필수 값입니다.") + Concept concept; + + @Min(value = 1, message = "최소 1개 이상 조회해야 합니다.") + @Max(value = 50, message = "최대 50개까지 조회할 수 있습니다.") + @NotNull(message = "숫자는 필수 값입니다.") + int count; // 몇 개 조회할지 +} diff --git a/src/main/java/com/example/Centralthon/global/config/RestTemplateConfig.java b/src/main/java/com/example/Centralthon/global/config/RestTemplateConfig.java new file mode 100644 index 0000000..99c3f33 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/config/RestTemplateConfig.java @@ -0,0 +1,15 @@ +package com.example.Centralthon.global.config; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/Centralthon/global/exception/GlobalExceptionHandler.java b/src/main/java/com/example/Centralthon/global/exception/GlobalExceptionHandler.java index 9b779b1..68c1624 100644 --- a/src/main/java/com/example/Centralthon/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/example/Centralthon/global/exception/GlobalExceptionHandler.java @@ -42,7 +42,7 @@ public ResponseEntity> handleBindException(BindException e) { return ResponseEntity.status(errorREsponse.getHttpStatus()).body(errorREsponse); } - //ReqeustBody 등으로 전달 받은 JSON 바디의 파싱이 실패 했을 때 + // ReqeustBody 등으로 전달 받은 JSON 바디의 파싱이 실패 했을 때 @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { log.error("HttpMessageNotReadableException : {}", e.getMessage(), e); @@ -66,7 +66,7 @@ public ResponseEntity> handleMissingServletRequestPartException return ResponseEntity.status(errorResponse.getHttpStatus()).body(errorResponse); } - //지원하지 않는 HTTP 메소드를 호출할 경우 + // 지원하지 않는 HTTP 메소드를 호출할 경우 @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseEntity> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { log.error("HttpRequestMethodNotSupportedException : {}", e.getMessage(), e); diff --git a/src/main/java/com/example/Centralthon/global/external/ai/client/AiClient.java b/src/main/java/com/example/Centralthon/global/external/ai/client/AiClient.java new file mode 100644 index 0000000..deeae33 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/ai/client/AiClient.java @@ -0,0 +1,21 @@ +package com.example.Centralthon.global.external.ai.client; + +import com.example.Centralthon.global.external.exception.AiCommunicationFailedException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +@RequiredArgsConstructor +public class AiClient { + private final RestTemplate restTemplate; + + public T postForObject(String url, Object request, Class responseType) { + try{ + return restTemplate.postForObject(url, request, responseType); + } catch (Exception e){ + throw new AiCommunicationFailedException(); + } + + } +} diff --git a/src/main/java/com/example/Centralthon/global/external/ai/service/AiService.java b/src/main/java/com/example/Centralthon/global/external/ai/service/AiService.java new file mode 100644 index 0000000..9d28173 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/ai/service/AiService.java @@ -0,0 +1,13 @@ +package com.example.Centralthon.global.external.ai.service; + +import com.example.Centralthon.global.external.ai.web.dto.GetMenusByConceptReq; +import com.example.Centralthon.global.external.ai.web.dto.GetMenusByConceptRes; +import com.example.Centralthon.global.external.ai.web.dto.GetTipReq; +import com.example.Centralthon.global.external.ai.web.dto.GetTipRes; + +import java.util.List; + +public interface AiService { + List getTipFromAi(GetTipReq getTipReq); + List getMenuByConceptFromAi(GetMenusByConceptReq getMenuByThemeReq); +} diff --git a/src/main/java/com/example/Centralthon/global/external/ai/service/AiServiceImpl.java b/src/main/java/com/example/Centralthon/global/external/ai/service/AiServiceImpl.java new file mode 100644 index 0000000..7d0e570 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/ai/service/AiServiceImpl.java @@ -0,0 +1,49 @@ +package com.example.Centralthon.global.external.ai.service; + +import com.example.Centralthon.global.external.ai.web.dto.*; +import com.example.Centralthon.global.external.response.ExternalResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AiServiceImpl implements AiService { + + private final RestTemplate restTemplate; + + @Value("${AI_SERVER_URL}") + private String baseUrl; + + @Override + public List getTipFromAi(GetTipReq getTipReq) { + String tipUrl = baseUrl + "tip"; + ResponseEntity>> response = restTemplate.exchange( + tipUrl, + HttpMethod.POST, + new HttpEntity<>(getTipReq), + new ParameterizedTypeReference>>() {} + ); + + return response.getBody().data(); + } + + @Override + public List getMenuByConceptFromAi(GetMenusByConceptReq getMenuByThemeReq) { + String recommendUrl = baseUrl + "menus/recommend"; + ResponseEntity> response = restTemplate.exchange( + recommendUrl, + HttpMethod.POST, + new HttpEntity<>(getMenuByThemeReq), + new ParameterizedTypeReference>() {} + ); + return response.getBody().data().items(); + } +} diff --git a/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetMenusByConceptReq.java b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetMenusByConceptReq.java new file mode 100644 index 0000000..c3ab000 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetMenusByConceptReq.java @@ -0,0 +1,17 @@ +package com.example.Centralthon.global.external.ai.web.dto; + +import com.example.Centralthon.domain.menu.entity.enums.Concept; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@Builder +public class GetMenusByConceptReq { + Concept concept; + int count; + List items; +} diff --git a/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetMenusByConceptRes.java b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetMenusByConceptRes.java new file mode 100644 index 0000000..d38da00 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetMenusByConceptRes.java @@ -0,0 +1,11 @@ +package com.example.Centralthon.global.external.ai.web.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record GetMenusByConceptRes( + @JsonProperty("input_menu") String inputMenu, + @JsonProperty("matched_name") String matchedName, + float similarity, + float suitability +) { +} diff --git a/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetMenusByConceptWrapper.java b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetMenusByConceptWrapper.java new file mode 100644 index 0000000..dd493ed --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetMenusByConceptWrapper.java @@ -0,0 +1,9 @@ +package com.example.Centralthon.global.external.ai.web.dto; + +import java.util.List; + +public record GetMenusByConceptWrapper( + String concept, + int count, + List items +) {} diff --git a/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetTipReq.java b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetTipReq.java new file mode 100644 index 0000000..1b92b2f --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetTipReq.java @@ -0,0 +1,16 @@ +package com.example.Centralthon.global.external.ai.web.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class GetTipReq { + @NotNull(message = "메뉴 목록은 필수 값입니다.") + @NotEmpty(message = "메뉴 목록은 비어있을 수 없습니다.") + List menus; +} diff --git a/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetTipRes.java b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetTipRes.java new file mode 100644 index 0000000..a825ad7 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/ai/web/dto/GetTipRes.java @@ -0,0 +1,7 @@ +package com.example.Centralthon.global.external.ai.web.dto; + +public record GetTipRes( + String title, + String content +) { +} diff --git a/src/main/java/com/example/Centralthon/global/external/exception/AiCommunicationFailedException.java b/src/main/java/com/example/Centralthon/global/external/exception/AiCommunicationFailedException.java new file mode 100644 index 0000000..e79aa76 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/exception/AiCommunicationFailedException.java @@ -0,0 +1,7 @@ +package com.example.Centralthon.global.external.exception; + +import com.example.Centralthon.global.exception.BaseException; + +public class AiCommunicationFailedException extends BaseException { + public AiCommunicationFailedException() {super(AiErrorCode.AI_COMMUNICATION_FAILED);} +} \ No newline at end of file diff --git a/src/main/java/com/example/Centralthon/global/external/exception/AiErrorCode.java b/src/main/java/com/example/Centralthon/global/external/exception/AiErrorCode.java new file mode 100644 index 0000000..b9422c1 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/exception/AiErrorCode.java @@ -0,0 +1,15 @@ +package com.example.Centralthon.global.external.exception; + +import com.example.Centralthon.global.response.code.BaseResponseCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum AiErrorCode implements BaseResponseCode { + AI_COMMUNICATION_FAILED("AI_500", 500, "AI 서버 호출에 실패했습니다."); + + private final String code; + private final int httpStatus; + private final String message; +} diff --git a/src/main/java/com/example/Centralthon/global/external/response/ExternalResponse.java b/src/main/java/com/example/Centralthon/global/external/response/ExternalResponse.java new file mode 100644 index 0000000..ffcfc86 --- /dev/null +++ b/src/main/java/com/example/Centralthon/global/external/response/ExternalResponse.java @@ -0,0 +1,8 @@ +package com.example.Centralthon.global.external.response; + +public record ExternalResponse( + boolean isSuccess, + int httpStatus, + T data, + String timeStamp +) {}