From dfea991abec2896242cfb147507b214980abb9d6 Mon Sep 17 00:00:00 2001 From: seheonnn Date: Thu, 20 Jun 2024 14:54:39 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20=EC=98=88=EC=83=81=20=EB=82=A0?= =?UTF-8?q?=EC=94=A8=20=EC=B9=B4=ED=94=84=EC=B9=B4=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/waither/weatherservice/service/WeatherService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/weather-service/src/main/java/com/waither/weatherservice/service/WeatherService.java b/weather-service/src/main/java/com/waither/weatherservice/service/WeatherService.java index 1fbf3b6d..30558bf2 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/service/WeatherService.java +++ b/weather-service/src/main/java/com/waither/weatherservice/service/WeatherService.java @@ -65,7 +65,8 @@ public void createExpectedWeather( ForeCastOpenApiResponse.Item item = items.get(0); List region = regionRepository.findRegionByXAndY(item.getNx(), item.getNy()); - String key = region.get(0).getRegionName() + "_" + item.getFcstDate() + "_" + item.getFcstTime(); + String regionName = region.get(0).getRegionName(); + String key = regionName + "_" + item.getFcstDate() + "_" + item.getFcstTime(); ExpectedWeather expectedWeather = ExpectedWeather.builder() .id(key) @@ -75,6 +76,10 @@ public void createExpectedWeather( .expectedSky(expectedSkyList) .build(); + String content = String.join(",", expectedWeather.getExpectedRain()); + KafkaMessage kafkaMessage = KafkaMessage.of(regionName, content); + producer.produceMessage("alarm-rain", kafkaMessage); + ExpectedWeather save = expectedWeatherRepository.save(expectedWeather); log.info("[*] 예상 기후 : {}", save); } From 71e2e68f0613a8089e2f160f950ba5ff7e2e948d Mon Sep 17 00:00:00 2001 From: seheonnn Date: Thu, 20 Jun 2024 16:21:40 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=ED=99=94=EB=A9=B4=20=EB=B0=98=ED=99=98=EA=B0=92=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weatherservice/dto/response/MainWeatherResponse.java | 3 +++ .../com/waither/weatherservice/entity/DailyWeather.java | 1 + .../com/waither/weatherservice/service/WeatherService.java | 6 ++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/weather-service/src/main/java/com/waither/weatherservice/dto/response/MainWeatherResponse.java b/weather-service/src/main/java/com/waither/weatherservice/dto/response/MainWeatherResponse.java index d944052c..8c30a676 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/dto/response/MainWeatherResponse.java +++ b/weather-service/src/main/java/com/waither/weatherservice/dto/response/MainWeatherResponse.java @@ -8,6 +8,7 @@ public record MainWeatherResponse( String pop, + String temp, String tempMin, String tempMax, String humidity, @@ -28,6 +29,7 @@ public record MainWeatherResponse( public static MainWeatherResponse from( String pop, + String temp, String tempMin, String tempMax, String humidity, @@ -47,6 +49,7 @@ public static MainWeatherResponse from( ) { return MainWeatherResponse.builder() .pop(pop) + .temp(temp) .tempMin(tempMin) .tempMax(tempMax) .humidity(humidity) diff --git a/weather-service/src/main/java/com/waither/weatherservice/entity/DailyWeather.java b/weather-service/src/main/java/com/waither/weatherservice/entity/DailyWeather.java index f2983596..e3022d07 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/entity/DailyWeather.java +++ b/weather-service/src/main/java/com/waither/weatherservice/entity/DailyWeather.java @@ -21,6 +21,7 @@ public class DailyWeather { // 강수확률 (%) private String pop; + private String tmp; private String tempMin; private String tempMax; private String humidity; diff --git a/weather-service/src/main/java/com/waither/weatherservice/service/WeatherService.java b/weather-service/src/main/java/com/waither/weatherservice/service/WeatherService.java index 30558bf2..f5fd5e6f 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/service/WeatherService.java +++ b/weather-service/src/main/java/com/waither/weatherservice/service/WeatherService.java @@ -96,6 +96,7 @@ public void createDailyWeather( "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst"); String pop = openApiUtil.apiResponseStringFilter(items, "POP"); + String tmp = openApiUtil.apiResponseStringFilter(items, "TMP"); String tmn = openApiUtil.apiResponseStringFilter(items, "TMN"); String tmx = openApiUtil.apiResponseStringFilter(items, "TMX"); String reh = openApiUtil.apiResponseStringFilter(items, "REH"); @@ -111,6 +112,7 @@ public void createDailyWeather( DailyWeather dailyWeather = DailyWeather.builder() .id(key) .pop(pop) + .tmp(tmp) .tempMin(tmn) .tempMax(tmx) .humidity(reh) @@ -165,7 +167,7 @@ public MainWeatherResponse getMainWeather(double latitude, double longitude) { List region = regionRepository.findRegionByLatAndLong(latitude, longitude); String key = region.get(0).getRegionName() + "_" + convertLocalDateTimeToString(now); - // 테스트 키 : "55_127_20240508_1500" + // 테스트 키 : "서울특별시_20240508_1500" DailyWeather dailyWeather = dailyWeatherRepository.findById(key) .orElseThrow(() -> new WeatherExceptionHandler(WeatherErrorCode.WEATHER_MAIN_ERROR)); @@ -174,7 +176,7 @@ public MainWeatherResponse getMainWeather(double latitude, double longitude) { .orElseThrow(() -> new WeatherExceptionHandler(WeatherErrorCode.WEATHER_MAIN_ERROR)); MainWeatherResponse weatherMainResponse = MainWeatherResponse.from( - dailyWeather.getPop(), dailyWeather.getTempMin(), + dailyWeather.getPop(), dailyWeather.getTmp(), dailyWeather.getTempMin(), dailyWeather.getTempMax(), dailyWeather.getHumidity(), dailyWeather.getWindVector(), dailyWeather.getWindDegree(), expectedWeather.getExpectedTemp(), From 8324e553c5821d0a2c9b521cc050d293bd78c328 Mon Sep 17 00:00:00 2001 From: seheonnn Date: Thu, 20 Jun 2024 16:43:08 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20Request=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weatherservice/controller/WeatherController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherController.java b/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherController.java index b171c876..2e87619e 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherController.java +++ b/weather-service/src/main/java/com/waither/weatherservice/controller/WeatherController.java @@ -1,7 +1,7 @@ package com.waither.weatherservice.controller; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -21,13 +21,13 @@ public class WeatherController { private final WeatherService weatherService; @GetMapping("/main") - public ApiResponse getMainWeather(@RequestBody @Valid GetWeatherRequest getWeatherRequest) { + public ApiResponse getMainWeather(@ModelAttribute @Valid GetWeatherRequest getWeatherRequest) { return ApiResponse.onSuccess( weatherService.getMainWeather(getWeatherRequest.latitude(), getWeatherRequest.longitude())); } @GetMapping("/region") - public ApiResponse convertGpsToRegionName(@RequestBody @Valid GetWeatherRequest getWeatherRequest) { + public ApiResponse convertGpsToRegionName(@ModelAttribute @Valid GetWeatherRequest getWeatherRequest) { return ApiResponse.onSuccess( weatherService.convertGpsToRegionName(getWeatherRequest.latitude(), getWeatherRequest.longitude())); } From 00e45fc361db3b9dfeee8ed4b3f54f0c2251581f Mon Sep 17 00:00:00 2001 From: seheonnn Date: Thu, 20 Jun 2024 17:28:51 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20=EC=8A=A4?= =?UTF-8?q?=ED=94=84=EB=A7=81=20=EB=B0=B0=EC=B9=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- weather-service/build.gradle | 3 + .../weatherservice/batch/BatchConfig.java | 63 ++++++++++ .../weatherservice/batch/BatchScheduler.java | 50 ++++++++ .../batch/DailyWeatherTasklet.java | 40 ++++++ .../batch/ExpectedWeatherTasklet.java | 40 ++++++ .../scheduler/SchedulerConfig.java | 116 +++++++++--------- 6 files changed, 254 insertions(+), 58 deletions(-) create mode 100644 weather-service/src/main/java/com/waither/weatherservice/batch/BatchConfig.java create mode 100644 weather-service/src/main/java/com/waither/weatherservice/batch/BatchScheduler.java create mode 100644 weather-service/src/main/java/com/waither/weatherservice/batch/DailyWeatherTasklet.java create mode 100644 weather-service/src/main/java/com/waither/weatherservice/batch/ExpectedWeatherTasklet.java diff --git a/weather-service/build.gradle b/weather-service/build.gradle index 014668a2..ec813b5b 100644 --- a/weather-service/build.gradle +++ b/weather-service/build.gradle @@ -59,6 +59,9 @@ dependencies { // File implementation 'org.apache.poi:poi-ooxml:5.2.0' + + // Spring batch + implementation 'org.springframework.boot:spring-boot-starter-batch' } openApi { diff --git a/weather-service/src/main/java/com/waither/weatherservice/batch/BatchConfig.java b/weather-service/src/main/java/com/waither/weatherservice/batch/BatchConfig.java new file mode 100644 index 00000000..b1b9d82a --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/batch/BatchConfig.java @@ -0,0 +1,63 @@ +package com.waither.weatherservice.batch; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +import com.waither.weatherservice.service.WeatherService; + +import lombok.RequiredArgsConstructor; + +@Configuration +@RequiredArgsConstructor +public class BatchConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager transactionManager; + private final WeatherService weatherService; + + @Bean + public Job dailyWeatherJob() { + return new JobBuilder("dailyWeatherJob", jobRepository) + .start(dailyWeatherStep()) + .build(); + } + + @Bean + public Step dailyWeatherStep() { + return new StepBuilder("dailyWeatherStep", jobRepository) + .tasklet(dailyWeatherTasklet(), transactionManager) + .build(); + } + + @Bean + public Tasklet dailyWeatherTasklet() { + return new DailyWeatherTasklet(weatherService); + } + // ==================================== + + @Bean + public Job expectedWeatherJob() { + return new JobBuilder("expectedWeatherJob", jobRepository) + .start(expectedWeatherStep()) + .build(); + } + + @Bean + public Step expectedWeatherStep() { + return new StepBuilder("expectedWeatherStep", jobRepository) + .tasklet(expectedWeatherTasklet(), transactionManager) + .build(); + } + + @Bean + public Tasklet expectedWeatherTasklet() { + return new ExpectedWeatherTasklet(weatherService); + } +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/batch/BatchScheduler.java b/weather-service/src/main/java/com/waither/weatherservice/batch/BatchScheduler.java new file mode 100644 index 00000000..c1436e47 --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/batch/BatchScheduler.java @@ -0,0 +1,50 @@ +package com.waither.weatherservice.batch; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecutionException; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Component +@EnableScheduling +@RequiredArgsConstructor +@Slf4j +public class BatchScheduler { + + private final JobLauncher jobLauncher; + private final Job dailyWeatherJob; + private final Job expectedWeatherJob; + + @Scheduled(cron = "0 0 2,5,8,11,14,17,20,23 * * *") // 3시간마다 + public void runDailyWeatherJob() { + try { + JobParameters jobParameters = new JobParametersBuilder() + .addLong("executedTime", System.currentTimeMillis()) + .toJobParameters(); + + jobLauncher.run(dailyWeatherJob, jobParameters); + } catch (JobExecutionException e) { + log.error("Error executing dailyWeatherJob: ", e); + } + } + + @Scheduled(cron = "0 0 * * * *") // 1시간마다 + public void runExpectedWeatherJob() { + try { + JobParameters jobParameters = new JobParametersBuilder() + .addLong("executedTime", System.currentTimeMillis()) + .toJobParameters(); + + jobLauncher.run(expectedWeatherJob, jobParameters); + } catch (JobExecutionException e) { + log.error("Error executing expectedWeatherJob: ", e); + } + } +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/batch/DailyWeatherTasklet.java b/weather-service/src/main/java/com/waither/weatherservice/batch/DailyWeatherTasklet.java new file mode 100644 index 00000000..69491f03 --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/batch/DailyWeatherTasklet.java @@ -0,0 +1,40 @@ +package com.waither.weatherservice.batch; + +import java.net.URISyntaxException; +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import com.waither.weatherservice.entity.Region; +import com.waither.weatherservice.exception.WeatherExceptionHandler; +import com.waither.weatherservice.response.WeatherErrorCode; +import com.waither.weatherservice.service.WeatherService; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class DailyWeatherTasklet implements Tasklet { + + private final WeatherService weatherService; + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + LocalDateTime now = LocalDateTime.now(); + String[] dateTime = weatherService.convertLocalDateTimeToString(now).split("_"); + List regionList = weatherService.getRegionList(); + regionList.stream() + .forEach(region -> { + try { + weatherService.createDailyWeather(region.getStartX(), region.getStartY(), dateTime[0], + dateTime[1]); + } catch (URISyntaxException e) { + throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_URI_ERROR); + } + }); + return RepeatStatus.FINISHED; + } +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/batch/ExpectedWeatherTasklet.java b/weather-service/src/main/java/com/waither/weatherservice/batch/ExpectedWeatherTasklet.java new file mode 100644 index 00000000..314e67ce --- /dev/null +++ b/weather-service/src/main/java/com/waither/weatherservice/batch/ExpectedWeatherTasklet.java @@ -0,0 +1,40 @@ +package com.waither.weatherservice.batch; + +import java.net.URISyntaxException; +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +import com.waither.weatherservice.entity.Region; +import com.waither.weatherservice.exception.WeatherExceptionHandler; +import com.waither.weatherservice.response.WeatherErrorCode; +import com.waither.weatherservice.service.WeatherService; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class ExpectedWeatherTasklet implements Tasklet { + + private final WeatherService weatherService; + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + LocalDateTime now = LocalDateTime.now(); + String[] dateTime = weatherService.convertLocalDateTimeToString(now).split("_"); + List regionList = weatherService.getRegionList(); + regionList.stream() + .forEach(region -> { + try { + weatherService.createExpectedWeather(region.getStartX(), region.getStartY(), dateTime[0], + dateTime[1]); + } catch (URISyntaxException e) { + throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_URI_ERROR); + } + }); + return RepeatStatus.FINISHED; + } +} diff --git a/weather-service/src/main/java/com/waither/weatherservice/scheduler/SchedulerConfig.java b/weather-service/src/main/java/com/waither/weatherservice/scheduler/SchedulerConfig.java index 5d1ea5e2..2775332d 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/scheduler/SchedulerConfig.java +++ b/weather-service/src/main/java/com/waither/weatherservice/scheduler/SchedulerConfig.java @@ -1,58 +1,58 @@ -package com.waither.weatherservice.scheduler; - -import java.net.URISyntaxException; -import java.time.LocalDateTime; -import java.util.List; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.scheduling.annotation.Scheduled; - -import com.waither.weatherservice.entity.Region; -import com.waither.weatherservice.exception.WeatherExceptionHandler; -import com.waither.weatherservice.response.WeatherErrorCode; -import com.waither.weatherservice.service.WeatherService; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@RequiredArgsConstructor -@ConfigurationProperties -public class SchedulerConfig { - - private final WeatherService weatherService; - - @Scheduled(cron = "0 0 2,5,8,11,14,17,20,23 * * *") // 3시간 마다 - public void createDailyWeather() { - - LocalDateTime now = LocalDateTime.now(); - String[] dateTime = weatherService.convertLocalDateTimeToString(now).split("_"); - List regionList = weatherService.getRegionList(); - regionList.stream() - .forEach(region -> { - try { - weatherService.createDailyWeather(region.getStartX(), region.getStartY(), dateTime[0], - dateTime[1]); - } catch (URISyntaxException e) { - new WeatherExceptionHandler(WeatherErrorCode.WEATHER_URI_ERROR); - } - }); - } - - @Scheduled(cron = "0 0 * * * *") // 1시간 마다 - public void createExpectedWeather() { - - LocalDateTime now = LocalDateTime.now(); - String[] dateTime = weatherService.convertLocalDateTimeToString(now).split("_"); - List regionList = weatherService.getRegionList(); - regionList.stream() - .forEach(region -> { - try { - weatherService.createExpectedWeather(region.getStartX(), region.getStartY(), dateTime[0], - dateTime[1]); - } catch (URISyntaxException e) { - new WeatherExceptionHandler(WeatherErrorCode.WEATHER_URI_ERROR); - } - }); - } -} +// package com.waither.weatherservice.scheduler; +// +// import java.net.URISyntaxException; +// import java.time.LocalDateTime; +// import java.util.List; +// +// import org.springframework.boot.context.properties.ConfigurationProperties; +// import org.springframework.scheduling.annotation.Scheduled; +// +// import com.waither.weatherservice.entity.Region; +// import com.waither.weatherservice.exception.WeatherExceptionHandler; +// import com.waither.weatherservice.response.WeatherErrorCode; +// import com.waither.weatherservice.service.WeatherService; +// +// import lombok.RequiredArgsConstructor; +// import lombok.extern.slf4j.Slf4j; +// +// @Slf4j +// @RequiredArgsConstructor +// @ConfigurationProperties +// public class SchedulerConfig { +// +// private final WeatherService weatherService; +// +// @Scheduled(cron = "0 0 2,5,8,11,14,17,20,23 * * *") // 3시간 마다 +// public void createDailyWeather() { +// +// LocalDateTime now = LocalDateTime.now(); +// String[] dateTime = weatherService.convertLocalDateTimeToString(now).split("_"); +// List regionList = weatherService.getRegionList(); +// regionList.stream() +// .forEach(region -> { +// try { +// weatherService.createDailyWeather(region.getStartX(), region.getStartY(), dateTime[0], +// dateTime[1]); +// } catch (URISyntaxException e) { +// throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_URI_ERROR); +// } +// }); +// } +// +// @Scheduled(cron = "0 0 * * * *") // 1시간 마다 +// public void createExpectedWeather() { +// +// LocalDateTime now = LocalDateTime.now(); +// String[] dateTime = weatherService.convertLocalDateTimeToString(now).split("_"); +// List regionList = weatherService.getRegionList(); +// regionList.stream() +// .forEach(region -> { +// try { +// weatherService.createExpectedWeather(region.getStartX(), region.getStartY(), dateTime[0], +// dateTime[1]); +// } catch (URISyntaxException e) { +// throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_URI_ERROR); +// } +// }); +// } +// } From dbe064306080441ef912356dc538f32be6fd097d Mon Sep 17 00:00:00 2001 From: seheonnn Date: Thu, 20 Jun 2024 17:36:02 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/waither/weatherservice/batch/BatchConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/weather-service/src/main/java/com/waither/weatherservice/batch/BatchConfig.java b/weather-service/src/main/java/com/waither/weatherservice/batch/BatchConfig.java index b1b9d82a..5b55607c 100644 --- a/weather-service/src/main/java/com/waither/weatherservice/batch/BatchConfig.java +++ b/weather-service/src/main/java/com/waither/weatherservice/batch/BatchConfig.java @@ -40,7 +40,6 @@ public Step dailyWeatherStep() { public Tasklet dailyWeatherTasklet() { return new DailyWeatherTasklet(weatherService); } - // ==================================== @Bean public Job expectedWeatherJob() {