개발 공부용

[팀 프로젝트] 프로젝트 기록 #4 - 기상청 API를 이용해 날씨 정보 받고 저장하기(2), @Scheduled 스케줄러를 이용한 자동 업데이트 본문

Project

[팀 프로젝트] 프로젝트 기록 #4 - 기상청 API를 이용해 날씨 정보 받고 저장하기(2), @Scheduled 스케줄러를 이용한 자동 업데이트

솝제로 2023. 11. 2. 23:48

 

데이터를 파싱 해보겠습니다!

 

Weather 객체를 반환하는 jsonParsing 메서드를 작성할 것입니다.

파싱을 하기 위해서 우선 받아온 데이터의 구조를 파악해야 합니다. 🐾

 

 

위 사진을 통해 아래와 같은 데이터 구조를 가졌음을 확인할 수 있습니다.💡

 

"response" : {

   "header" : {...},

   "body" : {

      "dataType" : ...,

      "items" : {

         "item" : [ { ... },

         ]

      }

   }

}

 

따라서  "response"에서 "body"를 추출하고, "body"에서 "items"를, "items"에서 "item" 배열을 추출해야 합니다.

그리고 추출한 item 배열에서 (최저기온, 최고기온 등의) 원하는 category의 원하는 시간대의 값을 찾아줍니다. 

 

 public Weather jsonParsing(String cityName, String result, String hourStr) {

        try {
            log.info("JSON Parsing 시작");
            // JSON 파서 생성
            JSONParser parser = new JSONParser();
            // JSON 데이터를 파싱하여 JSON 객체로 변환
            JSONObject jsonObject = (JSONObject) parser.parse(result);

            // "response" -> "body" -> "items" -> "item" 배열 추출
            JSONObject items = (JSONObject) jsonObject.get("response");
            items = (JSONObject) items.get("body");
            items = (JSONObject) items.get("items");
            JSONArray itemList = (JSONArray) items.get("item");


            for (Object item : itemList) {
                try {
                    JSONObject itemObject = (JSONObject) item;
                    String category = (String) itemObject.get("category");
                    Double fcstValue = Double.parseDouble((String) itemObject.get("fcstValue"));
                    String fcstTime = (String) itemObject.get("fcstTime");
                    String targetFcstTime = hourStr;
                    String targetCategory = "POP";

                    switch (category) {
                        case "TMN": //최저기온
                            minTemp = fcstValue;
                            break;
                        case "TMX": //최고기온
                            maxTemp = fcstValue;
                            break;
                    }

                    // fcstTime과 category가 모두 원하는 값인 경우 강수 확률 읽어와서 저장
                    if (targetFcstTime.equals(fcstTime) && targetCategory.equals(category)) {
                        rainProbability = fcstValue;
                    }


                } catch (NumberFormatException e) {
                    continue;
                }
            }
        } catch (ParseException e) {
            log.error("json parsing 중 에러가 발생했습니다.");
        }

        Weather weather = new Weather(cityName, rainProbability, maxTemp, minTemp, LocalDateTime.now());
        return weather;
    }

 

필요한 값이 최저기온과 최고기온, 강수확률인데

최저기온과 최고기온은 하루에 한 번만 데이터가 나오므로, 시간대를 지정해 줄 필요가 없습니다.

따라서 switch문으로 category가 TMN(최저기온)인 경우와 TMX(최고기온)인 경우의 값을 저장하도록 했습니다.

 

강수확률의 경우, 매시간마다 달라지는 데이터이므로 원하는 시간대를 지정해주어야 합니다.

따라서 if문으로 미리 설정해 두었던 hourStr, 즉 targetFcstTime과 같은 경우의 POP(강수확률)을 저장하도록 했습니다.

 

이렇게 파싱한 데이터로 Weather 객체를 생성하여 반환해 줍니다.

 

 

 

마지막으로 도시 개수만큼 이 과정을 반복하고 데이터 베이스에 저장하는 메서드를 작성해 보겠습니다.

 

public void saveNewWeather(){
        LocalDateTime now = LocalDateTime.now(); //데이터 조회 날짜와 시간
        String hourStr = makeHourStr();
        String yyyyMMdd = makeYYYYMMDD(now);

        try {
            //city 개수만큼 반복
            for (int cityId = 1; cityId <= cityService.cityCount(); cityId++) {

                City city = cityService.findVerifiedCity((long) cityId);
                String cityName = city.getName();
                String nx = city.getNx();
                String ny = city.getNy();


                //URL 만들기
                StringBuilder urlBuilder = makeURL(yyyyMMdd, nx, ny);

                //GET으로 요청 보내서 데이터 받기
                String result = null;
                try {
                    result = getData(urlBuilder);
                }
                catch (IOException e) {
                    log.error("데이터를 받아오는 과정에 에러가 발생했습니다.");
                }

                //받아온 값을 JSON 파싱하기
                Weather weather = jsonParsing(cityName, result, hourStr);

                if (weatherService.weatherCount() == cityService.cityCount()) {
                    String lastUpdateDate = makeYYYYMMDD(weatherService.findVerifiedWeather(1L).getDate());
                    log.info("지난 업데이트 날짜 : {}", lastUpdateDate);
                    log.info("현재 날짜 : {}", yyyyMMdd);
                    //weather 업데이트
                    weatherService.updateWeather(weather, (long) cityId);
                    log.info("날씨가 업데이트 되었습니다.");
                }
                else {
                    //weather 새로 저장
                    weatherService.postWeather(weather, (long) cityId);
                    log.info("날씨 정보가 새로 생성되었습니다.");
                }
            }
        } catch (NumberFormatException e) {
            log.error("유효하지 않은 날짜입니다.");
        }
    }

 

이렇게 하면 하나의 메서드만 호출하여 날씨 데이터를 받고, 파싱 하고, 저장하는 과정을 처리할 수 있습니다.

 

 

 

이제 스케줄러를 이용해서 프로그램을 실행해 놓으면 일정 시간마다 날씨 정보가 업데이트되도록 해보겠습니다.

스케줄러 사용은 매우 간단합니다. 🥰

@Scheduled 애너테이션을 사용하면 됩니다.

 

@Slf4j
@RestController
@RequiredArgsConstructor
public class WeatherApiScheduler {

    private final WeatherApiService weatherApiService;

    //첫 실행시 1분 뒤에 작동하고 그 다음부터 3시간마다 작동
    @Scheduled(initialDelay = 60000, fixedRate = 10800000)
    public void updateWeatherAutomatically() {

        //Weather Api 이용해 새 날씨 정보 받아와서 저장하거나 업데이트 하기
        weatherApiService.saveNewWeather();

    }
}

 

WeatherApiScheduler 클래스를 새로 만들어 주고, WeatherApiService를 DI 받습니다.

@Scheduled 애너테이션으로 작동 시간을 설정해 준 뒤, 앞서 생성했던 날씨 저장 메서드 saveNewWeather를 호출하도록 작성해 주면 날씨 부분은 끝입니다!!!