1+ package nexters.weski.batch
2+
3+ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
4+ import jakarta.transaction.Transactional
5+ import nexters.weski.ski_resort.SkiResort
6+ import nexters.weski.ski_resort.SkiResortRepository
7+ import nexters.weski.weather.CurrentWeather
8+ import nexters.weski.weather.CurrentWeatherRepository
9+ import org.springframework.beans.factory.annotation.Value
10+ import org.springframework.stereotype.Service
11+ import org.springframework.web.client.RestTemplate
12+ import java.time.LocalDateTime
13+ import java.time.format.DateTimeFormatter
14+ import kotlin.math.pow
15+
16+ @Service
17+ class ExternalWeatherService (
18+ private val currentWeatherRepository : CurrentWeatherRepository ,
19+ private val skiResortRepository : SkiResortRepository
20+ ) {
21+ @Value(" \$ {weather.api.key}" )
22+ lateinit var apiKey: String
23+
24+ val restTemplate = RestTemplate ()
25+ val objectMapper = jacksonObjectMapper()
26+
27+ @Transactional
28+ fun updateCurrentWeather () {
29+ val baseDate = LocalDateTime .now().format(DateTimeFormatter .ofPattern(" yyyyMMdd" ))
30+ val baseTime = getBaseTime()
31+ skiResortRepository.findAll().forEach { resort ->
32+ val url = " https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst" +
33+ " ?serviceKey=$apiKey " +
34+ " &pageNo=1" +
35+ " &numOfRows=1000" +
36+ " &dataType=JSON" +
37+ " &base_date=$baseDate " +
38+ " &base_time=$baseTime " +
39+ " &nx=${resort.xCoordinate} " +
40+ " &ny=${resort.yCoordinate} "
41+ val response = restTemplate.getForObject(url, String ::class .java)
42+ val weatherData = parseWeatherData(response)
43+ val newCurrentWeather = mapToCurrentWeather(weatherData, resort)
44+
45+ // 기존 데이터 조회
46+ val existingWeather = currentWeatherRepository.findBySkiResortResortId(resort.resortId)
47+
48+ if (existingWeather != null ) {
49+ // 기존 데이터의 ID를 사용하여 새로운 엔티티 생성
50+ val updatedWeather = newCurrentWeather.copy(id = existingWeather.id)
51+ currentWeatherRepository.save(updatedWeather)
52+ } else {
53+ // 새로운 데이터 삽입
54+ currentWeatherRepository.save(newCurrentWeather)
55+ }
56+ }
57+ }
58+
59+ private fun getBaseTime (): String {
60+ val now = LocalDateTime .now().minusHours(1 )
61+ val hour = now.hour.toString().padStart(2 , ' 0' )
62+ return " ${hour} 00"
63+ }
64+
65+ private fun parseWeatherData (response : String? ): Map <String , String > {
66+ val data = mutableMapOf<String , String >()
67+
68+ response?.let {
69+ val rootNode = objectMapper.readTree(it)
70+ val items = rootNode[" response" ][" body" ][" items" ][" item" ]
71+
72+ items.forEach { item ->
73+ val category = item[" category" ].asText()
74+ val value = item[" obsrValue" ].asText()
75+ data[category] = value
76+ }
77+ }
78+
79+ return data
80+ }
81+
82+ private fun mapToCurrentWeather (
83+ data : Map <String , String >,
84+ resort : SkiResort
85+ ): CurrentWeather {
86+ val temperature = data[" T1H" ]?.toDoubleOrNull()?.toInt() ? : 0
87+ val windSpeed = data[" WSD" ]?.toDoubleOrNull() ? : 0.0
88+ val feelsLike = calculateFeelsLike(temperature, windSpeed)
89+ val condition = determineCondition(data)
90+ val description = generateDescription(condition, temperature)
91+
92+ return CurrentWeather (
93+ temperature = temperature,
94+ maxTemp = data[" TMX" ]?.toDoubleOrNull()?.toInt() ? : temperature,
95+ minTemp = data[" TMN" ]?.toDoubleOrNull()?.toInt() ? : temperature,
96+ feelsLike = feelsLike,
97+ condition = condition,
98+ description = description,
99+ skiResort = resort
100+ )
101+ }
102+
103+ private fun calculateFeelsLike (temperature : Int , windSpeed : Double ): Int {
104+ return if (temperature <= 10 && windSpeed >= 4.8 ) {
105+ val feelsLike =
106+ 13.12 + 0.6215 * temperature - 11.37 * windSpeed.pow(0.16 ) + 0.3965 * temperature * windSpeed.pow(
107+ 0.16
108+ )
109+ feelsLike.toInt()
110+ } else {
111+ temperature
112+ }
113+ }
114+
115+ private fun determineCondition (data : Map <String , String >): String {
116+ val pty = data[" PTY" ]?.toIntOrNull() ? : 0
117+ val sky = data[" SKY" ]?.toIntOrNull() ? : 1
118+
119+ return when {
120+ pty == 1 || pty == 4 -> " 비"
121+ pty == 2 -> " 비/눈"
122+ pty == 3 -> " 눈"
123+ sky == 1 -> " 맑음"
124+ sky == 3 -> " 구름많음"
125+ sky == 4 -> " 흐림"
126+ else -> " 맑음"
127+ }
128+ }
129+
130+ private fun generateDescription (condition : String , temperature : Int ): String {
131+ val prefix = when (condition) {
132+ " 맑음" -> " 화창하고"
133+ " 구름많음" -> " 구름이 많고"
134+ " 흐림" -> " 흐리고"
135+ " 비" -> " 비가 오고"
136+ " 비/눈" -> " 눈비가 내리고"
137+ " 눈" -> " 눈이 오고"
138+ else -> " "
139+ }
140+
141+ val postfix = when {
142+ temperature <= - 15 -> " 매우 추워요"
143+ temperature in - 14 .. - 10 -> " 다소 추워요"
144+ temperature in - 9 .. - 5 -> " 적당한 온도에요"
145+ temperature in - 4 .. 0 -> " 조금 따뜻해요"
146+ temperature in 1 .. 5 -> " 따뜻해요"
147+ temperature in 6 .. 10 -> " 다소 더워요"
148+ else -> " 더워요"
149+ }
150+
151+ return " $prefix $postfix "
152+ }
153+ }
0 commit comments