From 58cbc9fceef1b6833ff7d0f30fd3684dc3d02851 Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Fri, 10 Oct 2025 16:06:50 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor(be)=20:=20TourService=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/tour/service/TourParamsParser.kt | 23 +++ .../domain/ai/tour/service/TourService.kt | 149 +---------------- .../domain/ai/tour/service/TourServiceCore.kt | 154 +++++++++++++++++- 3 files changed, 181 insertions(+), 145 deletions(-) create mode 100644 src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourParamsParser.kt diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourParamsParser.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourParamsParser.kt new file mode 100644 index 0000000..3f16d7a --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourParamsParser.kt @@ -0,0 +1,23 @@ +package com.back.koreaTravelGuide.domain.ai.tour.service + +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams +import org.springframework.stereotype.Component + +@Component +class TourParamsParser { + fun parse( + contentTypeId: String, + areaAndSigunguCode: String, + ): TourParams { + val codes = areaAndSigunguCode.split(",").map { it.trim() } + + val areaCode = codes.getOrNull(0) + val sigunguCode = codes.getOrNull(1) + + return TourParams( + contentTypeId = contentTypeId, + areaCode = areaCode, + sigunguCode = sigunguCode, + ) + } +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourService.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourService.kt index c8ba35e..56f433b 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourService.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourService.kt @@ -1,178 +1,41 @@ package com.back.koreaTravelGuide.domain.ai.tour.service -import com.back.koreaTravelGuide.domain.ai.tour.client.TourApiClient -import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailItem import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailParams import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailResponse -import com.back.koreaTravelGuide.domain.ai.tour.dto.TourItem import com.back.koreaTravelGuide.domain.ai.tour.dto.TourLocationBasedParams import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse -import org.slf4j.LoggerFactory -import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Service // 09.26 양현준 @Service class TourService( - private val tourApiClient: TourApiClient, + private val tourServiceCore: TourServiceCore, + private val tourParamsParser: TourParamsParser, ) { - private val logger = LoggerFactory.getLogger(this::class.java) - // 파라미터를 TourParams DTO에 맞게 파싱 fun parseParams( contentTypeId: String, areaAndSigunguCode: String, ): TourParams { - val codes = areaAndSigunguCode.split(",").map { it.trim() } - - val areaCode = codes.getOrNull(0) - val sigunguCode = codes.getOrNull(1) - - return TourParams( - contentTypeId = contentTypeId, - areaCode = areaCode, - sigunguCode = sigunguCode, - ) + return tourParamsParser.parse(contentTypeId, areaAndSigunguCode) } // API 호출 1, 지역기반 관광정보 조회 - areaBasedList2 - @Cacheable( - "tourAreaBased", - key = "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode", - ) fun fetchTours(tourParams: TourParams): TourResponse { - // 09.30 테스트용 하드코딩 - if ( - tourParams.contentTypeId == "12" && - tourParams.areaCode == "6" && - tourParams.sigunguCode == "10" - ) { - return PRESET_AREA_TOUR_RESPONSE - } - - val tours = tourApiClient.fetchTourInfo(tourParams) - - return tours + return tourServiceCore.fetchAreaBasedTours(tourParams) } // API 호출 2, 위치기반 관광정보 조회 - locationBasedList2 - @Cacheable( - "tourLocationBased", - key = - "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode + " + - "'_' + #locationParams.mapX + '_' + #locationParams.mapY + '_' + #locationParams.radius", - ) fun fetchLocationBasedTours( tourParams: TourParams, locationParams: TourLocationBasedParams, ): TourResponse { - // 09.30 테스트용 하드코딩 - if ( - tourParams.contentTypeId == "39" && - tourParams.areaCode == "1" && - tourParams.sigunguCode == "24" && - locationParams.mapX == "126.98375" && - locationParams.mapY == "37.563446" && - locationParams.radius == "100" - ) { - return PRESET_LOCATION_BASED_RESPONSE - } - - return tourApiClient.fetchLocationBasedTours(tourParams, locationParams) + return tourServiceCore.fetchLocationBasedTours(tourParams, locationParams) } // APi 호출 3, 관광정보 상세조회 - detailCommon2 - @Cacheable("tourDetail", key = "#detailParams.contentId") fun fetchTourDetail(detailParams: TourDetailParams): TourDetailResponse { - // 09.30 테스트용 하드코딩 - if ( - detailParams.contentId == "127974" - ) { - return PRESET_DETAIL_RESPONSE - } - - return tourApiClient.fetchTourDetail(detailParams) + return tourServiceCore.fetchTourDetail(detailParams) } } - -/** - * 09.30 테스트용 하드코딩 - * "areacode": "6" 부산 - * "sigungucode": "10" 사하구 - * "contenttypeid": "12" 관광지 - * 실제 API 호출 대신, 정해진 응답을 반환 - */ -private val PRESET_AREA_TOUR_RESPONSE = - TourResponse( - items = - listOf( - TourItem( - contentId = "127974", - contentTypeId = "12", - createdTime = "20031208090000", - modifiedTime = "20250411180037", - title = "을숙도 공원", - addr1 = "부산광역시 사하구 낙동남로 1240 (하단동)", - areaCode = "6", - firstimage = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image2_1.jpg", - firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image3_1.jpg", - mapX = "128.9460030322", - mapY = "35.1045320626", - distance = null, - mlevel = "6", - sigunguCode = "10", - lDongRegnCd = "26", - lDongSignguCd = "380", - ), - ), - ) - -private val PRESET_LOCATION_BASED_RESPONSE = - TourResponse( - items = - listOf( - TourItem( - contentId = "133858", - contentTypeId = "39", - createdTime = "20030529090000", - modifiedTime = "20250409105941", - title = "백제삼계탕", - addr1 = "서울특별시 중구 명동8길 8-10 (명동2가)", - areaCode = "1", - firstimage = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image2_1.JPG", - firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image3_1.JPG", - mapX = "126.9841178194", - mapY = "37.5634241535", - distance = "32.788938679922325", - mlevel = "6", - sigunguCode = "24", - lDongRegnCd = "11", - lDongSignguCd = "140", - ), - ), - ) - -private val PRESET_DETAIL_RESPONSE = - TourDetailResponse( - items = - listOf( - TourDetailItem( - contentId = "126128", - title = "동촌유원지", - overview = - "동촌유원지는 대구시 동쪽 금호강변에 있는 44만 평의 유원지로 오래전부터 대구 시민이 즐겨 찾는 곳이다. " + - "각종 위락시설이 잘 갖춰져 있으며, 드라이브를 즐길 수 있는 도로가 건설되어 있다. 수량이 많은 금호강에는 조교가 가설되어 있고, " + - "우아한 다리 이름을 가진 아양교가 걸쳐 있다. 금호강(琴湖江)을 끼고 있어 예로부터 봄에는 그네뛰기, 봉숭아꽃 구경, " + - "여름에는 수영과 보트 놀이, 가을에는 밤 줍기 등 즐길 거리가 많은 곳이다. 또한, 해맞이다리, 유선장, 체육시설, " + - "실내 롤러스케이트장 등 다양한 즐길 거리가 있어 여행의 재미를 더해준다.", - addr1 = "대구광역시 동구 효목동", - mapX = "128.6506352387", - mapY = "35.8826195757", - firstImage = "http://tongit g.visitkorea.or.kr/cms/resource/86/3488286_image2_1.JPG", - tel = "", - homepage = - "", - ), - ), - ) diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourServiceCore.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourServiceCore.kt index d7a2fd9..f361b11 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourServiceCore.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourServiceCore.kt @@ -1,4 +1,154 @@ package com.back.koreaTravelGuide.domain.ai.tour.service -// TODO: 관광 정보 핵심 서비스 - API 호출 및 비즈니스 로직 처리 -class TourServiceCore +import com.back.koreaTravelGuide.domain.ai.tour.client.TourApiClient +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailItem +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailResponse +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourItem +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourLocationBasedParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse +import org.springframework.cache.annotation.Cacheable +import org.springframework.stereotype.Service + +@Service +class TourServiceCore( + private val tourApiClient: TourApiClient, +) { + @Cacheable( + "tourAreaBased", + key = "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode", + ) + fun fetchAreaBasedTours(tourParams: TourParams): TourResponse { + // 09.30 테스트용 하드코딩 + if ( + tourParams.contentTypeId == "12" && + tourParams.areaCode == "6" && + tourParams.sigunguCode == "10" + ) { + return PRESET_AREA_TOUR_RESPONSE + } + + return tourApiClient.fetchTourInfo(tourParams) + } + + @Cacheable( + "tourLocationBased", + key = + "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode + " + + "'_' + #locationParams.mapX + '_' + #locationParams.mapY + '_' + #locationParams.radius", + ) + fun fetchLocationBasedTours( + tourParams: TourParams, + locationParams: TourLocationBasedParams, + ): TourResponse { + // 09.30 테스트용 하드코딩 + if ( + tourParams.contentTypeId == "39" && + tourParams.areaCode == "1" && + tourParams.sigunguCode == "24" && + locationParams.mapX == "126.98375" && + locationParams.mapY == "37.563446" && + locationParams.radius == "100" + ) { + return PRESET_LOCATION_BASED_RESPONSE + } + + return tourApiClient.fetchLocationBasedTours(tourParams, locationParams) + } + + @Cacheable("tourDetail", key = "#detailParams.contentId") + fun fetchTourDetail(detailParams: TourDetailParams): TourDetailResponse { + // 09.30 테스트용 하드코딩 + if ( + detailParams.contentId == "127974" + ) { + return PRESET_DETAIL_RESPONSE + } + + return tourApiClient.fetchTourDetail(detailParams) + } + + companion object { + /** + * 09.30 테스트용 하드코딩 + * "areacode": "6" 부산 + * "sigungucode": "10" 사하구 + * "contenttypeid": "12" 관광지 + * 실제 API 호출 대신, 정해진 응답을 반환 + */ + private val PRESET_AREA_TOUR_RESPONSE = + TourResponse( + items = + listOf( + TourItem( + contentId = "127974", + contentTypeId = "12", + createdTime = "20031208090000", + modifiedTime = "20250411180037", + title = "을숙도 공원", + addr1 = "부산광역시 사하구 낙동남로 1240 (하단동)", + areaCode = "6", + firstimage = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image2_1.jpg", + firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image3_1.jpg", + mapX = "128.9460030322", + mapY = "35.1045320626", + distance = null, + mlevel = "6", + sigunguCode = "10", + lDongRegnCd = "26", + lDongSignguCd = "380", + ), + ), + ) + + private val PRESET_LOCATION_BASED_RESPONSE = + TourResponse( + items = + listOf( + TourItem( + contentId = "133858", + contentTypeId = "39", + createdTime = "20030529090000", + modifiedTime = "20250409105941", + title = "백제삼계탕", + addr1 = "서울특별시 중구 명동8길 8-10 (명동2가)", + areaCode = "1", + firstimage = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image2_1.JPG", + firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image3_1.JPG", + mapX = "126.9841178194", + mapY = "37.5634241535", + distance = "32.788938679922325", + mlevel = "6", + sigunguCode = "24", + lDongRegnCd = "11", + lDongSignguCd = "140", + ), + ), + ) + + private val PRESET_DETAIL_RESPONSE = + TourDetailResponse( + items = + listOf( + TourDetailItem( + contentId = "126128", + title = "동촌유원지", + overview = + "동촌유원지는 대구시 동쪽 금호강변에 있는 44만 평의 유원지로 오래전부터 대구 시민이 즐겨 찾는 곳이다. " + + "각종 위락시설이 잘 갖춰져 있으며, 드라이브를 즐길 수 있는 도로가 건설되어 있다. 수량이 많은 금호강에는 조교가 가설되어 있고, " + + "우아한 다리 이름을 가진 아양교가 걸쳐 있다. 금호강(琴湖江)을 끼고 있어 예로부터 봄에는 그네뛰기, 봉숭아꽃 구경, " + + "여름에는 수영과 보트 놀이, 가을에는 밤 줍기 등 즐길 거리가 많은 곳이다. 또한, 해맞이다리, 유선장, 체육시설, " + + "실내 롤러스케이트장 등 다양한 즐길 거리가 있어 여행의 재미를 더해준다.", + addr1 = "대구광역시 동구 효목동", + mapX = "128.6506352387", + mapY = "35.8826195757", + firstImage = "http://tongit g.visitkorea.or.kr/cms/resource/86/3488286_image2_1.JPG", + tel = "", + homepage = + "", + ), + ), + ) + } +} From 94084815d90504bd92c5e97dfeb16ab8fa3d398f Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Fri, 10 Oct 2025 16:45:00 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor(be)=20:=20=EB=8B=A8=EC=9D=BC?= =?UTF-8?q?=EC=B1=85=EC=9E=84(SRP),=20=EA=B8=B0=EB=8A=A5=EB=B3=84=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4(ISP)=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ai/tour/service/TourService.kt | 17 +- .../domain/ai/tour/service/TourServiceCore.kt | 154 ------------------ .../service/core/TourAreaBasedServiceCore.kt | 57 +++++++ .../service/core/TourDetailServiceCore.kt | 49 ++++++ .../core/TourLocationBasedServiceCore.kt | 66 ++++++++ .../service/usecase/TourAreaBasedUseCase.kt | 8 + .../tour/service/usecase/TourDetailUseCase.kt | 8 + .../usecase/TourLocationBasedUseCase.kt | 12 ++ 8 files changed, 209 insertions(+), 162 deletions(-) delete mode 100644 src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourServiceCore.kt create mode 100644 src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourAreaBasedServiceCore.kt create mode 100644 src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourDetailServiceCore.kt create mode 100644 src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourLocationBasedServiceCore.kt create mode 100644 src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourAreaBasedUseCase.kt create mode 100644 src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourDetailUseCase.kt create mode 100644 src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourLocationBasedUseCase.kt diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourService.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourService.kt index 56f433b..b25f2eb 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourService.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourService.kt @@ -5,15 +5,19 @@ import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailResponse import com.back.koreaTravelGuide.domain.ai.tour.dto.TourLocationBasedParams import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse +import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourAreaBasedUseCase +import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourDetailUseCase +import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourLocationBasedUseCase import org.springframework.stereotype.Service // 09.26 양현준 @Service class TourService( - private val tourServiceCore: TourServiceCore, + private val tourAreaBasedUseCase: TourAreaBasedUseCase, + private val tourLocationBasedUseCase: TourLocationBasedUseCase, + private val tourDetailUseCase: TourDetailUseCase, private val tourParamsParser: TourParamsParser, ) { - // 파라미터를 TourParams DTO에 맞게 파싱 fun parseParams( contentTypeId: String, areaAndSigunguCode: String, @@ -21,21 +25,18 @@ class TourService( return tourParamsParser.parse(contentTypeId, areaAndSigunguCode) } - // API 호출 1, 지역기반 관광정보 조회 - areaBasedList2 fun fetchTours(tourParams: TourParams): TourResponse { - return tourServiceCore.fetchAreaBasedTours(tourParams) + return tourAreaBasedUseCase.fetchAreaBasedTours(tourParams) } - // API 호출 2, 위치기반 관광정보 조회 - locationBasedList2 fun fetchLocationBasedTours( tourParams: TourParams, locationParams: TourLocationBasedParams, ): TourResponse { - return tourServiceCore.fetchLocationBasedTours(tourParams, locationParams) + return tourLocationBasedUseCase.fetchLocationBasedTours(tourParams, locationParams) } - // APi 호출 3, 관광정보 상세조회 - detailCommon2 fun fetchTourDetail(detailParams: TourDetailParams): TourDetailResponse { - return tourServiceCore.fetchTourDetail(detailParams) + return tourDetailUseCase.fetchTourDetail(detailParams) } } diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourServiceCore.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourServiceCore.kt deleted file mode 100644 index f361b11..0000000 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourServiceCore.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.back.koreaTravelGuide.domain.ai.tour.service - -import com.back.koreaTravelGuide.domain.ai.tour.client.TourApiClient -import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailItem -import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailParams -import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailResponse -import com.back.koreaTravelGuide.domain.ai.tour.dto.TourItem -import com.back.koreaTravelGuide.domain.ai.tour.dto.TourLocationBasedParams -import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams -import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse -import org.springframework.cache.annotation.Cacheable -import org.springframework.stereotype.Service - -@Service -class TourServiceCore( - private val tourApiClient: TourApiClient, -) { - @Cacheable( - "tourAreaBased", - key = "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode", - ) - fun fetchAreaBasedTours(tourParams: TourParams): TourResponse { - // 09.30 테스트용 하드코딩 - if ( - tourParams.contentTypeId == "12" && - tourParams.areaCode == "6" && - tourParams.sigunguCode == "10" - ) { - return PRESET_AREA_TOUR_RESPONSE - } - - return tourApiClient.fetchTourInfo(tourParams) - } - - @Cacheable( - "tourLocationBased", - key = - "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode + " + - "'_' + #locationParams.mapX + '_' + #locationParams.mapY + '_' + #locationParams.radius", - ) - fun fetchLocationBasedTours( - tourParams: TourParams, - locationParams: TourLocationBasedParams, - ): TourResponse { - // 09.30 테스트용 하드코딩 - if ( - tourParams.contentTypeId == "39" && - tourParams.areaCode == "1" && - tourParams.sigunguCode == "24" && - locationParams.mapX == "126.98375" && - locationParams.mapY == "37.563446" && - locationParams.radius == "100" - ) { - return PRESET_LOCATION_BASED_RESPONSE - } - - return tourApiClient.fetchLocationBasedTours(tourParams, locationParams) - } - - @Cacheable("tourDetail", key = "#detailParams.contentId") - fun fetchTourDetail(detailParams: TourDetailParams): TourDetailResponse { - // 09.30 테스트용 하드코딩 - if ( - detailParams.contentId == "127974" - ) { - return PRESET_DETAIL_RESPONSE - } - - return tourApiClient.fetchTourDetail(detailParams) - } - - companion object { - /** - * 09.30 테스트용 하드코딩 - * "areacode": "6" 부산 - * "sigungucode": "10" 사하구 - * "contenttypeid": "12" 관광지 - * 실제 API 호출 대신, 정해진 응답을 반환 - */ - private val PRESET_AREA_TOUR_RESPONSE = - TourResponse( - items = - listOf( - TourItem( - contentId = "127974", - contentTypeId = "12", - createdTime = "20031208090000", - modifiedTime = "20250411180037", - title = "을숙도 공원", - addr1 = "부산광역시 사하구 낙동남로 1240 (하단동)", - areaCode = "6", - firstimage = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image2_1.jpg", - firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image3_1.jpg", - mapX = "128.9460030322", - mapY = "35.1045320626", - distance = null, - mlevel = "6", - sigunguCode = "10", - lDongRegnCd = "26", - lDongSignguCd = "380", - ), - ), - ) - - private val PRESET_LOCATION_BASED_RESPONSE = - TourResponse( - items = - listOf( - TourItem( - contentId = "133858", - contentTypeId = "39", - createdTime = "20030529090000", - modifiedTime = "20250409105941", - title = "백제삼계탕", - addr1 = "서울특별시 중구 명동8길 8-10 (명동2가)", - areaCode = "1", - firstimage = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image2_1.JPG", - firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image3_1.JPG", - mapX = "126.9841178194", - mapY = "37.5634241535", - distance = "32.788938679922325", - mlevel = "6", - sigunguCode = "24", - lDongRegnCd = "11", - lDongSignguCd = "140", - ), - ), - ) - - private val PRESET_DETAIL_RESPONSE = - TourDetailResponse( - items = - listOf( - TourDetailItem( - contentId = "126128", - title = "동촌유원지", - overview = - "동촌유원지는 대구시 동쪽 금호강변에 있는 44만 평의 유원지로 오래전부터 대구 시민이 즐겨 찾는 곳이다. " + - "각종 위락시설이 잘 갖춰져 있으며, 드라이브를 즐길 수 있는 도로가 건설되어 있다. 수량이 많은 금호강에는 조교가 가설되어 있고, " + - "우아한 다리 이름을 가진 아양교가 걸쳐 있다. 금호강(琴湖江)을 끼고 있어 예로부터 봄에는 그네뛰기, 봉숭아꽃 구경, " + - "여름에는 수영과 보트 놀이, 가을에는 밤 줍기 등 즐길 거리가 많은 곳이다. 또한, 해맞이다리, 유선장, 체육시설, " + - "실내 롤러스케이트장 등 다양한 즐길 거리가 있어 여행의 재미를 더해준다.", - addr1 = "대구광역시 동구 효목동", - mapX = "128.6506352387", - mapY = "35.8826195757", - firstImage = "http://tongit g.visitkorea.or.kr/cms/resource/86/3488286_image2_1.JPG", - tel = "", - homepage = - "", - ), - ), - ) - } -} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourAreaBasedServiceCore.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourAreaBasedServiceCore.kt new file mode 100644 index 0000000..85a3db1 --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourAreaBasedServiceCore.kt @@ -0,0 +1,57 @@ +package com.back.koreaTravelGuide.domain.ai.tour.service.core + +import com.back.koreaTravelGuide.domain.ai.tour.client.TourApiClient +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourItem +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse +import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourAreaBasedUseCase +import org.springframework.cache.annotation.Cacheable +import org.springframework.stereotype.Service + +@Service +class TourAreaBasedServiceCore( + private val tourApiClient: TourApiClient, +) : TourAreaBasedUseCase { + @Cacheable( + "tourAreaBased", + key = "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode", + ) + override fun fetchAreaBasedTours(tourParams: TourParams): TourResponse { + if ( + tourParams.contentTypeId == "12" && + tourParams.areaCode == "6" && + tourParams.sigunguCode == "10" + ) { + return PRESET_AREA_TOUR_RESPONSE + } + + return tourApiClient.fetchTourInfo(tourParams) + } + + private companion object { + val PRESET_AREA_TOUR_RESPONSE = + TourResponse( + items = + listOf( + TourItem( + contentId = "127974", + contentTypeId = "12", + createdTime = "20031208090000", + modifiedTime = "20250411180037", + title = "을숙도 공원", + addr1 = "부산광역시 사하구 낙동남로 1240 (하단동)", + areaCode = "6", + firstimage = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image2_1.jpg", + firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image3_1.jpg", + mapX = "128.9460030322", + mapY = "35.1045320626", + distance = null, + mlevel = "6", + sigunguCode = "10", + lDongRegnCd = "26", + lDongSignguCd = "380", + ), + ), + ) + } +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourDetailServiceCore.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourDetailServiceCore.kt new file mode 100644 index 0000000..afe3bc8 --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourDetailServiceCore.kt @@ -0,0 +1,49 @@ +package com.back.koreaTravelGuide.domain.ai.tour.service.core + +import com.back.koreaTravelGuide.domain.ai.tour.client.TourApiClient +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailItem +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailResponse +import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourDetailUseCase +import org.springframework.cache.annotation.Cacheable +import org.springframework.stereotype.Service + +@Service +class TourDetailServiceCore( + private val tourApiClient: TourApiClient, +) : TourDetailUseCase { + @Cacheable("tourDetail", key = "#detailParams.contentId") + override fun fetchTourDetail(detailParams: TourDetailParams): TourDetailResponse { + if (detailParams.contentId == "127974") { + return PRESET_DETAIL_RESPONSE + } + + return tourApiClient.fetchTourDetail(detailParams) + } + + private companion object { + val PRESET_DETAIL_RESPONSE = + TourDetailResponse( + items = + listOf( + TourDetailItem( + contentId = "126128", + title = "동촌유원지", + overview = + "동촌유원지는 대구시 동쪽 금호강변에 있는 44만 평의 유원지로 오래전부터 대구 시민이 즐겨 찾는 곳이다. " + + "각종 위락시설이 잘 갖춰져 있으며, 드라이브를 즐길 수 있는 도로가 건설되어 있다. 수량이 많은 금호강에는 조교가 가설되어 있고, " + + "우아한 다리 이름을 가진 아양교가 걸쳐 있다. 금호강(琴湖江)을 끼고 있어 예로부터 봄에는 그네뛰기, 봉숭아꽃 구경, " + + "여름에는 수영과 보트 놀이, 가을에는 밤 줍기 등 즐길 거리가 많은 곳이다. 또한, 해맞이다리, 유선장, 체육시설, " + + "실내 롤러스케이트장 등 다양한 즐길 거리가 있어 여행의 재미를 더해준다.", + addr1 = "대구광역시 동구 효목동", + mapX = "128.6506352387", + mapY = "35.8826195757", + firstImage = "http://tongit g.visitkorea.or.kr/cms/resource/86/3488286_image2_1.JPG", + tel = "", + homepage = + "", + ), + ), + ) + } +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourLocationBasedServiceCore.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourLocationBasedServiceCore.kt new file mode 100644 index 0000000..9f71713 --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourLocationBasedServiceCore.kt @@ -0,0 +1,66 @@ +package com.back.koreaTravelGuide.domain.ai.tour.service.core + +import com.back.koreaTravelGuide.domain.ai.tour.client.TourApiClient +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourItem +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourLocationBasedParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse +import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourLocationBasedUseCase +import org.springframework.cache.annotation.Cacheable +import org.springframework.stereotype.Service + +@Service +class TourLocationBasedServiceCore( + private val tourApiClient: TourApiClient, +) : TourLocationBasedUseCase { + @Cacheable( + "tourLocationBased", + key = + "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode + " + + "'_' + #locationParams.mapX + '_' + #locationParams.mapY + '_' + #locationParams.radius", + ) + override fun fetchLocationBasedTours( + tourParams: TourParams, + locationParams: TourLocationBasedParams, + ): TourResponse { + if ( + tourParams.contentTypeId == "39" && + tourParams.areaCode == "1" && + tourParams.sigunguCode == "24" && + locationParams.mapX == "126.98375" && + locationParams.mapY == "37.563446" && + locationParams.radius == "100" + ) { + return PRESET_LOCATION_BASED_RESPONSE + } + + return tourApiClient.fetchLocationBasedTours(tourParams, locationParams) + } + + private companion object { + val PRESET_LOCATION_BASED_RESPONSE = + TourResponse( + items = + listOf( + TourItem( + contentId = "133858", + contentTypeId = "39", + createdTime = "20030529090000", + modifiedTime = "20250409105941", + title = "백제삼계탕", + addr1 = "서울특별시 중구 명동8길 8-10 (명동2가)", + areaCode = "1", + firstimage = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image2_1.JPG", + firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image3_1.JPG", + mapX = "126.9841178194", + mapY = "37.5634241535", + distance = "32.788938679922325", + mlevel = "6", + sigunguCode = "24", + lDongRegnCd = "11", + lDongSignguCd = "140", + ), + ), + ) + } +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourAreaBasedUseCase.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourAreaBasedUseCase.kt new file mode 100644 index 0000000..13cb184 --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourAreaBasedUseCase.kt @@ -0,0 +1,8 @@ +package com.back.koreaTravelGuide.domain.ai.tour.service.usecase + +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse + +interface TourAreaBasedUseCase { + fun fetchAreaBasedTours(tourParams: TourParams): TourResponse +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourDetailUseCase.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourDetailUseCase.kt new file mode 100644 index 0000000..dec2eb4 --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourDetailUseCase.kt @@ -0,0 +1,8 @@ +package com.back.koreaTravelGuide.domain.ai.tour.service.usecase + +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailResponse + +interface TourDetailUseCase { + fun fetchTourDetail(detailParams: TourDetailParams): TourDetailResponse +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourLocationBasedUseCase.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourLocationBasedUseCase.kt new file mode 100644 index 0000000..507471b --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/usecase/TourLocationBasedUseCase.kt @@ -0,0 +1,12 @@ +package com.back.koreaTravelGuide.domain.ai.tour.service.usecase + +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourLocationBasedParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams +import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse + +interface TourLocationBasedUseCase { + fun fetchLocationBasedTours( + tourParams: TourParams, + locationParams: TourLocationBasedParams, + ): TourResponse +} From fc4ad7fe6ec254df34c3303f94d9384f6abcf952 Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Sat, 11 Oct 2025 22:56:26 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor(be)=20:=20Tour=20=EC=BA=90?= =?UTF-8?q?=EC=8B=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ai/tour/cache/TourCacheConfig.kt | 20 +++++++++++++++++-- .../service/core/TourAreaBasedServiceCore.kt | 1 + .../service/core/TourDetailServiceCore.kt | 2 +- .../core/TourLocationBasedServiceCore.kt | 1 + 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/cache/TourCacheConfig.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/cache/TourCacheConfig.kt index a8482c3..da1485e 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/cache/TourCacheConfig.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/cache/TourCacheConfig.kt @@ -1,4 +1,20 @@ package com.back.koreaTravelGuide.domain.ai.tour.cache -// TODO: 관광 정보 캐시 설정 - 캐시 주기 및 정책 관리 (필요시 구현) -class TourCacheConfig +import com.back.koreaTravelGuide.common.logging.log +import org.springframework.cache.annotation.CacheEvict +import org.springframework.context.annotation.Configuration +import org.springframework.scheduling.annotation.Scheduled + +@Configuration +class TourCacheConfig { + companion object { + private val TOUR_CACHE_NAMES = arrayOf("tourAreaBased", "tourLocationBased", "tourDetail") + } + + // 매일 자정(00:00)마다 모든 캐시 항목을 무효화 + @CacheEvict(cacheNames = ["tourAreaBased", "tourLocationBased", "tourDetail"], allEntries = true) + @Scheduled(cron = "0 0 0 * * ?", zone = "Asia/Seoul") + fun clearTourCaches() { + log.info("clearTourCaches - evicting {}", TOUR_CACHE_NAMES.joinToString()) + } +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourAreaBasedServiceCore.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourAreaBasedServiceCore.kt index 85a3db1..45e6f47 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourAreaBasedServiceCore.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourAreaBasedServiceCore.kt @@ -15,6 +15,7 @@ class TourAreaBasedServiceCore( @Cacheable( "tourAreaBased", key = "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode", + unless = "#result == null", ) override fun fetchAreaBasedTours(tourParams: TourParams): TourResponse { if ( diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourDetailServiceCore.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourDetailServiceCore.kt index afe3bc8..6e7b23d 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourDetailServiceCore.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourDetailServiceCore.kt @@ -12,7 +12,7 @@ import org.springframework.stereotype.Service class TourDetailServiceCore( private val tourApiClient: TourApiClient, ) : TourDetailUseCase { - @Cacheable("tourDetail", key = "#detailParams.contentId") + @Cacheable("tourDetail", key = "#detailParams.contentId", unless = "#result == null") override fun fetchTourDetail(detailParams: TourDetailParams): TourDetailResponse { if (detailParams.contentId == "127974") { return PRESET_DETAIL_RESPONSE diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourLocationBasedServiceCore.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourLocationBasedServiceCore.kt index 9f71713..16bda55 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourLocationBasedServiceCore.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/core/TourLocationBasedServiceCore.kt @@ -18,6 +18,7 @@ class TourLocationBasedServiceCore( key = "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode + " + "'_' + #locationParams.mapX + '_' + #locationParams.mapY + '_' + #locationParams.radius", + unless = "#result == null", ) override fun fetchLocationBasedTours( tourParams: TourParams,