diff --git a/src/main/java/com/comma/soomteum/domain/place/controller/KorService2Controller.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/TourApiDebugController.java similarity index 57% rename from src/main/java/com/comma/soomteum/domain/place/controller/KorService2Controller.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/TourApiDebugController.java index e6fe264..1579a04 100644 --- a/src/main/java/com/comma/soomteum/domain/place/controller/KorService2Controller.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/TourApiDebugController.java @@ -1,11 +1,11 @@ -package com.comma.soomteum.domain.place.controller; +package com.comma.soomteum.domain.external.tourapi; -import com.comma.soomteum.domain.place.dto.response.PlaceDetailResponseDto; -import com.comma.soomteum.domain.place.dto.TourApiRequestDto; -import com.comma.soomteum.domain.place.dto.KorService2Response; -import com.comma.soomteum.domain.place.service.KorAreaService; -import com.comma.soomteum.domain.place.service.KorDetailService; -import com.comma.soomteum.domain.place.service.KorLocationService; +import com.comma.soomteum.domain.external.tourapi.dto.KorService2Response; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiRequestDto; +import com.comma.soomteum.domain.external.tourapi.dto.response.PlaceDetailResponseDto; +import com.comma.soomteum.domain.external.tourapi.service.KorAreaService; +import com.comma.soomteum.domain.external.tourapi.service.KorDetailService; +import com.comma.soomteum.domain.external.tourapi.service.KorLocationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -21,21 +21,21 @@ import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; -@Tag(name = "디버그- KorService2", description = "한국관광공사 데이터 확인용 API (개발환경에서만 사용)") +@Tag(name = "Debug - TourAPI", description = "한국관광공사 TourAPI 응답 구조 확인·디버그용 (개발 환경 전용)") @RestController -@RequestMapping(path = "/api/debug", produces = MediaType.APPLICATION_JSON_VALUE) +@RequestMapping(path = "/api/debug/tour", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor @Validated -public class KorService2Controller { +public class TourApiDebugController { private final KorAreaService areaService; private final KorLocationService locationService; private final KorDetailService detailService; @Operation( - summary = "위치기반 관광정보 조회 (/locationBasedList2)", - description = "경도(mapX)·위도(mapY)와 반경(radius) 기준으로 관광정보를 조회합니다. " - + "옵션: cat1/cat2(분류), pageNo/numOfRows(페이징), arrange(정렬; 기본값 서비스 내부 적용).", + summary = "위치 기반 관광정보 원본 확인", + description = "공공데이터 TourAPI(locationBasedList2)를 호출해 경도·위도와 반경 기준 원본 응답을 반환합니다. " + + "디버그 용도로 cat1/cat2, pageNo/numOfRows, arrange 옵션을 그대로 전달합니다.", responses = { @ApiResponse(responseCode = "200", description = "성공", content = @Content(schema = @Schema(implementation = KorService2Response.class))), @@ -45,15 +45,13 @@ public class KorService2Controller { public Mono locationBasedList( @Valid @ParameterObject TourApiRequestDto.LocationBasedList2 req ) { - // DTO 내부의 pageNoOrDefault, rowsOrDefault, arrangeOrDefault 등을 - // 서비스에서 사용 가능 (컨트롤러에서는 그대로 전달) return locationService.locationBasedList(req); } @Operation( - summary = "지역기반 관광정보 조회 (/areaBasedList2)", - description = "지역과 테마를 기반으로 관광지를 조회합니다. " - + " 옵션: cat1/cat2(분류), pageNo/numOfRows(페이징), arrange(정렬; 기본값 서비스 내부 적용)", + summary = "지역 기반 관광정보 원본 확인", + description = "지역·테마 기반으로 TourAPI(areaBasedList2)를 호출한 원본 응답을 반환합니다. " + + "cat1/cat2, pageNo/numOfRows, arrange 옵션을 그대로 전달해 디버그에 활용합니다.", responses = { @ApiResponse(responseCode = "200", description = "성공", content = @Content(schema = @Schema(implementation = KorService2Response.class))), @@ -67,12 +65,11 @@ public Mono areaBasedList( } @Operation( - summary = "여행지 공통정보 조회 (/detailCommon2)", - description = "contentId로 공통정보를 조회하고, 이름/대표이미지/위치/주소/소개만 추려 반환합니다." + summary = "여행지 공통정보 원본 확인", + description = "contentId로 TourAPI(detailCommon2)를 호출한 원본 데이터에서 기본 필드만 추려 반환합니다." ) @ApiResponse(responseCode = "200", description = "성공", content = @Content(schema = @Schema(implementation = PlaceDetailResponseDto.class))) - @GetMapping("/detail") public Mono detail( @Valid @ParameterObject TourApiRequestDto.DetailCommon2 req diff --git a/src/main/java/com/comma/soomteum/domain/place/config/TourApiConfig.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/config/TourApiConfig.java similarity index 98% rename from src/main/java/com/comma/soomteum/domain/place/config/TourApiConfig.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/config/TourApiConfig.java index 6e7b90a..ba44cec 100644 --- a/src/main/java/com/comma/soomteum/domain/place/config/TourApiConfig.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/config/TourApiConfig.java @@ -1,4 +1,4 @@ -package com.comma.soomteum.domain.place.config; +package com.comma.soomteum.domain.external.tourapi.config; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.dataformat.xml.XmlMapper; diff --git a/src/main/java/com/comma/soomteum/domain/place/config/TourApiProperties.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/config/TourApiProperties.java similarity index 95% rename from src/main/java/com/comma/soomteum/domain/place/config/TourApiProperties.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/config/TourApiProperties.java index 9b767e5..4004325 100644 --- a/src/main/java/com/comma/soomteum/domain/place/config/TourApiProperties.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/config/TourApiProperties.java @@ -1,4 +1,4 @@ -package com.comma.soomteum.domain.place.config; +package com.comma.soomteum.domain.external.tourapi.config; import lombok.*; @@ -65,4 +65,4 @@ public static class Client { private String baseUrl; private String serviceKey; } -} \ No newline at end of file +} diff --git a/src/main/java/com/comma/soomteum/domain/place/dto/CategoryCodeResponse.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/CategoryCodeResponse.java similarity index 96% rename from src/main/java/com/comma/soomteum/domain/place/dto/CategoryCodeResponse.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/dto/CategoryCodeResponse.java index 98cece4..b7a276a 100644 --- a/src/main/java/com/comma/soomteum/domain/place/dto/CategoryCodeResponse.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/CategoryCodeResponse.java @@ -1,4 +1,4 @@ -package com.comma.soomteum.domain.place.dto; +package com.comma.soomteum.domain.external.tourapi.dto; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonRootName; @@ -55,4 +55,4 @@ public static class Item { private String name; private String rnum; } -} \ No newline at end of file +} diff --git a/src/main/java/com/comma/soomteum/domain/place/dto/KorService2Response.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/KorService2Response.java similarity index 97% rename from src/main/java/com/comma/soomteum/domain/place/dto/KorService2Response.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/dto/KorService2Response.java index e115e74..974f8b1 100644 --- a/src/main/java/com/comma/soomteum/domain/place/dto/KorService2Response.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/KorService2Response.java @@ -1,4 +1,4 @@ -package com.comma.soomteum.domain.place.dto; +package com.comma.soomteum.domain.external.tourapi.dto; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/comma/soomteum/domain/place/dto/TourApiErrorResponse.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/TourApiErrorResponse.java similarity index 97% rename from src/main/java/com/comma/soomteum/domain/place/dto/TourApiErrorResponse.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/dto/TourApiErrorResponse.java index 660b3f5..c529f1f 100644 --- a/src/main/java/com/comma/soomteum/domain/place/dto/TourApiErrorResponse.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/TourApiErrorResponse.java @@ -1,4 +1,4 @@ -package com.comma.soomteum.domain.place.dto; +package com.comma.soomteum.domain.external.tourapi.dto; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/src/main/java/com/comma/soomteum/domain/place/dto/TourApiRequestDto.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/TourApiRequestDto.java similarity index 98% rename from src/main/java/com/comma/soomteum/domain/place/dto/TourApiRequestDto.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/dto/TourApiRequestDto.java index 94467a5..7522552 100644 --- a/src/main/java/com/comma/soomteum/domain/place/dto/TourApiRequestDto.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/TourApiRequestDto.java @@ -1,4 +1,4 @@ -package com.comma.soomteum.domain.place.dto; +package com.comma.soomteum.domain.external.tourapi.dto; import lombok.*; diff --git a/src/main/java/com/comma/soomteum/domain/place/dto/response/PlaceDetailResponseDto.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/response/PlaceDetailResponseDto.java similarity index 97% rename from src/main/java/com/comma/soomteum/domain/place/dto/response/PlaceDetailResponseDto.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/dto/response/PlaceDetailResponseDto.java index 8bfc229..ed74653 100644 --- a/src/main/java/com/comma/soomteum/domain/place/dto/response/PlaceDetailResponseDto.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/dto/response/PlaceDetailResponseDto.java @@ -1,4 +1,4 @@ -package com.comma.soomteum.domain.place.dto.response; +package com.comma.soomteum.domain.external.tourapi.dto.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/com/comma/soomteum/domain/place/service/KorApiCaller.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorApiCaller.java similarity index 97% rename from src/main/java/com/comma/soomteum/domain/place/service/KorApiCaller.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorApiCaller.java index c96f4f8..4abd2c9 100644 --- a/src/main/java/com/comma/soomteum/domain/place/service/KorApiCaller.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorApiCaller.java @@ -1,7 +1,7 @@ -package com.comma.soomteum.domain.place.service; +package com.comma.soomteum.domain.external.tourapi.service; -import com.comma.soomteum.domain.place.config.TourApiProperties; -import com.comma.soomteum.domain.place.dto.TourApiErrorResponse; +import com.comma.soomteum.domain.external.tourapi.config.TourApiProperties; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiErrorResponse; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; @@ -177,4 +177,4 @@ public static class Upstream5xxException extends RuntimeException { public final String body; public Upstream5xxException(String msg, String body) { super(msg); this.body = body; } } -} \ No newline at end of file +} diff --git a/src/main/java/com/comma/soomteum/domain/place/service/KorAreaService.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorAreaService.java similarity index 77% rename from src/main/java/com/comma/soomteum/domain/place/service/KorAreaService.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorAreaService.java index 6049ef2..0107f22 100644 --- a/src/main/java/com/comma/soomteum/domain/place/service/KorAreaService.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorAreaService.java @@ -1,12 +1,12 @@ -package com.comma.soomteum.domain.place.service; +package com.comma.soomteum.domain.external.tourapi.service; -import com.comma.soomteum.domain.place.dto.KorService2Response; -import com.comma.soomteum.domain.place.dto.TourApiRequestDto; +import com.comma.soomteum.domain.external.tourapi.dto.KorService2Response; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiRequestDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; -import static com.comma.soomteum.domain.place.service.KorApiCaller.qpIfPresent; +import static com.comma.soomteum.domain.external.tourapi.service.KorApiCaller.qpIfPresent; @Service @RequiredArgsConstructor @@ -30,4 +30,4 @@ public Mono areaBasedList(TourApiRequestDto.AreaBasedList2 qpIfPresent(b, "cat2", req.getCat2()); }, KorService2Response.class); } -} \ No newline at end of file +} diff --git a/src/main/java/com/comma/soomteum/domain/place/service/KorCategoryService.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorCategoryService.java similarity index 69% rename from src/main/java/com/comma/soomteum/domain/place/service/KorCategoryService.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorCategoryService.java index b333534..600d1c8 100644 --- a/src/main/java/com/comma/soomteum/domain/place/service/KorCategoryService.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorCategoryService.java @@ -1,12 +1,12 @@ -package com.comma.soomteum.domain.place.service; +package com.comma.soomteum.domain.external.tourapi.service; -import com.comma.soomteum.domain.place.dto.CategoryCodeResponse; -import com.comma.soomteum.domain.place.dto.TourApiRequestDto; +import com.comma.soomteum.domain.external.tourapi.dto.CategoryCodeResponse; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiRequestDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; -import static com.comma.soomteum.domain.place.service.KorApiCaller.qpIfPresent; +import static com.comma.soomteum.domain.external.tourapi.service.KorApiCaller.qpIfPresent; @Service @RequiredArgsConstructor diff --git a/src/main/java/com/comma/soomteum/domain/place/service/KorDetailService.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorDetailService.java similarity index 74% rename from src/main/java/com/comma/soomteum/domain/place/service/KorDetailService.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorDetailService.java index 8016f44..986120d 100644 --- a/src/main/java/com/comma/soomteum/domain/place/service/KorDetailService.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorDetailService.java @@ -1,7 +1,7 @@ -package com.comma.soomteum.domain.place.service; +package com.comma.soomteum.domain.external.tourapi.service; -import com.comma.soomteum.domain.place.dto.response.PlaceDetailResponseDto; -import com.comma.soomteum.domain.place.dto.TourApiRequestDto; +import com.comma.soomteum.domain.external.tourapi.dto.response.PlaceDetailResponseDto; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiRequestDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; diff --git a/src/main/java/com/comma/soomteum/domain/place/service/KorLocationService.java b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorLocationService.java similarity index 75% rename from src/main/java/com/comma/soomteum/domain/place/service/KorLocationService.java rename to src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorLocationService.java index 26070d1..5c41682 100644 --- a/src/main/java/com/comma/soomteum/domain/place/service/KorLocationService.java +++ b/src/main/java/com/comma/soomteum/domain/external/tourapi/service/KorLocationService.java @@ -1,12 +1,12 @@ -package com.comma.soomteum.domain.place.service; +package com.comma.soomteum.domain.external.tourapi.service; -import com.comma.soomteum.domain.place.dto.KorService2Response; -import com.comma.soomteum.domain.place.dto.TourApiRequestDto; +import com.comma.soomteum.domain.external.tourapi.dto.KorService2Response; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiRequestDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; -import static com.comma.soomteum.domain.place.service.KorApiCaller.qpIfPresent; +import static com.comma.soomteum.domain.external.tourapi.service.KorApiCaller.qpIfPresent; @Service @RequiredArgsConstructor @@ -27,4 +27,4 @@ public Mono locationBasedList(TourApiRequestDto.LocationBas qpIfPresent(b, "cat2", req.getCat2()); }, KorService2Response.class); } -} \ No newline at end of file +} diff --git a/src/main/java/com/comma/soomteum/domain/place/controller/PlaceController.java b/src/main/java/com/comma/soomteum/domain/place/controller/PlaceController.java index e8710fe..b9bf5fb 100644 --- a/src/main/java/com/comma/soomteum/domain/place/controller/PlaceController.java +++ b/src/main/java/com/comma/soomteum/domain/place/controller/PlaceController.java @@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; -@Tag(name = "여행지", description = "여행지와 관련된 로직") +@Tag(name = "여행지 상세", description = "여행지 통합 상세 조회 전용 API") @RestController @RequestMapping(path = "/api/places", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor @@ -26,34 +26,22 @@ public class PlaceController { private final PlaceDetailIntegratedService placeDetailIntegratedService; @Operation( - summary = "여행지 상세 통합 정보 조회", - description = """ - 여행지 상세 조회 시 필요한 모든 정보를 통합하여 제공합니다. - - **제공 정보:** - - 여행지 이름, 사진, 주소, 지역, 테마, 소개 - - 한적함 등급 (1-5단계, -1: 데이터 없음) - - 좋아요 수 - - AI 꿀팁 요약 (강릉시만 제공) - - 근처 공영주차장 정보 (강릉시만 제공) - - **강릉시 전용 기능:** - - AI가 생성한 여행 꿀팁 요약 - - 주변 5km 이내 공영주차장 최대 5개 정보 - """ + summary = "여행지 통합 상세 조회", + description = "여행지 상세 화면에서 필요한 정보를 한 번에 제공합니다. " + + "기본 프로필(이름, 사진, 주소, 지역, 테마, 소개), 한적함 등급, 좋아요 수, AI 꿀팁(강릉시), 근처 공영주차장(강릉시)까지 통합 조회합니다." ) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "200", + responseCode = "200", description = "통합 정보 조회 성공", content = @Content(schema = @Schema(implementation = PlaceDetailIntegratedResponseDto.class)) ), @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "404", + responseCode = "404", description = "여행지를 찾을 수 없음" ), @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "500", + responseCode = "500", description = "서버 내부 오류" ) }) @@ -61,7 +49,7 @@ public class PlaceController { public Mono> getIntegratedPlaceDetail( @Parameter(description = "공공데이터 API의 컨텐츠 ID", required = true, example = "128758") @PathVariable String contentId) { - + return placeDetailIntegratedService.getIntegratedPlaceDetail(contentId) .map(ApiResponse::ok) .onErrorReturn(new ApiResponse<>(null, false, null, null)); diff --git a/src/main/java/com/comma/soomteum/domain/place/service/PlaceDetailIntegratedService.java b/src/main/java/com/comma/soomteum/domain/place/service/PlaceDetailIntegratedService.java index 3fa73c2..c3c4746 100644 --- a/src/main/java/com/comma/soomteum/domain/place/service/PlaceDetailIntegratedService.java +++ b/src/main/java/com/comma/soomteum/domain/place/service/PlaceDetailIntegratedService.java @@ -4,16 +4,17 @@ import com.comma.soomteum.domain.ai.dto.AiReviewResponse; import com.comma.soomteum.domain.ai.service.AiRecommendationService; import com.comma.soomteum.domain.ai.service.AiReviewService; +import com.comma.soomteum.domain.external.tourapi.service.KorCategoryService; +import com.comma.soomteum.domain.external.tourapi.service.KorDetailService; import com.comma.soomteum.domain.parking.dto.PublicParkingResponseDto; import com.comma.soomteum.domain.parking.service.PublicParkingService; -import com.comma.soomteum.domain.place.dto.CategoryCodeResponse; -import com.comma.soomteum.domain.place.dto.KorService2Response; -import com.comma.soomteum.domain.place.dto.TourApiRequestDto; +import com.comma.soomteum.domain.external.tourapi.dto.CategoryCodeResponse; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiRequestDto; import com.comma.soomteum.domain.place.dto.response.PlaceDetailIntegratedResponseDto; -import com.comma.soomteum.domain.place.dto.response.PlaceDetailResponseDto; +import com.comma.soomteum.domain.external.tourapi.dto.response.PlaceDetailResponseDto; import com.comma.soomteum.domain.theme.entity.Theme; import com.comma.soomteum.domain.theme.repository.ThemeRepository; -import com.comma.soomteum.domain.tour.service.TourService; +import com.comma.soomteum.domain.recommendation.service.TourService; import com.comma.soomteum.domain.userPlace.enums.UserActionType; import com.comma.soomteum.domain.userPlace.service.UserPlaceService; import lombok.RequiredArgsConstructor; @@ -624,4 +625,4 @@ public String getThemeName() { return themeName; } } -} \ No newline at end of file +} diff --git a/src/main/java/com/comma/soomteum/domain/place/service/TatsCnctrApiCaller.java b/src/main/java/com/comma/soomteum/domain/place/service/TatsCnctrApiCaller.java index 13dad0f..5e84491 100644 --- a/src/main/java/com/comma/soomteum/domain/place/service/TatsCnctrApiCaller.java +++ b/src/main/java/com/comma/soomteum/domain/place/service/TatsCnctrApiCaller.java @@ -1,7 +1,7 @@ package com.comma.soomteum.domain.place.service; -import com.comma.soomteum.domain.place.config.TourApiProperties; -import com.comma.soomteum.domain.place.dto.TourApiErrorResponse; +import com.comma.soomteum.domain.external.tourapi.config.TourApiProperties; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiErrorResponse; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.cfg.CoercionAction; diff --git a/src/main/java/com/comma/soomteum/domain/place/service/TatsCnctrService.java b/src/main/java/com/comma/soomteum/domain/place/service/TatsCnctrService.java index e8106ff..80e0e93 100644 --- a/src/main/java/com/comma/soomteum/domain/place/service/TatsCnctrService.java +++ b/src/main/java/com/comma/soomteum/domain/place/service/TatsCnctrService.java @@ -1,6 +1,6 @@ package com.comma.soomteum.domain.place.service; -import com.comma.soomteum.domain.place.dto.KorService2Response; +import com.comma.soomteum.domain.external.tourapi.dto.KorService2Response; import com.comma.soomteum.domain.place.dto.TatsCnctrResponse; import com.comma.soomteum.domain.region.entity.Region; import com.comma.soomteum.domain.region.entity.RegionCnctr; diff --git a/src/main/java/com/comma/soomteum/domain/recommendation/controller/PlaceRecommendationController.java b/src/main/java/com/comma/soomteum/domain/recommendation/controller/PlaceRecommendationController.java new file mode 100644 index 0000000..abf7e2f --- /dev/null +++ b/src/main/java/com/comma/soomteum/domain/recommendation/controller/PlaceRecommendationController.java @@ -0,0 +1,62 @@ +package com.comma.soomteum.domain.recommendation.controller; + +import com.comma.soomteum.domain.ai.adapter.AiServiceAdapter; +import com.comma.soomteum.domain.place.dto.TatsCnctrResponse; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiRequestDto; +import com.comma.soomteum.domain.recommendation.service.TourService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + +@Tag(name = "여행지 추천", description = "위치/지역 기반 추천 API") +@RestController +@RequestMapping(path = "/api/places", produces = MediaType.APPLICATION_JSON_VALUE) +@RequiredArgsConstructor +@Validated +public class PlaceRecommendationController { + private final AiServiceAdapter aiServiceAdapter; + private final TourService recommendPlacesService; + + @Operation( + summary = "위치 기반 여행지 추천", + description = "경도·위도와 반경을 기준으로 검색한 여행지에 한적함 점수(cnctrRate)를 포함해 추천합니다. " + + "cat1/cat2로 분류 필터링 가능하며, pageNo/numOfRows, arrange로 페이징 및 정렬을 설정합니다.", + responses = { + @ApiResponse(responseCode = "200", description = "성공", + content = @Content(schema = @Schema(implementation = TatsCnctrResponse.TatsCnctrResponseDto.class))) + } + ) + @GetMapping("/ai") + public Flux locationRecommendPlaces( + @Valid @ParameterObject TourApiRequestDto.LocationBasedList2 req + ) { + return recommendPlacesService.locationPlaces(req); + } + + @Operation( + summary = "지역 기반 여행지 추천", + description = "지역·테마 조건으로 검색하고, 한적함 점수(cnctrRate)와 AI 추천 순서를 포함해 반환합니다. " + + "keyword, cat1, cat2 필터 및 arrange 옵션(A: AI 추천순, C: 한적함순 등)으로 정렬을 제어합니다.", + responses = { + @ApiResponse(responseCode = "200", description = "성공", + content = @Content(schema = @Schema(implementation = TatsCnctrResponse.TatsCnctrResponseDto.class))) + } + ) + @GetMapping("") + public Flux areaRecommendPlaces( + @Valid @ParameterObject TourApiRequestDto.AreaBasedList2 req + ) { + return recommendPlacesService.AreaPlaces(req); + } +} diff --git a/src/main/java/com/comma/soomteum/domain/tour/service/TourService.java b/src/main/java/com/comma/soomteum/domain/recommendation/service/TourService.java similarity index 97% rename from src/main/java/com/comma/soomteum/domain/tour/service/TourService.java rename to src/main/java/com/comma/soomteum/domain/recommendation/service/TourService.java index c70acee..7ae5c05 100644 --- a/src/main/java/com/comma/soomteum/domain/tour/service/TourService.java +++ b/src/main/java/com/comma/soomteum/domain/recommendation/service/TourService.java @@ -1,12 +1,12 @@ -package com.comma.soomteum.domain.tour.service; +package com.comma.soomteum.domain.recommendation.service; import com.comma.soomteum.domain.ai.adapter.AiServiceAdapter; -import com.comma.soomteum.domain.place.dto.KorService2Response; +import com.comma.soomteum.domain.external.tourapi.dto.KorService2Response; import com.comma.soomteum.domain.place.dto.TatsCnctrResponse; -import com.comma.soomteum.domain.place.dto.TourApiRequestDto; +import com.comma.soomteum.domain.external.tourapi.dto.TourApiRequestDto; import com.comma.soomteum.domain.ai.dto.AiRecommendationRequest; // AI 요청 DTO 임포트 -import com.comma.soomteum.domain.place.service.KorAreaService; -import com.comma.soomteum.domain.place.service.KorLocationService; +import com.comma.soomteum.domain.external.tourapi.service.KorAreaService; +import com.comma.soomteum.domain.external.tourapi.service.KorLocationService; import com.comma.soomteum.domain.place.service.TatsCnctrService; import com.comma.soomteum.domain.place.service.PlaceService; import com.comma.soomteum.domain.theme.repository.ThemeRepository; @@ -242,4 +242,4 @@ private int calculateQuietnessLevel(double rate) { return 5; // 매우 한적함 } } -} \ No newline at end of file +} diff --git a/src/main/java/com/comma/soomteum/domain/tour/controller/TourController.java b/src/main/java/com/comma/soomteum/domain/tour/controller/TourController.java deleted file mode 100644 index 745cded..0000000 --- a/src/main/java/com/comma/soomteum/domain/tour/controller/TourController.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.comma.soomteum.domain.tour.controller; - -import com.comma.soomteum.domain.ai.adapter.AiServiceAdapter; -import com.comma.soomteum.domain.place.dto.TatsCnctrResponse; -import com.comma.soomteum.domain.place.dto.TourApiRequestDto; -import com.comma.soomteum.domain.tour.service.TourService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springdoc.core.annotations.ParameterObject; -import org.springframework.http.MediaType; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Flux; - -@Tag(name = "AI & Area Recommended Places", description = "최종 장소 추천 API 2개") -@RestController -@RequestMapping(path = "/api/places", produces = MediaType.APPLICATION_JSON_VALUE) -@RequiredArgsConstructor -@Validated -public class TourController { - private final AiServiceAdapter aiServiceAdapter; - private final TourService recommendPlacesService; - - @Operation( - summary = "위치 기반 한적한 장소 추천", - description = """ - 주어진 위치(경도, 위도)와 반경을 기준으로 장소를 검색하고, 각 장소의 한적함 점수(cnctrRate)를 포함하여 반환합니다. - - `cnctrRate`는 관광객 혼잡도를 나타내는 지수로, -1은 데이터가 없음을 의미합니다. - - 정렬(arrange) 기본값: O=제목순, Q=수정일순, R=등록일순, S=거리순 - - `cat1`, `cat2`를 통해 장소 분류 코드로 필터링할 수 있습니다. - """, - responses = { - @ApiResponse(responseCode = "200", description = "성공", - content = @Content(schema = @Schema(implementation = TatsCnctrResponse.TatsCnctrResponseDto.class))) - } - ) - @GetMapping("/ai") - public Flux locationRecommendPlaces( - @Valid @ParameterObject TourApiRequestDto.LocationBasedList2 req - ) { - return recommendPlacesService.locationPlaces(req); - } - - @Operation( - summary = "지역 기반 한적한 장소 추천", - description = """ - 각 장소의 한적함 점수(cnctrRate)를 포함하여 반환합니다. - - **검색 기능:** - - `keyword`: 장소명으로 검색 (부분 일치, 대소문자 무관) - - 검색어가 없으면 전체 결과 반환 - - **정렬 옵션:** - - `arrange=A`: AI 추천순 (기본값) - 종합적인 추천 알고리즘 사용 - - `arrange=C`: 한적함순 - cnctrRate 높은 순으로 정렬 (AI 정렬 건너뜀) - - `arrange=Q`: 수정일순 - - `arrange=R`: 등록일순 - - **기타 필터:** - - `cnctrRate`: 관광객 혼잡도 지수 (-1은 데이터 없음) - - `cat1`, `cat2`: 장소 분류 코드로 필터링 - - **예시:** - - `/api/places?areaCode=1&keyword=바다` - "바다" 포함 장소 검색 - - `/api/places?areaCode=1&arrange=C` - 한적함순 정렬 - - `/api/places?areaCode=1&keyword=카페&arrange=C` - "카페" 검색 + 한적함순 - """, - responses = { - @ApiResponse(responseCode = "200", description = "성공", - content = @Content(schema = @Schema(implementation = TatsCnctrResponse.TatsCnctrResponseDto.class))) - } - ) - @GetMapping("") - public Flux AreaRecommendPlaces( - @Valid @ParameterObject TourApiRequestDto.AreaBasedList2 req - ) { - return recommendPlacesService.AreaPlaces(req); - } - - - -} \ No newline at end of file diff --git a/src/main/java/com/comma/soomteum/global/exception/GlobalExceptionHandler.java b/src/main/java/com/comma/soomteum/global/exception/GlobalExceptionHandler.java index b327a71..e66333d 100644 --- a/src/main/java/com/comma/soomteum/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/comma/soomteum/global/exception/GlobalExceptionHandler.java @@ -41,9 +41,9 @@ public ResponseEntity> handleIllegalState(IllegalStateException e } // 업스트림 예외 매핑 - @ExceptionHandler(com.comma.soomteum.domain.place.service.KorApiCaller.Upstream4xxException.class) + @ExceptionHandler(com.comma.soomteum.domain.external.tourapi.service.KorApiCaller.Upstream4xxException.class) public ResponseEntity> handleUpstream4xx( - com.comma.soomteum.domain.place.service.KorApiCaller.Upstream4xxException ex, + com.comma.soomteum.domain.external.tourapi.service.KorApiCaller.Upstream4xxException ex, HttpServletRequest request ) { var custom = new CustomException(ErrorCode.INTERNAL_SERVER_ERROR, ex.getMessage()); @@ -51,9 +51,9 @@ public ResponseEntity> handleUpstream4xx( .body(ApiResponse.fail(custom, request.getRequestURI())); } - @ExceptionHandler(com.comma.soomteum.domain.place.service.KorApiCaller.Upstream5xxException.class) + @ExceptionHandler(com.comma.soomteum.domain.external.tourapi.service.KorApiCaller.Upstream5xxException.class) public ResponseEntity> handleUpstream5xx( - com.comma.soomteum.domain.place.service.KorApiCaller.Upstream5xxException ex, + com.comma.soomteum.domain.external.tourapi.service.KorApiCaller.Upstream5xxException ex, HttpServletRequest request ) { var custom = new CustomException(ErrorCode.INTERNAL_SERVER_ERROR, ex.getMessage());