From 3a7721ade4aeb1c7ce2bb60455315a632f557827 Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Fri, 10 Oct 2025 11:21:42 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20RedisConfig=20merge=20=EC=B6=A9?= =?UTF-8?q?=EB=8F=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/RedisConfig.kt | 30 ------------------- .../user/dto/request/GuideUpdateRequest.kt | 4 +-- .../domain/user/dto/response/GuideResponse.kt | 3 +- .../domain/user/entity/User.kt | 3 +- .../controller/ChatMessageController.kt | 23 ++++++++++++-- .../chatroom/controller/ChatRoomController.kt | 24 ++++++++++++--- 6 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/main/kotlin/com/back/koreaTravelGuide/common/config/RedisConfig.kt b/src/main/kotlin/com/back/koreaTravelGuide/common/config/RedisConfig.kt index d6c25c6..b740801 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/common/config/RedisConfig.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/common/config/RedisConfig.kt @@ -6,16 +6,12 @@ import com.back.koreaTravelGuide.domain.ai.weather.dto.MidForecastDto import com.back.koreaTravelGuide.domain.ai.weather.dto.TemperatureAndLandForecastDto import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.cache.CacheManager import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.data.redis.cache.RedisCacheConfiguration import org.springframework.data.redis.cache.RedisCacheManager import org.springframework.data.redis.connection.RedisConnectionFactory -import org.springframework.data.redis.connection.RedisStandaloneConfiguration -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory import org.springframework.data.redis.core.RedisTemplate import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer import org.springframework.data.redis.serializer.RedisSerializationContext @@ -24,27 +20,6 @@ import java.time.Duration @Configuration class RedisConfig { - @Value("\${spring.data.redis.host:localhost}") - private lateinit var redisHost: String - - @Value("\${spring.data.redis.port:6379}") - private var redisPort: Int = 6379 - - @Value("\${spring.data.redis.password:}") - private var redisPassword: String = "" - - @Bean - @ConditionalOnMissingBean - fun redisConnectionFactory(): RedisConnectionFactory { - val redisConfiguration = RedisStandaloneConfiguration() - redisConfiguration.hostName = redisHost - redisConfiguration.port = redisPort - if (redisPassword.isNotEmpty()) { - redisConfiguration.setPassword(redisPassword) - } - return LettuceConnectionFactory(redisConfiguration) - } - @Bean fun objectMapper(): ObjectMapper = ObjectMapper().apply { @@ -55,15 +30,10 @@ class RedisConfig { @Bean fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate { val template = RedisTemplate() - template.connectionFactory = connectionFactory - // Key와 Value의 Serializer를 String으로 설정 - template.keySerializer = StringRedisSerializer() - template.valueSerializer = StringRedisSerializer() - return template } diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/request/GuideUpdateRequest.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/request/GuideUpdateRequest.kt index 4e6ab34..11c5200 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/request/GuideUpdateRequest.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/request/GuideUpdateRequest.kt @@ -1,10 +1,8 @@ package com.back.koreaTravelGuide.domain.user.dto.request -import com.back.koreaTravelGuide.domain.user.enums.Region - data class GuideUpdateRequest( val nickname: String?, val profileImageUrl: String?, - val location: Region?, + val location: String?, val description: String?, ) diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/response/GuideResponse.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/response/GuideResponse.kt index a90608a..c6e9b2e 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/response/GuideResponse.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/dto/response/GuideResponse.kt @@ -1,7 +1,6 @@ package com.back.koreaTravelGuide.domain.user.dto.response import com.back.koreaTravelGuide.domain.user.entity.User -import com.back.koreaTravelGuide.domain.user.enums.Region import com.back.koreaTravelGuide.domain.user.enums.UserRole data class GuideResponse( @@ -10,7 +9,7 @@ data class GuideResponse( val nickname: String, val profileImageUrl: String?, val role: UserRole, - val location: Region?, + val location: String?, val description: String?, ) { companion object { diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/entity/User.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/entity/User.kt index 0f41253..dd05038 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/entity/User.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/entity/User.kt @@ -1,6 +1,5 @@ package com.back.koreaTravelGuide.domain.user.entity -import com.back.koreaTravelGuide.domain.user.enums.Region import com.back.koreaTravelGuide.domain.user.enums.UserRole import jakarta.persistence.Column import jakarta.persistence.Entity @@ -39,7 +38,7 @@ class User( var role: UserRole = UserRole.USER, @Enumerated(EnumType.STRING) @Column - var location: Region? = null, + var location: String? = null, @Column(columnDefinition = "TEXT") var description: String? = null, @CreatedDate diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatmessage/controller/ChatMessageController.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatmessage/controller/ChatMessageController.kt index 500ddef..1800611 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatmessage/controller/ChatMessageController.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatmessage/controller/ChatMessageController.kt @@ -4,6 +4,8 @@ import com.back.koreaTravelGuide.common.ApiResponse import com.back.koreaTravelGuide.domain.userChat.chatmessage.dto.ChatMessageResponse import com.back.koreaTravelGuide.domain.userChat.chatmessage.dto.ChatMessageSendRequest import com.back.koreaTravelGuide.domain.userChat.chatmessage.service.ChatMessageService +import org.springframework.core.env.Environment +import org.springframework.core.env.Profiles import org.springframework.http.ResponseEntity import org.springframework.messaging.simp.SimpMessagingTemplate import org.springframework.security.access.AccessDeniedException @@ -12,6 +14,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestHeader import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @@ -21,15 +24,30 @@ import org.springframework.web.bind.annotation.RestController class ChatMessageController( private val messageService: ChatMessageService, private val messagingTemplate: SimpMessagingTemplate, + private val environment: Environment, ) { + // 오류 해결을 위해 수정: dev 환경에서 테스트용 헤더로 인증 대체 + private fun resolveMemberId( + principalId: Long?, + fallbackId: Long?, + ): Long { + return principalId + ?: if (environment.acceptsProfiles(Profiles.of("dev")) && fallbackId != null) { + fallbackId + } else { + throw AccessDeniedException("인증이 필요합니다.") + } + } + @GetMapping("/{roomId}/messages") fun listMessages( @PathVariable roomId: Long, @AuthenticationPrincipal requesterId: Long?, + @RequestHeader("X-Dev-Member-Id", required = false) devMemberId: Long?, @RequestParam(required = false) after: Long?, @RequestParam(defaultValue = "50") limit: Int, ): ResponseEntity>> { - val memberId = requesterId ?: throw AccessDeniedException("인증이 필요합니다.") + val memberId = resolveMemberId(requesterId, devMemberId) val messages = if (after == null) { messageService.getlistbefore(roomId, limit, memberId) @@ -44,9 +62,10 @@ class ChatMessageController( fun sendMessage( @PathVariable roomId: Long, @AuthenticationPrincipal senderId: Long?, + @RequestHeader("X-Dev-Member-Id", required = false) devMemberId: Long?, @RequestBody req: ChatMessageSendRequest, ): ResponseEntity> { - val memberId = senderId ?: throw AccessDeniedException("인증이 필요합니다.") + val memberId = resolveMemberId(senderId, devMemberId) val saved = messageService.send(roomId, memberId, req.content) val response = ChatMessageResponse.from(saved) messagingTemplate.convertAndSend( diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/controller/ChatRoomController.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/controller/ChatRoomController.kt index 5d51364..74d376b 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/controller/ChatRoomController.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/controller/ChatRoomController.kt @@ -5,6 +5,8 @@ import com.back.koreaTravelGuide.domain.userChat.chatroom.dto.ChatRoomListRespon import com.back.koreaTravelGuide.domain.userChat.chatroom.dto.ChatRoomResponse import com.back.koreaTravelGuide.domain.userChat.chatroom.dto.ChatRoomStartRequest import com.back.koreaTravelGuide.domain.userChat.chatroom.service.ChatRoomService +import org.springframework.core.env.Environment +import org.springframework.core.env.Profiles import org.springframework.http.ResponseEntity import org.springframework.security.access.AccessDeniedException import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -21,14 +23,28 @@ import org.springframework.web.bind.annotation.RestController @RequestMapping("/api/userchat/rooms") class ChatRoomController( private val roomService: ChatRoomService, + private val environment: Environment, ) { + // 오류 해결을 위해 수정: dev 프로필에서는 인증 누락 시 요청값을 대신 사용 + private fun requireAuthenticatedId( + requesterId: Long?, + fallbackId: Long? = null, + ): Long { + return requesterId + ?: if (environment.acceptsProfiles(Profiles.of("dev")) && fallbackId != null) { + fallbackId + } else { + throw AccessDeniedException("인증이 필요합니다.") + } + } + @GetMapping fun listRooms( @AuthenticationPrincipal requesterId: Long?, @RequestParam(required = false, defaultValue = "20") limit: Int, @RequestParam(required = false) cursor: String?, ): ResponseEntity> { - val authenticatedId = requesterId ?: throw AccessDeniedException("인증이 필요합니다.") + val authenticatedId = requireAuthenticatedId(requesterId) val safeLimit = limit.coerceIn(1, 100) val response = roomService.listRooms(authenticatedId, safeLimit, cursor) return ResponseEntity.ok(ApiResponse(msg = "채팅방 목록 조회", data = response)) @@ -40,7 +56,7 @@ class ChatRoomController( @AuthenticationPrincipal requesterId: Long?, @RequestBody req: ChatRoomStartRequest, ): ResponseEntity> { - val authenticatedId = requesterId ?: throw AccessDeniedException("인증이 필요합니다.") + val authenticatedId = requireAuthenticatedId(requesterId, req.userId) val room = roomService.createOneToOneRoom(req.guideId, req.userId, authenticatedId) return ResponseEntity.ok(ApiResponse(msg = "채팅방 시작", data = ChatRoomResponse.from(room))) } @@ -50,7 +66,7 @@ class ChatRoomController( @PathVariable roomId: Long, @AuthenticationPrincipal requesterId: Long?, ): ResponseEntity> { - val authenticatedId = requesterId ?: throw AccessDeniedException("인증이 필요합니다.") + val authenticatedId = requireAuthenticatedId(requesterId) roomService.deleteByOwner(roomId, authenticatedId) return ResponseEntity.ok(ApiResponse("채팅방 삭제 완료")) } @@ -60,7 +76,7 @@ class ChatRoomController( @PathVariable roomId: Long, @AuthenticationPrincipal requesterId: Long?, ): ResponseEntity> { - val authenticatedId = requesterId ?: throw AccessDeniedException("인증이 필요합니다.") + val authenticatedId = requireAuthenticatedId(requesterId) val room = roomService.get(roomId, authenticatedId) return ResponseEntity.ok(ApiResponse(msg = "채팅방 조회", data = ChatRoomResponse.from(room))) } From f39566ffc901c9655f6f9c5213c742e866b1e9ee Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Fri, 10 Oct 2025 16:06:50 +0900 Subject: [PATCH 2/5] =?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 4b264ef0252e7d9fe98a76c68357c8a8cb316728 Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Fri, 10 Oct 2025 16:45:00 +0900 Subject: [PATCH 3/5] =?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 6ed97e56406e536637fd646a50987bd188359e1d Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Fri, 10 Oct 2025 18:06:19 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat(be)=20:=20tour=20=EC=BA=90=EC=8B=9C=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # src/main/kotlin/com/back/koreaTravelGuide/KoreaTravelGuideApplication.kt --- .../KoreaTravelGuideApplication.kt | 3 +++ .../domain/ai/tour/cache/TourCacheConfig.kt | 20 +++++++++++++++++-- .../service/core/TourAreaBasedServiceCore.kt | 1 + .../service/core/TourDetailServiceCore.kt | 2 +- .../core/TourLocationBasedServiceCore.kt | 1 + 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/back/koreaTravelGuide/KoreaTravelGuideApplication.kt b/src/main/kotlin/com/back/koreaTravelGuide/KoreaTravelGuideApplication.kt index 008dfc6..d4f87ef 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/KoreaTravelGuideApplication.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/KoreaTravelGuideApplication.kt @@ -5,9 +5,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration import org.springframework.boot.runApplication import org.springframework.cache.annotation.EnableCaching +import org.springframework.scheduling.annotation.EnableScheduling import org.springframework.data.jpa.repository.config.EnableJpaRepositories @EnableCaching +@EnableScheduling +@SpringBootApplication(scanBasePackages = ["com.back.koreaTravelGuide"]) @EnableJpaRepositories(basePackages = ["com.back.koreaTravelGuide.domain"]) @SpringBootApplication( scanBasePackages = ["com.back.koreaTravelGuide"], 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, From 657b92bfda590fee1657fb078af94c92043b2429 Mon Sep 17 00:00:00 2001 From: YangHJ2415 Date: Fri, 10 Oct 2025 18:34:50 +0900 Subject: [PATCH 5/5] fix(be) : rebase --- .../back/koreaTravelGuide/KoreaTravelGuideApplication.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/back/koreaTravelGuide/KoreaTravelGuideApplication.kt b/src/main/kotlin/com/back/koreaTravelGuide/KoreaTravelGuideApplication.kt index d4f87ef..f3765e4 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/KoreaTravelGuideApplication.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/KoreaTravelGuideApplication.kt @@ -2,20 +2,15 @@ package com.back.koreaTravelGuide import io.github.cdimascio.dotenv.dotenv import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration import org.springframework.boot.runApplication import org.springframework.cache.annotation.EnableCaching -import org.springframework.scheduling.annotation.EnableScheduling import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.scheduling.annotation.EnableScheduling @EnableCaching @EnableScheduling -@SpringBootApplication(scanBasePackages = ["com.back.koreaTravelGuide"]) @EnableJpaRepositories(basePackages = ["com.back.koreaTravelGuide.domain"]) -@SpringBootApplication( - scanBasePackages = ["com.back.koreaTravelGuide"], - exclude = [RedisRepositoriesAutoConfiguration::class], -) +@SpringBootApplication(scanBasePackages = ["com.back.koreaTravelGuide"]) class KoreaTravelGuideApplication fun main(args: Array) {