Skip to content

Commit 18b9438

Browse files
authored
tourtool 수정 및 레디스 설정 전면 교체 (#90)
1 parent 8a5ddb7 commit 18b9438

File tree

5 files changed

+119
-36
lines changed

5 files changed

+119
-36
lines changed
Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.back.koreaTravelGuide.common.config
22

3+
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailResponse
4+
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse
5+
import com.back.koreaTravelGuide.domain.ai.weather.dto.MidForecastDto
6+
import com.back.koreaTravelGuide.domain.ai.weather.dto.TemperatureAndLandForecastDto
37
import com.fasterxml.jackson.databind.ObjectMapper
4-
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator
58
import com.fasterxml.jackson.module.kotlin.KotlinModule
69
import org.springframework.cache.CacheManager
710
import org.springframework.context.annotation.Bean
@@ -10,13 +13,20 @@ import org.springframework.data.redis.cache.RedisCacheConfiguration
1013
import org.springframework.data.redis.cache.RedisCacheManager
1114
import org.springframework.data.redis.connection.RedisConnectionFactory
1215
import org.springframework.data.redis.core.RedisTemplate
13-
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
16+
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
1417
import org.springframework.data.redis.serializer.RedisSerializationContext
1518
import org.springframework.data.redis.serializer.StringRedisSerializer
1619
import java.time.Duration
1720

1821
@Configuration
1922
class RedisConfig {
23+
@Bean
24+
fun objectMapper(): ObjectMapper =
25+
ObjectMapper().apply {
26+
// Kotlin 모듈 등록 (data class 생성자 인식)
27+
registerModule(KotlinModule.Builder().build())
28+
}
29+
2030
@Bean
2131
fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<String, String> {
2232
val template = RedisTemplate<String, String>()
@@ -33,35 +43,76 @@ class RedisConfig {
3343
}
3444

3545
@Bean
36-
fun cacheManager(connectionFactory: RedisConnectionFactory): CacheManager {
37-
// Kotlin data class 역직렬화 지원을 위한 ObjectMapper 생성
38-
val objectMapper =
39-
ObjectMapper().apply {
40-
// Kotlin 모듈 등록 (data class 생성자 인식)
41-
registerModule(KotlinModule.Builder().build())
42-
// 타입 정보 보존을 위한 검증기 설정
43-
activateDefaultTyping(
44-
BasicPolymorphicTypeValidator.builder()
45-
.allowIfBaseType(Any::class.java)
46-
.build(),
47-
ObjectMapper.DefaultTyping.NON_FINAL,
46+
fun cacheManager(
47+
connectionFactory: RedisConnectionFactory,
48+
objectMapper: ObjectMapper,
49+
): CacheManager {
50+
51+
// 각 캐시 타입별 Serializer 생성
52+
val tourResponseSerializer = Jackson2JsonRedisSerializer<TourResponse>(objectMapper, TourResponse::class.java)
53+
val tourDetailResponseSerializer = Jackson2JsonRedisSerializer<TourDetailResponse>(objectMapper, TourDetailResponse::class.java)
54+
55+
// List<MidForecastDto> 타입을 위한 JavaType 생성
56+
val midForecastListType =
57+
objectMapper.typeFactory.constructCollectionType(
58+
List::class.java,
59+
MidForecastDto::class.java,
60+
)
61+
val midForecastListSerializer = Jackson2JsonRedisSerializer<List<MidForecastDto>>(objectMapper, midForecastListType)
62+
63+
// List<TemperatureAndLandForecastDto> 타입을 위한 JavaType 생성
64+
val tempAndLandListType =
65+
objectMapper.typeFactory.constructCollectionType(
66+
List::class.java,
67+
TemperatureAndLandForecastDto::class.java,
68+
)
69+
val tempAndLandListSerializer = Jackson2JsonRedisSerializer<List<TemperatureAndLandForecastDto>>(objectMapper, tempAndLandListType)
70+
71+
// 공통 키 Serializer
72+
val keySerializer = RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer())
73+
74+
// Tour 관련 캐시 설정 (TourResponse 타입)
75+
val tourResponseConfig =
76+
RedisCacheConfiguration.defaultCacheConfig()
77+
.serializeKeysWith(keySerializer)
78+
.serializeValuesWith(
79+
RedisSerializationContext.SerializationPair.fromSerializer(tourResponseSerializer),
4880
)
49-
}
81+
.entryTtl(Duration.ofHours(12))
5082

51-
val redisCacheConfiguration =
83+
// Tour 상세 캐시 설정 (TourDetailResponse 타입)
84+
val tourDetailConfig =
5285
RedisCacheConfiguration.defaultCacheConfig()
53-
.serializeKeysWith(
54-
RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()),
86+
.serializeKeysWith(keySerializer)
87+
.serializeValuesWith(
88+
RedisSerializationContext.SerializationPair.fromSerializer(tourDetailResponseSerializer),
5589
)
90+
.entryTtl(Duration.ofHours(12))
91+
92+
// 중기예보 캐시 설정 (List<MidForecastDto> 타입)
93+
val midForecastConfig =
94+
RedisCacheConfiguration.defaultCacheConfig()
95+
.serializeKeysWith(keySerializer)
96+
.serializeValuesWith(
97+
RedisSerializationContext.SerializationPair.fromSerializer(midForecastListSerializer),
98+
)
99+
.entryTtl(Duration.ofHours(12))
100+
101+
// 기온/육상 예보 캐시 설정 (List<TemperatureAndLandForecastDto> 타입)
102+
val tempAndLandConfig =
103+
RedisCacheConfiguration.defaultCacheConfig()
104+
.serializeKeysWith(keySerializer)
56105
.serializeValuesWith(
57-
RedisSerializationContext.SerializationPair.fromSerializer(
58-
GenericJackson2JsonRedisSerializer(objectMapper),
59-
),
106+
RedisSerializationContext.SerializationPair.fromSerializer(tempAndLandListSerializer),
60107
)
61108
.entryTtl(Duration.ofHours(12))
62109

63110
return RedisCacheManager.builder(connectionFactory)
64-
.cacheDefaults(redisCacheConfiguration)
111+
.withCacheConfiguration("tourAreaBased", tourResponseConfig)
112+
.withCacheConfiguration("tourLocationBased", tourResponseConfig)
113+
.withCacheConfiguration("tourDetail", tourDetailConfig)
114+
.withCacheConfiguration("weatherMidFore", midForecastConfig)
115+
.withCacheConfiguration("weatherTempAndLandFore", tempAndLandConfig)
65116
.build()
66117
}
67118
}

src/main/kotlin/com/back/koreaTravelGuide/common/security/SecurityConfig.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class SecurityConfig(
2121
) {
2222
@Bean
2323
fun filterChain(http: HttpSecurity): SecurityFilterChain {
24-
val isDev = environment.activeProfiles.contains("dev")
24+
val isDev = environment.getProperty("spring.profiles.active")?.contains("dev") == true ||
25+
environment.activeProfiles.contains("dev")
2526

2627
http {
2728
csrf { disable() }

src/main/kotlin/com/back/koreaTravelGuide/domain/ai/aiChat/controller/AiChatController.kt

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.back.koreaTravelGuide.domain.ai.aiChat.controller
22

33
import com.back.koreaTravelGuide.common.ApiResponse
4+
import com.back.koreaTravelGuide.common.security.getUserId
45
import com.back.koreaTravelGuide.domain.ai.aiChat.dto.AiChatRequest
56
import com.back.koreaTravelGuide.domain.ai.aiChat.dto.AiChatResponse
67
import com.back.koreaTravelGuide.domain.ai.aiChat.dto.SessionMessagesResponse
@@ -9,14 +10,14 @@ import com.back.koreaTravelGuide.domain.ai.aiChat.dto.UpdateSessionTitleRequest
910
import com.back.koreaTravelGuide.domain.ai.aiChat.dto.UpdateSessionTitleResponse
1011
import com.back.koreaTravelGuide.domain.ai.aiChat.service.AiChatService
1112
import org.springframework.http.ResponseEntity
13+
import org.springframework.security.core.Authentication
1214
import org.springframework.web.bind.annotation.DeleteMapping
1315
import org.springframework.web.bind.annotation.GetMapping
1416
import org.springframework.web.bind.annotation.PatchMapping
1517
import org.springframework.web.bind.annotation.PathVariable
1618
import org.springframework.web.bind.annotation.PostMapping
1719
import org.springframework.web.bind.annotation.RequestBody
1820
import org.springframework.web.bind.annotation.RequestMapping
19-
import org.springframework.web.bind.annotation.RequestParam
2021
import org.springframework.web.bind.annotation.RestController
2122

2223
@RestController
@@ -26,8 +27,9 @@ class AiChatController(
2627
) {
2728
@GetMapping("/sessions")
2829
fun getSessions(
29-
@RequestParam userId: Long,
30+
authentication: Authentication?,
3031
): ResponseEntity<ApiResponse<List<SessionsResponse>>> {
32+
val userId = authentication?.getUserId() ?: 1L // dev 모드: 기본 userId=1
3133
val sessions =
3234
aiChatService.getSessions(userId).map {
3335
SessionsResponse.from(it)
@@ -37,8 +39,9 @@ class AiChatController(
3739

3840
@PostMapping("/sessions")
3941
fun createSession(
40-
@RequestParam userId: Long,
42+
authentication: Authentication?,
4143
): ResponseEntity<ApiResponse<SessionsResponse>> {
44+
val userId = authentication?.getUserId() ?: 1L // dev 모드: 기본 userId=1
4245
val session = aiChatService.createSession(userId)
4346
val response = SessionsResponse.from(session)
4447
return ResponseEntity.ok(ApiResponse("채팅방이 성공적으로 생성되었습니다.", response))
@@ -47,17 +50,19 @@ class AiChatController(
4750
@DeleteMapping("/sessions/{sessionId}")
4851
fun deleteSession(
4952
@PathVariable sessionId: Long,
50-
@RequestParam userId: Long,
53+
authentication: Authentication?,
5154
): ResponseEntity<ApiResponse<Unit>> {
55+
val userId = authentication?.getUserId() ?: 1L // dev 모드: 기본 userId=1
5256
aiChatService.deleteSession(sessionId, userId)
5357
return ResponseEntity.ok(ApiResponse("채팅방이 성공적으로 삭제되었습니다."))
5458
}
5559

5660
@GetMapping("/sessions/{sessionId}/messages")
5761
fun getSessionMessages(
5862
@PathVariable sessionId: Long,
59-
@RequestParam userId: Long,
63+
authentication: Authentication?,
6064
): ResponseEntity<ApiResponse<List<SessionMessagesResponse>>> {
65+
val userId = authentication?.getUserId() ?: 1L // dev 모드: 기본 userId=1
6166
val messages = aiChatService.getSessionMessages(sessionId, userId)
6267
val response =
6368
messages.map {
@@ -69,9 +74,10 @@ class AiChatController(
6974
@PostMapping("/sessions/{sessionId}/messages")
7075
fun sendMessage(
7176
@PathVariable sessionId: Long,
72-
@RequestParam userId: Long,
77+
authentication: Authentication?,
7378
@RequestBody request: AiChatRequest,
7479
): ResponseEntity<ApiResponse<AiChatResponse>> {
80+
val userId = authentication?.getUserId() ?: 1L // dev 모드: 기본 userId=1
7581
val (userMessage, aiMessage) = aiChatService.sendMessage(sessionId, userId, request.message)
7682
val response =
7783
AiChatResponse(
@@ -84,9 +90,10 @@ class AiChatController(
8490
@PatchMapping("/sessions/{sessionId}/title")
8591
fun updateSessionTitle(
8692
@PathVariable sessionId: Long,
87-
@RequestParam userId: Long,
93+
authentication: Authentication?,
8894
@RequestBody request: UpdateSessionTitleRequest,
8995
): ResponseEntity<ApiResponse<UpdateSessionTitleResponse>> {
96+
val userId = authentication?.getUserId() ?: 1L // dev 모드: 기본 userId=1
9097
val updatedSession = aiChatService.updateSessionTitle(sessionId, userId, request.newTitle)
9198
val response =
9299
UpdateSessionTitleResponse(

src/main/kotlin/com/back/koreaTravelGuide/domain/ai/aiChat/tool/TourTool.kt

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import com.back.koreaTravelGuide.common.logging.log
55
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailParams
66
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourLocationBasedParams
77
import com.back.koreaTravelGuide.domain.ai.tour.service.TourService
8+
import com.fasterxml.jackson.databind.ObjectMapper
89
import org.springframework.ai.tool.annotation.Tool
910
import org.springframework.ai.tool.annotation.ToolParam
1011
import org.springframework.stereotype.Component
1112

1213
@Component
1314
class TourTool(
1415
private val tourService: TourService,
16+
private val objectMapper: ObjectMapper,
1517
) {
1618
/**
1719
* fetchTours - 지역기반 관광정보 조회
@@ -47,8 +49,14 @@ class TourTool(
4749
val tourParams = tourService.parseParams(contentTypeId, areaAndSigunguCode)
4850
val tourInfo = tourService.fetchTours(tourParams)
4951

50-
log.info("✅ [TOOL RESULT] getAreaBasedTourInfo - 결과: ${tourInfo.toString().take(100)}...")
51-
return tourInfo.toString() ?: "지역기반 관광정보 조회를 가져올 수 없습니다."
52+
return try {
53+
val result = tourInfo.let { objectMapper.writeValueAsString(it) }
54+
log.info("✅ [TOOL RESULT] getAreaBasedTourInfo - 결과: ${result.take(100)}...")
55+
result
56+
} catch (e: Exception) {
57+
log.error("❌ [TOOL ERROR] getAreaBasedTourInfo - 예외 발생", e)
58+
"지역기반 관광정보 조회를 가져올 수 없습니다."
59+
}
5260
}
5361

5462
/**
@@ -98,8 +106,14 @@ class TourTool(
98106
val locationBasedParams = TourLocationBasedParams(mapX, mapY, radius)
99107
val tourLocationBasedInfo = tourService.fetchLocationBasedTours(tourParams, locationBasedParams)
100108

101-
log.info("✅ [TOOL RESULT] getLocationBasedTourInfo - 결과: ${tourLocationBasedInfo.toString().take(100)}...")
102-
return tourLocationBasedInfo.toString() ?: "위치기반 관광정보 조회를 가져올 수 없습니다."
109+
return try {
110+
val result = tourLocationBasedInfo.let { objectMapper.writeValueAsString(it) }
111+
log.info("✅ [TOOL RESULT] getLocationBasedTourInfo - 결과: ${result.take(100)}...")
112+
result
113+
} catch (e: Exception) {
114+
log.error("❌ [TOOL ERROR] getLocationBasedTourInfo - 예외 발생", e)
115+
"위치기반 관광정보 조회를 가져올 수 없습니다."
116+
}
103117
}
104118

105119
/**
@@ -123,7 +137,13 @@ class TourTool(
123137
val tourDetailParams = TourDetailParams(contentId)
124138
val tourDetailInfo = tourService.fetchTourDetail(tourDetailParams)
125139

126-
log.info("✅ [TOOL RESULT] getTourDetailInfo - 결과: ${tourDetailInfo.toString().take(100)}...")
127-
return tourDetailInfo.toString() ?: "관광정보 상세조회를 가져올 수 없습니다."
140+
return try {
141+
val result = tourDetailInfo.let { objectMapper.writeValueAsString(it) }
142+
log.info("✅ [TOOL RESULT] getTourDetailInfo - 결과: ${result.take(100)}...")
143+
result
144+
} catch (e: Exception) {
145+
log.error("❌ [TOOL ERROR] getTourDetailInfo - 예외 발생", e)
146+
"관광정보 상세조회를 가져올 수 없습니다."
147+
}
128148
}
129149
}

src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/dto/TourResponse.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.back.koreaTravelGuide.domain.ai.tour.dto
22

3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
35
/**
46
* 9.27 양현준
57
* 관광 정보 응답 DTO
@@ -41,7 +43,9 @@ data class TourItem(
4143
// 시군구코드
4244
val sigunguCode: String?,
4345
// 법정동 시도 코드
46+
@get:JsonProperty("lDongRegnCd")
4447
val lDongRegnCd: String?,
4548
// 법정동 시군구 코드
49+
@get:JsonProperty("lDongSignguCd")
4650
val lDongSignguCd: String?,
4751
)

0 commit comments

Comments
 (0)