11package com.back.koreaTravelGuide.domain.ai.weather.client
22
33// TODO: 기상청 API 클라이언트 - HTTP 요청으로 날씨 데이터 조회 및 JSON 파싱
4- import com.back.koreaTravelGuide.domain.ai.weather.dto.TemperatureDto
5- import com.back.koreaTravelGuide.domain.ai.weather.dto.remove.PrecipitationData
6- import com.back.koreaTravelGuide.domain.ai.weather.dto.remove.PrecipitationInfo
7- import com.back.koreaTravelGuide.domain.ai.weather.dto.remove.TemperatureData
8- import com.back.koreaTravelGuide.domain.ai.weather.dto.remove.TemperatureInfo
4+ import com.back.koreaTravelGuide.domain.ai.weather.client.parser.DataParser
5+ import com.back.koreaTravelGuide.domain.ai.weather.client.tools.Tools
6+ import com.back.koreaTravelGuide.domain.ai.weather.dto.LandForecastData
7+ import com.back.koreaTravelGuide.domain.ai.weather.dto.TemperatureData
98import org.springframework.beans.factory.annotation.Value
109import org.springframework.stereotype.Component
1110import org.springframework.web.client.RestTemplate
1211
1312@Component
1413class WeatherApiClient (
1514 private val restTemplate : RestTemplate ,
15+ private val tools : Tools ,
16+ private val dataParser : DataParser ,
1617 @Value(" \$ {weather.api.key}" ) private val serviceKey : String ,
1718 @Value(" \$ {weather.api.base-url}" ) private val apiUrl : String ,
1819) {
@@ -21,7 +22,7 @@ class WeatherApiClient(
2122 regionId : String ,
2223 baseTime : String ,
2324 ): String? {
24- val stnId = getStnIdFromRegionCode(regionId)
25+ val stnId = tools. getStnIdFromRegionCode(regionId)
2526 val url = " $apiUrl /getMidFcst?serviceKey=$serviceKey &numOfRows=10&pageNo=1&stnId=$stnId &tmFc=$baseTime &dataType=JSON"
2627
2728 println (" 🔮 중기전망조회 API 호출: $url " )
@@ -33,13 +34,13 @@ class WeatherApiClient(
3334
3435 jsonResponse?.let { response ->
3536 // API 오류 응답 체크
36- val resultCode = extractJsonValue(response, " response.header.resultCode" ) as ? String
37+ val resultCode = dataParser. extractJsonValue(response, " response.header.resultCode" ) as ? String
3738 if (resultCode == " 03" || resultCode == " NO_DATA" ) {
3839 println (" ⚠️ 기상청 API NO_DATA 오류 - 발표시각을 조정해야 할 수 있습니다" )
3940 return null
4041 }
4142
42- extractJsonValue(response, " response.body.items.item[0].wfSv" ) as ? String
43+ dataParser. extractJsonValue(response, " response.body.items.item[0].wfSv" ) as ? String
4344 }
4445 } catch (e: Exception ) {
4546 println (" ❌ 중기전망조회 JSON API 오류: ${e.message} " )
@@ -51,7 +52,7 @@ class WeatherApiClient(
5152 fun fetchTemperature (
5253 regionId : String ,
5354 baseTime : String ,
54- ): TemperatureDto ? {
55+ ): TemperatureData ? {
5556 val url = " $apiUrl /getMidTa?serviceKey=$serviceKey &numOfRows=10&pageNo=1®Id=$regionId &tmFc=$baseTime &dataType=JSON"
5657
5758 println (" 🌡️ 중기기온조회 API 호출: $url " )
@@ -61,10 +62,10 @@ class WeatherApiClient(
6162 val jsonResponse = restTemplate.getForObject(url, Map ::class .java) as ? Map <String , Any >
6263 println (" 📡 중기기온 JSON 응답 수신" )
6364
64- jsonResponse?.let { parseTemperatureDataFromJson(it) } ? : TemperatureDto ()
65+ jsonResponse?.let { dataParser. parseTemperatureDataFromJson(it) } ? : TemperatureData ()
6566 } catch (e: Exception ) {
6667 println (" ❌ 중기기온조회 JSON API 오류: ${e.message} " )
67- TemperatureDto ()
68+ TemperatureData ()
6869 }
6970 }
7071
@@ -82,127 +83,10 @@ class WeatherApiClient(
8283 val jsonResponse = restTemplate.getForObject(url, Map ::class .java) as ? Map <String , Any >
8384 println (" 📡 중기육상예보 JSON 응답 수신" )
8485
85- jsonResponse?.let { parsePrecipitationDataFromJson(it) } ? : PrecipitationData ()
86+ jsonResponse?.let { dataParser. parsePrecipitationDataFromJson(it) } ? : PrecipitationData ()
8687 } catch (e: Exception ) {
8788 println (" ❌ 중기육상예보조회 JSON API 오류: ${e.message} " )
8889 PrecipitationData ()
8990 }
9091 }
91-
92- // 기온 데이터 JSON 파싱
93- private fun parseTemperatureDataFromJson (jsonResponse : Map <String , Any >): TemperatureData {
94- val temperatureData = TemperatureData ()
95-
96- for (day in 4 .. 10 ) {
97- val minTemp = (extractJsonValue(jsonResponse, " response.body.items.item[0].taMin$day " ) as ? Number )?.toInt()
98- val maxTemp = (extractJsonValue(jsonResponse, " response.body.items.item[0].taMax$day " ) as ? Number )?.toInt()
99- val minTempLow = (extractJsonValue(jsonResponse, " response.body.items.item[0].taMin${day} Low" ) as ? Number )?.toInt()
100- val minTempHigh = (extractJsonValue(jsonResponse, " response.body.items.item[0].taMin${day} High" ) as ? Number )?.toInt()
101- val maxTempLow = (extractJsonValue(jsonResponse, " response.body.items.item[0].taMax${day} Low" ) as ? Number )?.toInt()
102- val maxTempHigh = (extractJsonValue(jsonResponse, " response.body.items.item[0].taMax${day} High" ) as ? Number )?.toInt()
103-
104- if (minTemp != null || maxTemp != null ) {
105- val tempInfo =
106- TemperatureInfo (
107- minTemp = minTemp,
108- maxTemp = maxTemp,
109- minTempRange = if (minTempLow != null && minTempHigh != null ) " $minTempLow ~$minTempHigh ℃" else null ,
110- maxTempRange = if (maxTempLow != null && maxTempHigh != null ) " $maxTempLow ~$maxTempHigh ℃" else null ,
111- )
112-
113- temperatureData.setDay(day, tempInfo)
114- }
115- }
116-
117- return temperatureData
118- }
119-
120- // 강수 확률 데이터 JSON 파싱
121- private fun parsePrecipitationDataFromJson (jsonResponse : Map <String , Any >): PrecipitationData {
122- val precipitationData = PrecipitationData ()
123-
124- for (day in 4 .. 10 ) {
125- if (day <= 7 ) {
126- // 4~7일: 오전/오후 구분
127- val amRain = (extractJsonValue(jsonResponse, " response.body.items.item[0].rnSt${day} Am" ) as ? Number )?.toInt()
128- val pmRain = (extractJsonValue(jsonResponse, " response.body.items.item[0].rnSt${day} Pm" ) as ? Number )?.toInt()
129- val amWeather = extractJsonValue(jsonResponse, " response.body.items.item[0].wf${day} Am" ) as ? String
130- val pmWeather = extractJsonValue(jsonResponse, " response.body.items.item[0].wf${day} Pm" ) as ? String
131-
132- if (amRain != null || pmRain != null || ! amWeather.isNullOrBlank() || ! pmWeather.isNullOrBlank()) {
133- val precipInfo =
134- PrecipitationInfo (
135- amRainPercent = amRain,
136- pmRainPercent = pmRain,
137- amWeather = amWeather,
138- pmWeather = pmWeather,
139- )
140-
141- precipitationData.setDay(day, precipInfo)
142- }
143- } else {
144- // 8~10일: 통합 (오전/오후 구분 없음)
145- val rainPercent = (extractJsonValue(jsonResponse, " response.body.items.item[0].rnSt$day " ) as ? Number )?.toInt()
146- val weather = extractJsonValue(jsonResponse, " response.body.items.item[0].wf$day " ) as ? String
147-
148- if (rainPercent != null || ! weather.isNullOrBlank()) {
149- val precipInfo =
150- PrecipitationInfo (
151- amRainPercent = rainPercent,
152- pmRainPercent = null ,
153- amWeather = weather,
154- pmWeather = null ,
155- )
156-
157- precipitationData.setDay(day, precipInfo)
158- }
159- }
160- }
161-
162- return precipitationData
163- }
164-
165- // JSON에서 값 추출 ("response.body.items.item[0].wfSv" 같은 경로로)
166- private fun extractJsonValue (
167- jsonMap : Map <String , Any >,
168- path : String ,
169- ): Any? {
170- var current: Any? = jsonMap
171- val parts = path.split(" ." )
172-
173- for (part in parts) {
174- when {
175- current == null -> return null
176- part.contains(" [" ) && part.contains(" ]" ) -> {
177- // 배열 인덱스 처리 (item[0] 같은 경우)
178- val arrayName = part.substringBefore(" [" )
179- val index = part.substringAfter(" [" ).substringBefore(" ]" ).toIntOrNull() ? : 0
180-
181- current = (current as ? Map <* , * >)?.get(arrayName)
182- current = (current as ? List <* >)?.getOrNull(index)
183- }
184- else -> {
185- current = (current as ? Map <* , * >)?.get(part)
186- }
187- }
188- }
189-
190- return current
191- }
192-
193- private fun getStnIdFromRegionCode (regionCode : String ): String {
194- return when {
195- regionCode.startsWith(" 11B" ) -> " 109" // 서울,인천,경기도
196- regionCode.startsWith(" 11D1" ) -> " 105" // 강원도영서
197- regionCode.startsWith(" 11D2" ) -> " 105" // 강원도영동
198- regionCode.startsWith(" 11C2" ) -> " 133" // 대전,세종,충청남도
199- regionCode.startsWith(" 11C1" ) -> " 131" // 충청북도
200- regionCode.startsWith(" 11F2" ) -> " 156" // 광주,전라남도
201- regionCode.startsWith(" 11F1" ) -> " 146" // 전북자치도
202- regionCode.startsWith(" 11H1" ) -> " 143" // 대구,경상북도
203- regionCode.startsWith(" 11H2" ) -> " 159" // 부산,울산,경상남도
204- regionCode.startsWith(" 11G" ) -> " 184" // 제주도
205- else -> " 108" // 전국
206- }
207- }
20892}
0 commit comments