11package com.back.koreaTravelGuide.domain.weather.client
22
3- // TODO: 기상청 API 클라이언트 - HTTP 요청으로 날씨 데이터 조회 및 XML 파싱
3+ // TODO: 기상청 API 클라이언트 - HTTP 요청으로 날씨 데이터 조회 및 JSON 파싱
44import com.back.koreaTravelGuide.domain.weather.dto.*
55import org.springframework.beans.factory.annotation.Value
66import org.springframework.stereotype.Component
@@ -16,119 +16,142 @@ class WeatherApiClient(
1616 // 1. 중기전망조회 (getMidFcst) - 텍스트 기반 전망
1717 fun fetchMidForecast (regionId : String , baseTime : String ): String? {
1818 val stnId = getStnIdFromRegionCode(regionId)
19- val url = " $apiUrl /getMidFcst?serviceKey=$serviceKey &numOfRows=10&pageNo=1&stnId=$stnId &tmFc=$baseTime &dataType=XML "
19+ val url = " $apiUrl /getMidFcst?serviceKey=$serviceKey &numOfRows=10&pageNo=1&stnId=$stnId &tmFc=$baseTime &dataType=JSON "
2020
2121 println (" 🔮 중기전망조회 API 호출: $url " )
2222
2323 return try {
24- val xmlResponse = restTemplate.getForObject(url, String ::class .java)
25- println (" 📡 중기전망 응답 수신 (길이: ${xmlResponse?.length ? : 0 } )" )
24+ @Suppress(" UNCHECKED_CAST" )
25+ val jsonResponse = restTemplate.getForObject(url, Map ::class .java) as ? Map <String , Any >
26+ println (" 📡 중기전망 JSON 응답 수신" )
2627
27- // API 오류 응답 체크
28- xmlResponse?.let { response ->
29- if (response.contains(" <resultCode>03</resultCode>" ) || response.contains(" NO_DATA" )) {
28+ jsonResponse?.let { response ->
29+ // API 오류 응답 체크
30+ val resultCode = extractJsonValue(response, " response.header.resultCode" ) as ? String
31+ if (resultCode == " 03" || resultCode == " NO_DATA" ) {
3032 println (" ⚠️ 기상청 API NO_DATA 오류 - 발표시각을 조정해야 할 수 있습니다" )
3133 return null
3234 }
3335
34- val wfSvMatch = Regex (" <wfSv><!\\ [CDATA\\ [(.*?)]]></wfSv>" ).find(response)
35- wfSvMatch?.groupValues?.get(1 )?.trim()
36+ extractJsonValue(response, " response.body.items.item[0].wfSv" ) as ? String
3637 }
3738 } catch (e: Exception ) {
38- println (" ❌ 중기전망조회 API 오류: ${e.message} " )
39+ println (" ❌ 중기전망조회 JSON API 오류: ${e.message} " )
3940 null
4041 }
4142 }
4243
4344 // 2. 중기기온조회 (getMidTa) - 상세 기온 정보
4445 fun fetchTemperature (regionId : String , baseTime : String ): TemperatureData ? {
45- val url = " $apiUrl /getMidTa?serviceKey=$serviceKey &numOfRows=10&pageNo=1®Id=$regionId &tmFc=$baseTime &dataType=XML "
46+ val url = " $apiUrl /getMidTa?serviceKey=$serviceKey &numOfRows=10&pageNo=1®Id=$regionId &tmFc=$baseTime &dataType=JSON "
4647
4748 println (" 🌡️ 중기기온조회 API 호출: $url " )
4849
4950 return try {
50- val xmlResponse = restTemplate.getForObject(url, String ::class .java)
51- println (" 📡 중기기온 응답 수신 (길이: ${xmlResponse?.length ? : 0 } )" )
51+ @Suppress(" UNCHECKED_CAST" )
52+ val jsonResponse = restTemplate.getForObject(url, Map ::class .java) as ? Map <String , Any >
53+ println (" 📡 중기기온 JSON 응답 수신" )
5254
53- xmlResponse ?.let { parseTemperatureData (it) } ? : TemperatureData ()
55+ jsonResponse ?.let { parseTemperatureDataFromJson (it) } ? : TemperatureData ()
5456 } catch (e: Exception ) {
55- println (" ❌ 중기기온조회 API 오류: ${e.message} " )
57+ println (" ❌ 중기기온조회 JSON API 오류: ${e.message} " )
5658 TemperatureData ()
5759 }
5860 }
5961
6062 // 3. 중기육상예보조회 (getMidLandFcst) - 강수 확률
6163 fun fetchLandForecast (regionId : String , baseTime : String ): PrecipitationData ? {
62- val url = " $apiUrl /getMidLandFcst?serviceKey=$serviceKey &numOfRows=10&pageNo=1®Id=$regionId &tmFc=$baseTime &dataType=XML "
64+ val url = " $apiUrl /getMidLandFcst?serviceKey=$serviceKey &numOfRows=10&pageNo=1®Id=$regionId &tmFc=$baseTime &dataType=JSON "
6365
6466 println (" 🌧️ 중기육상예보조회 API 호출: $url " )
6567
6668 return try {
67- val xmlResponse = restTemplate.getForObject(url, String ::class .java)
68- println (" 📡 중기육상예보 응답 수신 (길이: ${xmlResponse?.length ? : 0 } )" )
69+ @Suppress(" UNCHECKED_CAST" )
70+ val jsonResponse = restTemplate.getForObject(url, Map ::class .java) as ? Map <String , Any >
71+ println (" 📡 중기육상예보 JSON 응답 수신" )
6972
70- xmlResponse ?.let { parsePrecipitationData (it) } ? : PrecipitationData ()
73+ jsonResponse ?.let { parsePrecipitationDataFromJson (it) } ? : PrecipitationData ()
7174 } catch (e: Exception ) {
72- println (" ❌ 중기육상예보조회 API 오류: ${e.message} " )
75+ println (" ❌ 중기육상예보조회 JSON API 오류: ${e.message} " )
7376 PrecipitationData ()
7477 }
7578 }
7679
77- // 기온 데이터 파싱
78- private fun parseTemperatureData ( xmlResponse : String ): TemperatureData {
80+ // 기온 데이터 JSON 파싱
81+ private fun parseTemperatureDataFromJson ( jsonResponse : Map < String , Any > ): TemperatureData {
7982 val temperatureData = TemperatureData ()
80-
83+
8184 for (day in 4 .. 10 ) {
82- val minTemp = extractXmlValue(xmlResponse , " taMin$day " )?.toIntOrNull ()
83- val maxTemp = extractXmlValue(xmlResponse , " taMax$day " )?.toIntOrNull ()
84- val minTempLow = extractXmlValue(xmlResponse , " taMin${day} Low" )?.toIntOrNull ()
85- val minTempHigh = extractXmlValue(xmlResponse , " taMin${day} High" )?.toIntOrNull ()
86- val maxTempLow = extractXmlValue(xmlResponse , " taMax${day} Low" )?.toIntOrNull ()
87- val maxTempHigh = extractXmlValue(xmlResponse , " taMax${day} High" )?.toIntOrNull ()
88-
85+ val minTemp = (extractJsonValue(jsonResponse , " response.body.items.item[0]. taMin$day " ) as ? Number )?.toInt ()
86+ val maxTemp = (extractJsonValue(jsonResponse , " response.body.items.item[0]. taMax$day " ) as ? Number )?.toInt ()
87+ val minTempLow = (extractJsonValue(jsonResponse , " response.body.items.item[0]. taMin${day} Low" ) as ? Number )?.toInt ()
88+ val minTempHigh = (extractJsonValue(jsonResponse , " response.body.items.item[0]. taMin${day} High" ) as ? Number )?.toInt ()
89+ val maxTempLow = (extractJsonValue(jsonResponse , " response.body.items.item[0]. taMax${day} Low" ) as ? Number )?.toInt ()
90+ val maxTempHigh = (extractJsonValue(jsonResponse , " response.body.items.item[0]. taMax${day} High" ) as ? Number )?.toInt ()
91+
8992 if (minTemp != null || maxTemp != null ) {
9093 val tempInfo = TemperatureInfo (
9194 minTemp = minTemp,
9295 maxTemp = maxTemp,
9396 minTempRange = if (minTempLow != null && minTempHigh != null ) " $minTempLow ~$minTempHigh ℃" else null ,
9497 maxTempRange = if (maxTempLow != null && maxTempHigh != null ) " $maxTempLow ~$maxTempHigh ℃" else null
9598 )
96-
99+
97100 temperatureData.setDay(day, tempInfo)
98101 }
99102 }
100-
103+
101104 return temperatureData
102105 }
103106
104- // 강수 확률 데이터 파싱
105- private fun parsePrecipitationData ( xmlResponse : String ): PrecipitationData {
107+ // 강수 확률 데이터 JSON 파싱
108+ private fun parsePrecipitationDataFromJson ( jsonResponse : Map < String , Any > ): PrecipitationData {
106109 val precipitationData = PrecipitationData ()
107-
110+
108111 for (day in 4 .. 10 ) {
109- val amRain = extractXmlValue(xmlResponse , " rnSt${day} Am" )?.toIntOrNull ()
110- val pmRain = extractXmlValue(xmlResponse , " rnSt${day} Pm" )?.toIntOrNull ()
111- val amWeather = extractXmlValue(xmlResponse , " wf${day} Am" )
112- val pmWeather = extractXmlValue(xmlResponse , " wf${day} Pm" )
113-
112+ val amRain = (extractJsonValue(jsonResponse , " response.body.items.item[0]. rnSt${day} Am" ) as ? Number )?.toInt ()
113+ val pmRain = (extractJsonValue(jsonResponse , " response.body.items.item[0]. rnSt${day} Pm" ) as ? Number )?.toInt ()
114+ val amWeather = extractJsonValue(jsonResponse , " response.body.items.item[0]. wf${day} Am" ) as ? String
115+ val pmWeather = extractJsonValue(jsonResponse , " response.body.items.item[0]. wf${day} Pm" ) as ? String
116+
114117 if (amRain != null || pmRain != null || ! amWeather.isNullOrBlank() || ! pmWeather.isNullOrBlank()) {
115118 val precipInfo = PrecipitationInfo (
116119 amRainPercent = amRain,
117120 pmRainPercent = pmRain,
118121 amWeather = amWeather,
119122 pmWeather = pmWeather
120123 )
121-
124+
122125 precipitationData.setDay(day, precipInfo)
123126 }
124127 }
125-
128+
126129 return precipitationData
127130 }
128131
129- private fun extractXmlValue (xmlResponse : String , tagName : String ): String? {
130- val regex = Regex (" <$tagName >(.*?)</$tagName >" )
131- return regex.find(xmlResponse)?.groupValues?.get(1 )?.trim()?.takeIf { it.isNotBlank() }
132+ // JSON에서 값 추출 ("response.body.items.item[0].wfSv" 같은 경로로)
133+ private fun extractJsonValue (jsonMap : Map <String , Any >, path : String ): Any? {
134+ var current: Any? = jsonMap
135+ val parts = path.split(" ." )
136+
137+ for (part in parts) {
138+ when {
139+ current == null -> return null
140+ part.contains(" [" ) && part.contains(" ]" ) -> {
141+ // 배열 인덱스 처리 (item[0] 같은 경우)
142+ val arrayName = part.substringBefore(" [" )
143+ val index = part.substringAfter(" [" ).substringBefore(" ]" ).toIntOrNull() ? : 0
144+
145+ current = (current as ? Map <* , * >)?.get(arrayName)
146+ current = (current as ? List <* >)?.getOrNull(index)
147+ }
148+ else -> {
149+ current = (current as ? Map <* , * >)?.get(part)
150+ }
151+ }
152+ }
153+
154+ return current
132155 }
133156
134157 private fun getStnIdFromRegionCode (regionCode : String ): String {
0 commit comments