diff --git a/src/main/java/com/seoultech/sanEseo/global/config/DataGoConfig.java b/src/main/java/com/seoultech/sanEseo/global/config/DataAPIConfig.java similarity index 64% rename from src/main/java/com/seoultech/sanEseo/global/config/DataGoConfig.java rename to src/main/java/com/seoultech/sanEseo/global/config/DataAPIConfig.java index 340d31d..e8e43ea 100644 --- a/src/main/java/com/seoultech/sanEseo/global/config/DataGoConfig.java +++ b/src/main/java/com/seoultech/sanEseo/global/config/DataAPIConfig.java @@ -7,10 +7,10 @@ import org.springframework.web.service.invoker.HttpServiceProxyFactory; @Configuration -class DataGoConfig { +class DataAPIConfig { @Bean - public DataGoAPI dataGoApi() { + public DataGoAPI dataGoAPI() { WebClient client = WebClient.create("https://apis.data.go.kr"); return HttpServiceProxyFactory @@ -19,4 +19,14 @@ public DataGoAPI dataGoApi() { .createClient(DataGoAPI.class); } + @Bean + public DataSeoulAPI DataSeoulAPI() { + WebClient client = WebClient.create("http://openapi.seoul.go.kr:8088"); + + return HttpServiceProxyFactory + .builderFor(WebClientAdapter.create(client)) + .build() + .createClient(DataSeoulAPI.class); + } + } diff --git a/src/main/java/com/seoultech/sanEseo/global/config/DataGoAPI.java b/src/main/java/com/seoultech/sanEseo/global/config/DataGoAPI.java index bb04c9b..519fcb8 100644 --- a/src/main/java/com/seoultech/sanEseo/global/config/DataGoAPI.java +++ b/src/main/java/com/seoultech/sanEseo/global/config/DataGoAPI.java @@ -1,12 +1,13 @@ package com.seoultech.sanEseo.global.config; +import com.seoultech.sanEseo.weather.application.service.WeatherAPIResponse; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.service.annotation.GetExchange; public interface DataGoAPI { - @GetExchange("/1360000/VilageFcstInfoService_2.0/getVilageFcst?serviceKey={serviceKey}&pageNo=1&numOfRows=20&dataType=json&base_date={base_date}&base_time={base_time}&nx={nx}&ny={ny}") - String getWeather( + @GetExchange("/1360000/VilageFcstInfoService_2.0/getVilageFcst?serviceKey={serviceKey}&pageNo=1&numOfRows=1000&dataType=json&base_date={base_date}&base_time={base_time}&nx={nx}&ny={ny}") + WeatherAPIResponse getWeather( @PathVariable("serviceKey") String serviceKey, @PathVariable("base_date") String baseDate, @PathVariable("base_time") String baseTime, diff --git a/src/main/java/com/seoultech/sanEseo/global/config/DataSeoulAPI.java b/src/main/java/com/seoultech/sanEseo/global/config/DataSeoulAPI.java new file mode 100644 index 0000000..2cebc80 --- /dev/null +++ b/src/main/java/com/seoultech/sanEseo/global/config/DataSeoulAPI.java @@ -0,0 +1,15 @@ +package com.seoultech.sanEseo.global.config; + +import com.seoultech.sanEseo.weather.application.service.PollutionAPIResponse; +import com.seoultech.sanEseo.weather.application.service.WeatherAPIResponse; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.service.annotation.GetExchange; + +public interface DataSeoulAPI { + + @GetExchange("/{serviceKey}/json/ListAirQualityByDistrictService/1/1/{code}/") + PollutionAPIResponse getPollution( + @PathVariable("serviceKey") String serviceKey, + @PathVariable("code") int code + ); +} diff --git a/src/main/java/com/seoultech/sanEseo/weather/adapter/in/web/WeatherController.java b/src/main/java/com/seoultech/sanEseo/weather/adapter/in/web/WeatherController.java index 34c49de..f9cde2a 100644 --- a/src/main/java/com/seoultech/sanEseo/weather/adapter/in/web/WeatherController.java +++ b/src/main/java/com/seoultech/sanEseo/weather/adapter/in/web/WeatherController.java @@ -16,7 +16,7 @@ public class WeatherController { private final WeatherService weatherService; @GetMapping public ResponseEntity getWeather(@RequestParam Long districtId) { - return ApiResponse.ok("기상 정보 조회 성공", weatherService.getWeatherData(districtId)); + return ApiResponse.ok("기상 정보 조회 성공", weatherService.getWeatherResponse(districtId)); } } diff --git a/src/main/java/com/seoultech/sanEseo/weather/adapter/in/web/WeatherResponse.java b/src/main/java/com/seoultech/sanEseo/weather/adapter/in/web/WeatherResponse.java index 344aff0..0a77f56 100644 --- a/src/main/java/com/seoultech/sanEseo/weather/adapter/in/web/WeatherResponse.java +++ b/src/main/java/com/seoultech/sanEseo/weather/adapter/in/web/WeatherResponse.java @@ -15,7 +15,7 @@ public class WeatherResponse { private Float temperature; private Float temperatureMax; private Float temperatureMin; - private int precipitation; + private String precipitation; private int humidity; private Float microDust; private Float ultraMicroDust; diff --git a/src/main/java/com/seoultech/sanEseo/weather/application/service/PollutionAPIResponse.java b/src/main/java/com/seoultech/sanEseo/weather/application/service/PollutionAPIResponse.java new file mode 100644 index 0000000..a6f6dc7 --- /dev/null +++ b/src/main/java/com/seoultech/sanEseo/weather/application/service/PollutionAPIResponse.java @@ -0,0 +1,49 @@ +package com.seoultech.sanEseo.weather.application.service; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class PollutionAPIResponse { + + @JsonProperty("ListAirQualityByDistrictService") + private ListAirQualityByDistrictService listAirQualityByDistrictService; + + @Data + public static class ListAirQualityByDistrictService { + @JsonProperty("list_total_count") + private int count; + + private List row; + } + + @Data + public static class AirQualityData { + @JsonProperty("MSRDATE") + private String date; + @JsonProperty("MSRADMCODE") + private String districtCode; + @JsonProperty("MSRSTENAME") + private String districtName; + @JsonProperty("MAXINDEX") + private String maxIndex; + @JsonProperty("GRADE") + private String grade; + @JsonProperty("POLLUTANT") + private String pollutant; + @JsonProperty("NITROGEN") + private String nitrogen; + @JsonProperty("OZONE") + private String ozone; + @JsonProperty("CARBON") + private String carbon; + @JsonProperty("SULFUROUS") + private String sulfurous; + @JsonProperty("PM10") + private String pm10; + @JsonProperty("PM25") + private String pm25; + } +} diff --git a/src/main/java/com/seoultech/sanEseo/weather/application/service/PollutionResponse.java b/src/main/java/com/seoultech/sanEseo/weather/application/service/PollutionResponse.java deleted file mode 100644 index eaf3ab4..0000000 --- a/src/main/java/com/seoultech/sanEseo/weather/application/service/PollutionResponse.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.seoultech.sanEseo.weather.application.service; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class PollutionResponse { - @JsonProperty("MSRDATE") - private String date; - - @JsonProperty("MSRADMCODE") - private String code; - - @JsonProperty("MSRSTENAME") - private String district; - - @JsonProperty("MAXINDEX") - private String maxIndex; - - @JsonProperty("GRADE") - private String grade; - - @JsonProperty("POLLUTANT") - private String pollutant; - - @JsonProperty("NITROGEN") - private String nitrogen; - - @JsonProperty("OZONE") - private String ozone; - - @JsonProperty("CARBON") - private String carbon; - - @JsonProperty("SULFUROUS") - private String sulfurous; - - @JsonProperty("PM10") - private String pm10; - - @JsonProperty("PM25") - private String pm25; - - // Getters and setters -} diff --git a/src/main/java/com/seoultech/sanEseo/weather/application/service/WeatherAPIResponse.java b/src/main/java/com/seoultech/sanEseo/weather/application/service/WeatherAPIResponse.java new file mode 100644 index 0000000..6327b2f --- /dev/null +++ b/src/main/java/com/seoultech/sanEseo/weather/application/service/WeatherAPIResponse.java @@ -0,0 +1,67 @@ +package com.seoultech.sanEseo.weather.application.service; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import java.util.List; + +@Data +public class WeatherAPIResponse { + @JsonProperty("response") + private Response response; + + @Data + public static class Response { + private Header header; + private Body body; + } + + @Data + public static class Header { + private String resultCode; + private String resultMsg; + } + + @Data + public static class Body { + private String dataType; + private Items items; + private int pageNo; + private int numOfRows; + private int totalCount; + } + + @Data + public static class Items { + private List item; + } + + @Data + public static class Item { + private String baseDate; + private String baseTime; + private String category; + private String fcstDate; + private String fcstTime; + private String fcstValue; + private int nx; + private int ny; + } + + public String getValueByCategoryAndDateTime(String category, String fcstDate, String fcstTime) { + for (Item item : response.body.items.item) { + if (item.getCategory().equals(category) && item.getFcstDate().equals(fcstDate) && item.getFcstTime().equals(fcstTime)) { + return item.getFcstValue(); + } + } + return null; + } + + public String getValueByCategoryAndDate(String category, String fcstDate) { + for (Item item : response.body.items.item) { + if (item.getCategory().equals(category) && item.getFcstDate().equals(fcstDate)) { + return item.getFcstValue(); + } + } + return null; + } +} diff --git a/src/main/java/com/seoultech/sanEseo/weather/application/service/WeatherService.java b/src/main/java/com/seoultech/sanEseo/weather/application/service/WeatherService.java index c0835e1..003eb9c 100644 --- a/src/main/java/com/seoultech/sanEseo/weather/application/service/WeatherService.java +++ b/src/main/java/com/seoultech/sanEseo/weather/application/service/WeatherService.java @@ -4,10 +4,17 @@ import com.seoultech.sanEseo.district.application.service.DistrictService; import com.seoultech.sanEseo.district.domain.District; import com.seoultech.sanEseo.global.config.DataGoAPI; +import com.seoultech.sanEseo.global.config.DataSeoulAPI; +import com.seoultech.sanEseo.global.exception.BusinessException; +import com.seoultech.sanEseo.global.exception.ErrorType; import com.seoultech.sanEseo.global.property.PublicDataProperty; import com.seoultech.sanEseo.weather.adapter.in.web.WeatherResponse; +import com.seoultech.sanEseo.weather.domain.PollutionData; +import com.seoultech.sanEseo.weather.domain.WeatherAPIRequest; +import com.seoultech.sanEseo.weather.domain.WeatherData; import com.seoultech.sanEseo.weather.exceptin.PublicAPIException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -17,7 +24,12 @@ import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +@Slf4j @RequiredArgsConstructor @Service public class WeatherService { @@ -25,63 +37,113 @@ public class WeatherService { private final PublicDataProperty publicDataProperty; private final DistrictService districtService; private final DataGoAPI dataGoAPI; + private final DataSeoulAPI dataSeoulAPI; - public WeatherResponse getWeatherData(Long districtId) { + private ExecutorService executorService = Executors.newCachedThreadPool(); + + public WeatherResponse getWeatherResponse(Long districtId) { District district = districtService.findById(districtId); - PollutionResponse pollutionResponse = getPollution(district); - // WeatherResponse weatherResponse = getWeather(district); + Future task1 = executorService.submit(() -> getPollution(district.getPollutionAPI().getCode())); + Future task2 = executorService.submit(() -> getWeatherData(district.getWeatherAPI().getX(), district.getWeatherAPI().getY())); + + PollutionData pollutionData = null; + WeatherData weatherData = null; + try { + pollutionData = task1.get(); + weatherData = task2.get(); + } catch (InterruptedException | ExecutionException e) { + throw new BusinessException(ErrorType.INTERNAL_ERROR, "비동기 처리 오류"); + } + return WeatherResponse.builder() .districtId(districtId) .districtName(district.getName()) - .temperature(16.0f) - .temperatureMax(20.0f) - .temperatureMin(10.0f) - .precipitation(0) - .humidity(50) - .microDust(30.0f) - .ultraMicroDust(20.0f) - .ozone(0.03f) + .temperature(weatherData.getTemperature()) + .temperatureMax(weatherData.getTemperatureMax()) + .temperatureMin(weatherData.getTemperatureMin()) + .precipitation(weatherData.getPrecipitation()) + .humidity(weatherData.getHumidity()) + .microDust(pollutionData.getMicroDust()) + .ultraMicroDust(pollutionData.getUltraMicroDust()) + .ozone(pollutionData.getOzone()) .build(); } - public PollutionResponse getPollution(District district) { - - RestTemplate restTemplate = new RestTemplate(); - String apiUrl = "http://openapi.seoul.go.kr:8088/" + publicDataProperty.getSeoulAccessKey() + "/json/ListAirQualityByDistrictService/1/1/" + district.getPollutionAPI().getCode() + "/"; - ResponseEntity response = restTemplate.getForEntity(apiUrl, Map.class); - Map responseBody = response.getBody(); - if (responseBody != null) { - Map airQualityData = (Map) responseBody.get("ListAirQualityByDistrictService"); - if (airQualityData != null) { - List> row = (List>) airQualityData.get("row"); - if (row != null) { - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.convertValue(row.get(0), PollutionResponse.class); - } - } + public PollutionData getPollution(int code) { + try { + PollutionAPIResponse result = dataSeoulAPI.getPollution( + publicDataProperty.getSeoulAccessKey(), + code + ); + log.info("result: {}", result); + Float microDust = Float.parseFloat(result.getListAirQualityByDistrictService().getRow().get(0).getPm10()); + Float ultraMicroDust = Float.parseFloat(result.getListAirQualityByDistrictService().getRow().get(0).getPm25()); + Float ozone = Float.parseFloat(result.getListAirQualityByDistrictService().getRow().get(0).getOzone()); + log.info("PM10: {}, PM25: {}, O3: {}", microDust, ultraMicroDust, ozone); + + return new PollutionData(microDust, ultraMicroDust, ozone); + } catch (Exception e) { + e.printStackTrace(); + throw new PublicAPIException("대기 정보를 불러올 수 없습니다."); } - return null; } - public Map getWeather(District district) { - try { - String dateString = getToday(); - String timeString = getRecentTime(); - int x = district.getWeatherAPI().getX(); - int y = district.getWeatherAPI().getY(); - // dateString, timeString, x, y 출력 - System.out.println(dateString); - System.out.println(timeString); - System.out.println(x); - System.out.println(y); - - String key = publicDataProperty.getAccessKey(); - - String result = dataGoAPI.getWeather(key, dateString, timeString, x, y); - System.out.println(result); + public WeatherData getWeatherData(int nx, int ny) { - return null; + try { + String baseDate = getBaseDate(); + String baseTime = getBaseTime(); + + String fcstDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); + String fcstTime = LocalTime.now().plusHours(1).format(DateTimeFormatter.ofPattern("HH00")); + + WeatherAPIRequest request = WeatherAPIRequest.builder() + .serviceKey(publicDataProperty.getAccessKey()) + .baseDate(baseDate) + .baseTime(baseTime) + .nx(nx) + .ny(ny) + .build(); + + Future task1 = executorService.submit(() -> dataGoAPI.getWeather( + request.getServiceKey(), + request.getBaseDate(), + request.getBaseTime(), + request.getNx(), + request.getNy() + )); + + String tmpMaxMinBaseDate = getTmpMaxMinBaseDate(); + String tmpMaxMinBaseTime = getTmpMaxMinBaseTime(); + + WeatherAPIRequest newRequest = WeatherAPIRequest.builder() + .serviceKey(publicDataProperty.getAccessKey()) + .baseDate(tmpMaxMinBaseDate) + .baseTime(tmpMaxMinBaseTime) + .nx(nx) + .ny(ny) + .build(); + + Future task2 = executorService.submit(() -> dataGoAPI.getWeather( + newRequest.getServiceKey(), + newRequest.getBaseDate(), + newRequest.getBaseTime(), + newRequest.getNx(), + newRequest.getNy())); + + + WeatherAPIResponse result = task1.get(); + Float tmp = Float.parseFloat(result.getValueByCategoryAndDateTime("TMP", fcstDate, fcstTime)); + String pcp = result.getValueByCategoryAndDateTime("PCP", fcstDate, fcstTime); + int reh = Integer.parseInt(result.getValueByCategoryAndDateTime("REH", fcstDate, fcstTime)); + + + WeatherAPIResponse newResult = task2.get(); + Float tmx = Float.parseFloat(newResult.getValueByCategoryAndDate("TMX", fcstDate)); + Float tmn = Float.parseFloat(newResult.getValueByCategoryAndDate("TMN", fcstDate)); + + return new WeatherData(tmp, tmx, tmn, pcp, reh); } catch (Exception e) { e.printStackTrace(); @@ -90,14 +152,53 @@ public Map getWeather(District district) { } - private String getToday() { + private String getToday(int minusDays) { + LocalDate today = LocalDate.now().minusDays(minusDays); + return today.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + } + + private List baseHours = List.of(2, 5, 8, 11, 14, 17, 20, 23); + + private String getBaseDate() { + LocalTime now = LocalTime.now(); + boolean isToday = now.isAfter(LocalTime.of(baseHours.get(0), 30)); + + LocalDate today = LocalDate.now(); + if(!isToday) { + today = today.minusDays(1); + } + return today.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + } + + private String getBaseTime() { + LocalTime now = LocalTime.now(); + int result = baseHours.get(baseHours.size() - 1); + for(int baseHour : baseHours) { + if(now.isAfter(LocalTime.of(baseHour, 30))) { + result = baseHour; + } + } + return String.format("%02d00", result); + } + + private String getTmpMaxMinBaseDate() { + LocalTime now = LocalTime.now(); + boolean isToday = now.isAfter(LocalTime.of(2, 30)); LocalDate today = LocalDate.now(); + if(!isToday) { + today = today.minusDays(1); + } return today.format(DateTimeFormatter.ofPattern("yyyyMMdd")); } - private String getRecentTime() { - LocalTime currentTime = LocalTime.now(); - LocalTime roundedTime = currentTime.minusHours(5).withMinute(0).withSecond(0).withNano(0); - return roundedTime.format(DateTimeFormatter.ofPattern("HHmm")); + private String getTmpMaxMinBaseTime() { + LocalTime now = LocalTime.now(); + int result = 0; + if(now.isAfter(LocalTime.of(2, 30))) { + result = baseHours.get(0); + } else { + result = baseHours.get(baseHours.size() - 1); + } + return String.format("%02d00", result); } } diff --git a/src/main/java/com/seoultech/sanEseo/weather/domain/PollutionData.java b/src/main/java/com/seoultech/sanEseo/weather/domain/PollutionData.java new file mode 100644 index 0000000..35deeb2 --- /dev/null +++ b/src/main/java/com/seoultech/sanEseo/weather/domain/PollutionData.java @@ -0,0 +1,14 @@ +package com.seoultech.sanEseo.weather.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class PollutionData { + private Float microDust; + private Float ultraMicroDust; + private Float ozone; +} diff --git a/src/main/java/com/seoultech/sanEseo/weather/domain/WeatherAPIRequest.java b/src/main/java/com/seoultech/sanEseo/weather/domain/WeatherAPIRequest.java new file mode 100644 index 0000000..3efb0cd --- /dev/null +++ b/src/main/java/com/seoultech/sanEseo/weather/domain/WeatherAPIRequest.java @@ -0,0 +1,16 @@ +package com.seoultech.sanEseo.weather.domain; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@Builder +public class WeatherAPIRequest { + private String serviceKey; + private String baseDate; + private String baseTime; + private int nx; + private int ny; +} diff --git a/src/main/java/com/seoultech/sanEseo/weather/domain/WeatherData.java b/src/main/java/com/seoultech/sanEseo/weather/domain/WeatherData.java new file mode 100644 index 0000000..9a9110c --- /dev/null +++ b/src/main/java/com/seoultech/sanEseo/weather/domain/WeatherData.java @@ -0,0 +1,16 @@ +package com.seoultech.sanEseo.weather.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class WeatherData { + private Float temperature; + private Float temperatureMax; + private Float temperatureMin; + private String precipitation; + private int humidity; +}