@@ -45,70 +45,85 @@ public class WeatherService {
4545
4646 private static final ZoneId KST = ZoneId .of ("Asia/Seoul" );
4747 private static final DateTimeFormatter HOUR_FMT = DateTimeFormatter .ofPattern ("a h시" , Locale .KOREAN );
48+ private static final String CACHE_KEY_PREFIX = "weather:gumi:basic:" ;
49+ private static final String API_EXCLUDE_PARAMS = "minutely,alerts" ;
50+ private static final int SKIP_CURRENT_HOUR = 1 ;
51+ private static final int HOURLY_FORECAST_COUNT = 6 ;
52+ private static final int CACHE_BUFFER_SECONDS = 5 ;
4853
54+ /*
55+ 현재 시간을 기반으로 Redis 키 생성
56+ */
4957 private String cacheKey () {
5058 ZonedDateTime hour = ZonedDateTime .now (KST ).truncatedTo (ChronoUnit .HOURS );
51- return "weather:gumi:basic:" + hour .toEpochSecond ();
59+ return CACHE_KEY_PREFIX + hour .toEpochSecond ();
5260 }
61+
5362 private Duration ttlUntilNextHour () {
5463 ZonedDateTime now = ZonedDateTime .now (KST );
55- return Duration .between (now , now .truncatedTo (ChronoUnit .HOURS ).plusHours (1 )).plusSeconds (5 );
64+ return Duration .between (now , now .truncatedTo (ChronoUnit .HOURS ).plusHours (1 )).plusSeconds (CACHE_BUFFER_SECONDS );
5665 }
5766
5867 public WeatherBasic oneCall () {
5968 String key = cacheKey ();
69+ // 캐시된 날씨 데이터가 있으면 바로 가져오기
6070 Object hit = redis .opsForValue ().get (key );
61- if (hit instanceof WeatherBasic cached ) {
71+ if (hit instanceof WeatherBasic cached ) {
6272 return cached ;
6373 }
64- OpenWeatherDto dto = requestOpenWeather ();
74+ // OpenWeatherApi를 호출하여 날씨 데이터 가져오기
75+ OpenWeatherDto dto = requestOpenWeather ();
6576 OpenWeatherDto .Current current = dto .current ();
6677 List <OpenWeatherDto .Hourly > hourly = dto .hourly ();
6778 List <OpenWeatherDto .Daily > daily = dto .daily ();
68-
6979 // 현재
70- WeatherBasic .Now now = new WeatherBasic .Now (
71- current .temp (),
72- current .weather ().getFirst ().icon (),
73- daily .getFirst ().temp ().max (),
74- daily .getFirst ().temp ().min ());
75-
80+ WeatherBasic .Now now = convertToCurrentWeather (current , daily );
7681 // 6시간
77- List <WeatherBasic .Hour > hourList = hourly .stream ()
78- .skip (1 )
79- .limit (6 )
80- .map (hour -> {
81- ZonedDateTime kstTime = Instant .ofEpochSecond (hour .dt ())
82- .atZone (ZoneId .of ("Asia/Seoul" ));
83-
84- DateTimeFormatter formatter = DateTimeFormatter .ofPattern ("a h시" , Locale .KOREAN );
85- String formattedTime = kstTime .format (formatter );
86- return new WeatherBasic .Hour (formattedTime , hour .temp (), hour .weather ().getFirst ().icon ());
87- })
88- .toList ();
89-
90- // 오늘 제외
82+ List <WeatherBasic .Hour > hourList = convertToHourWeather (hourly );
9183 WeatherBasic view = new WeatherBasic (now , hourList );
9284 redis .opsForValue ().set (key , view , ttlUntilNextHour ());
9385 return view ;
9486 }
87+
9588 public OpenWeatherDto requestOpenWeather () {
9689 URI uri = UriComponentsBuilder .fromUriString (baseUrl + "/data/3.0/onecall" )
9790 .queryParam ("lat" , lat )
9891 .queryParam ("lon" , lon )
9992 .queryParam ("appid" , apiKey )
10093 .queryParam ("lang" , lang )
10194 .queryParam ("units" , units )
102- .queryParam ("exclude" , "minutely,alerts" )
95+ .queryParam ("exclude" , API_EXCLUDE_PARAMS )
10396 .build ()
10497 .toUri ();
10598 log .info ("onecall uri = {}" , uri );
106- try {
99+ try {
107100 return rest .getForObject (uri , OpenWeatherDto .class );
108- } catch (HttpClientErrorException | HttpServerErrorException | UnknownContentTypeException | ResourceAccessException e ) {
101+ } catch (HttpClientErrorException | HttpServerErrorException | UnknownContentTypeException |
102+ ResourceAccessException e ) {
109103 log .error ("OpenWeather API 호출 실패: {}" , e .getMessage ());
110104 throw new BusinessException (ExceptionType .WEATHER_API_ERROR );
111105 }
106+ }
107+ public WeatherBasic .Now convertToCurrentWeather (OpenWeatherDto .Current current , List <OpenWeatherDto .Daily > daily ) {
108+ return new WeatherBasic .Now (
109+ current .temp (),
110+ current .weather ().getFirst ().icon (),
111+ daily .getFirst ().temp ().max (),
112+ daily .getFirst ().temp ().min ());
113+ }
114+
115+ public List <WeatherBasic .Hour > convertToHourWeather (List <OpenWeatherDto .Hourly > hourly ){
116+ return hourly .stream ()
117+ .skip (SKIP_CURRENT_HOUR )
118+ .limit (HOURLY_FORECAST_COUNT )
119+ .map (hour -> {
120+ ZonedDateTime kstTime = Instant .ofEpochSecond (hour .dt ())
121+ .atZone (ZoneId .of ("Asia/Seoul" ));
112122
123+ DateTimeFormatter formatter = DateTimeFormatter .ofPattern ("a h시" , Locale .KOREAN );
124+ String formattedTime = kstTime .format (formatter );
125+ return new WeatherBasic .Hour (formattedTime , hour .temp (), hour .weather ().getFirst ().icon ());
126+ })
127+ .toList ();
113128 }
114129}
0 commit comments