From 4427abb88c7c301ebb8141f45e79815b707abd2c Mon Sep 17 00:00:00 2001 From: Dayeon-Hong Date: Sat, 13 Jul 2024 19:38:38 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix=20:=20weather=20API=20METHOD=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../country/controller/CountryController.java | 34 ++++++++++--- .../dto/response/DailyWeatherResponse.java | 1 - .../repository/ExchageRateRespository.java | 2 + .../country/service/CountryService.java | 50 +++++++++---------- backend/src/main/resources/application.yml | 3 +- 5 files changed, 56 insertions(+), 34 deletions(-) create mode 100644 backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java diff --git a/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java b/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java index f37df048..67e585dd 100644 --- a/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java +++ b/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java @@ -5,14 +5,17 @@ import com.isp.backend.domain.country.dto.response.LocationResponse; import com.isp.backend.domain.country.dto.response.WeatherResponse; import com.isp.backend.domain.country.service.CountryService; +import com.isp.backend.domain.country.service.ExchangeRateService; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.time.LocalTime; import java.util.List; +import java.util.Map; import java.util.Optional; @RestController @@ -20,8 +23,8 @@ @RequiredArgsConstructor public class CountryController { - @Autowired - private CountryService countryService; + private final CountryService countryService; + private final ExchangeRateService exchangeRateService ; /** 여행지 좌표 찾기 **/ @PostMapping("/locations") @@ -37,7 +40,7 @@ public ResponseEntity findLocation(@RequestBody LocationReques /** 현재 날씨 정보 가져오기 **/ - @GetMapping("/weather/current") + @PostMapping("/weather/current") public ResponseEntity getCurrentWeather(@RequestBody LocationRequest requestDTO) { String city = requestDTO.getCountry(); WeatherResponse weatherResponse = countryService.getCurrentWeather(city); @@ -46,7 +49,7 @@ public ResponseEntity getCurrentWeather(@RequestBody LocationRe /** 한 주 날씨 정보 조회 **/ - @GetMapping("/weather/weekly") + @PostMapping("/weather/weekly") public ResponseEntity> getWeeklyWeather(@RequestBody LocationRequest requestDTO) { String city = requestDTO.getCountry(); LocalTime requestedTime = LocalTime.of(12, 0); @@ -55,5 +58,24 @@ public ResponseEntity> getWeeklyWeather(@RequestBody } + /** 특정 기준 통화에 대한 모든 환율 정보 API **/ +// @GetMapping("/exchange-rates") +// public ResponseEntity getExchangeRates(@RequestParam String baseCurrency) { +// logger.info("Received request for exchange rates with base currency: {}", baseCurrency); +// +// try { +// Map exchangeRates = exchangeRateService.getExchangeRates(baseCurrency); +// return ResponseEntity.ok(exchangeRates); +// } catch (Exception e) { +// logger.error("Error fetching exchange rates", e); +// return ResponseEntity.badRequest().body(Map.of( +// "errorMessage", "환율 정보를 가져오는데 실패했습니다.", +// "httpStatus", "BAD_REQUEST", +// "code", null +// )); +// } +// } + + } \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/country/dto/response/DailyWeatherResponse.java b/backend/src/main/java/com/isp/backend/domain/country/dto/response/DailyWeatherResponse.java index 8e373de9..f2e96331 100644 --- a/backend/src/main/java/com/isp/backend/domain/country/dto/response/DailyWeatherResponse.java +++ b/backend/src/main/java/com/isp/backend/domain/country/dto/response/DailyWeatherResponse.java @@ -1,6 +1,5 @@ package com.isp.backend.domain.country.dto.response; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java b/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java new file mode 100644 index 00000000..5dab5ba3 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java @@ -0,0 +1,2 @@ +package com.isp.backend.domain.country.repository;public class ExchageRateRespository { +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/service/CountryService.java b/backend/src/main/java/com/isp/backend/domain/country/service/CountryService.java index 156fc845..ffe467dd 100644 --- a/backend/src/main/java/com/isp/backend/domain/country/service/CountryService.java +++ b/backend/src/main/java/com/isp/backend/domain/country/service/CountryService.java @@ -1,6 +1,5 @@ package com.isp.backend.domain.country.service; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.isp.backend.domain.country.dto.response.DailyWeatherResponse; @@ -33,7 +32,7 @@ public class CountryService { private CountryRepository countryRepository; @Value("${api-key.open-weather}") - private String apiKey; + private String weatherApiKey; private final RestTemplate restTemplate = new RestTemplate(); @@ -58,6 +57,7 @@ public Optional findLocationByCity(String city) { } + /** 여행지의 날씨 정보 가져오기 **/ public WeatherResponse getCurrentWeather(String city) { Optional findCountry = findCountryByCity(city); @@ -65,7 +65,7 @@ public WeatherResponse getCurrentWeather(String city) { double latitude = country.getLatitude(); double longitude = country.getLongitude(); - String url = String.format("http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s", latitude, longitude, apiKey); + String url = String.format("http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s", latitude, longitude, weatherApiKey); String jsonResponse = restTemplate.getForObject(url, String.class); ObjectMapper objectMapper = new ObjectMapper(); @@ -100,7 +100,8 @@ public WeatherResponse getCurrentWeather(String city) { } - /** 한주의 날씨 정보 조회 **/ + + /** 한 주의 날씨 정보 조회 **/ public List getWeeklyWeather(String city, LocalTime requestedTime) { Optional findCountry = findCountryByCity(city); Country country = findCountry.get(); @@ -108,7 +109,7 @@ public List getWeeklyWeather(String city, LocalTime reques double longitude = country.getLongitude(); List weeklyWeather = new ArrayList<>(); - String url = String.format("http://api.openweathermap.org/data/2.5/forecast?lat=%f&lon=%f&appid=%s", latitude, longitude, apiKey); + String url = String.format("http://api.openweathermap.org/data/2.5/forecast?lat=%f&lon=%f&appid=%s", latitude, longitude, weatherApiKey); String jsonResponse = restTemplate.getForObject(url, String.class); try { @@ -116,7 +117,7 @@ public List getWeeklyWeather(String city, LocalTime reques JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode forecastList = rootNode.path("list"); - Map> dailyTemperatures = new HashMap<>(); // 각 날짜의 온도 목록을 저장할 맵 + Map dailyWeatherMap = new HashMap<>(); // 각 날짜의 날씨 정보를 저장할 맵 for (JsonNode forecast : forecastList) { String dateTime = forecast.path("dt_txt").asText(); // 예측 시간 정보 @@ -127,29 +128,23 @@ public List getWeeklyWeather(String city, LocalTime reques String dateString = localDateTime.toLocalDate().toString(); double temperature = forecast.path("main").path("temp").asDouble(); // 온도 정보 - // 해당 날짜의 온도 목록에 추가 - dailyTemperatures.computeIfAbsent(dateString, k -> new ArrayList<>()).add(temperature); - } - } - - // 각 날짜의 평균 온도를 계산하여 DailyWeatherResponse 객체로 변환한 후 리스트에 추가 - dailyTemperatures.forEach((dateString, temperatures) -> { - double avgTemperature = temperatures.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); - String temperature = convertToCelsius(avgTemperature); - - // 아이콘 URL 가져오기 - String iconCode = forecastList.get(0).path("weather").get(0).path("icon").asText(); - String iconUrl = String.format(s3ImageService.get(S3BucketDirectory.WEATHER.getDirectory(), iconCode + ".png")); + // 해당 날짜의 온도 정보를 저장 (동일한 시간대에 여러 데이터가 있을 경우, 가장 첫 번째 데이터를 사용) + if (!dailyWeatherMap.containsKey(dateString)) { + // 아이콘 URL 가져오기 + String iconCode = forecast.path("weather").get(0).path("icon").asText(); + String iconUrl = String.format(s3ImageService.get(S3BucketDirectory.WEATHER.getDirectory(), iconCode + ".png")); - DailyWeatherResponse dailyWeather = new DailyWeatherResponse(); - dailyWeather.setDate(parseDayOfWeek(dateString)); - dailyWeather.setTemp(temperature); - dailyWeather.setIconUrl(iconUrl); + DailyWeatherResponse dailyWeather = new DailyWeatherResponse(); + dailyWeather.setDate(parseDayOfWeek(dateString)); + dailyWeather.setTemp(convertToCelsius(temperature)); + dailyWeather.setIconUrl(iconUrl); - weeklyWeather.add(dailyWeather); - }); + dailyWeatherMap.put(dateString, dailyWeather); + } + } + } - // 날짜 순서대로 정렬 + weeklyWeather.addAll(dailyWeatherMap.values()); weeklyWeather.sort(Comparator.comparing(DailyWeatherResponse::getDate)); } catch (IOException e) { @@ -161,6 +156,7 @@ public List getWeeklyWeather(String city, LocalTime reques } + /** 현지 시간으로 변환 **/ private String getLocalTime(int timezoneOffset) { Instant now = Instant.now(); @@ -171,6 +167,7 @@ private String getLocalTime(int timezoneOffset) { } + /** 온도를 섭씨로 변환 **/ private String convertToCelsius(double kelvin) { double celsius = kelvin - 273.15; @@ -179,6 +176,7 @@ private String convertToCelsius(double kelvin) { } + /** 날짜 문자열에서 요일을 파싱 **/ private String parseDayOfWeek(String dateString) { LocalDate localDate = LocalDate.parse(dateString); diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 87d1c333..c430f506 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -38,4 +38,5 @@ api-key: amadeus: accessKey: ${AMADEUS_ACCESS_KEY} secretKey: ${AMADEUS_SECRET_KEY} - open-weather: ${OPEN_WEATHER_API_KEY} \ No newline at end of file + open-weather: ${OPEN_WEATHER_API_KEY} + exchange-rate: ${EXCHANGE_RATE_API_KEY} \ No newline at end of file From 20fdc15e9dd545a56b4c6a0425cea6acb76fbce6 Mon Sep 17 00:00:00 2001 From: Dayeon-Hong Date: Sat, 13 Jul 2024 19:41:23 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix=20:=20=EC=BB=B4=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=EB=A1=9C,=20=ED=98=84=EC=9E=AC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20push?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/CurrencyCalculateRequest.java | 20 +++++++ .../domain/country/entity/ExchangeRate.java | 30 ++++++++++ .../repository/ExchageRateRespository.java | 8 ++- .../country/service/ExchangeRateService.java | 57 +++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/isp/backend/domain/country/dto/request/CurrencyCalculateRequest.java create mode 100644 backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java create mode 100644 backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java diff --git a/backend/src/main/java/com/isp/backend/domain/country/dto/request/CurrencyCalculateRequest.java b/backend/src/main/java/com/isp/backend/domain/country/dto/request/CurrencyCalculateRequest.java new file mode 100644 index 00000000..b9772314 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/dto/request/CurrencyCalculateRequest.java @@ -0,0 +1,20 @@ +package com.isp.backend.domain.country.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CurrencyCalculateRequest { + + private String baseCity; + + private double basePrice ; + + private String targetCity ; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java b/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java new file mode 100644 index 00000000..228bce07 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java @@ -0,0 +1,30 @@ +package com.isp.backend.domain.country.entity; + +import com.isp.backend.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import java.math.BigDecimal; + + +@Getter +@AllArgsConstructor +@Entity +@Builder +@NoArgsConstructor +@Table(name = "exchange_rate") +public class ExchangeRate extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String fromCurrency; + + private String toCurrency; + + private BigDecimal rate; + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java b/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java index 5dab5ba3..b79f39d0 100644 --- a/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java +++ b/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java @@ -1,2 +1,8 @@ -package com.isp.backend.domain.country.repository;public class ExchageRateRespository { +package com.isp.backend.domain.country.repository; + +import com.isp.backend.domain.country.entity.ExchangeRate; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExchageRateRespository extends JpaRepository { + } diff --git a/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java new file mode 100644 index 00000000..c2619055 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java @@ -0,0 +1,57 @@ +package com.isp.backend.domain.country.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +@Service +public class ExchangeRateService { + + + @Value("${api-key.exchange-rate}") + private String exchangeRateApiKey; + + private final String BASE_URL = "https://v6.exchangerate-api.com/v6"; + + /** 특정 기준 통화에 대한 모든 환율 정보 가져오기 **/ +// public Map getExchangeRates(String baseCurrency) { +// String url = BASE_URL + exchangeRateApiKey + "/latest/" + baseCurrency; +// logger.info("Requesting exchange rates for base currency: {}", baseCurrency); +// +// try { +// String jsonResponse = restTemplate.getForObject(url, String.class); +// ObjectMapper objectMapper = new ObjectMapper(); +// JsonNode rootNode = objectMapper.readTree(jsonResponse); +// logger.info("Received response: {}", jsonResponse); +// +// String result = rootNode.path("result").asText(); +// if (!"success".equals(result)) { +// throw new RuntimeException("API 요청 실패: " + result); +// } +// +// JsonNode conversionRates = rootNode.path("conversion_rates"); +// Map rates = new HashMap<>(); +// conversionRates.fields().forEachRemaining(entry -> { +// rates.put(entry.getKey(), entry.getValue().asDouble()); +// }); +// +// return rates; +// } catch (IOException e) { +// logger.error("Error parsing JSON response", e); +// throw new RuntimeException("환율 정보를 가져오는데 실패했습니다.", e); +// } +// } + + + +} From bdb99cf0424194b3abfcd16ee91d2730fd6e800f Mon Sep 17 00:00:00 2001 From: Dayeon-Hong Date: Sun, 14 Jul 2024 02:18:06 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat=20:=20=ED=99=98=EC=9C=A8=20=EA=B8=80?= =?UTF-8?q?=EB=A1=9C=EB=B2=8C=20=EC=97=90=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/isp/backend/global/exception/ErrorCode.java | 4 +++- .../openApi/ExchangeRateIsFailedException.java | 12 ++++++++++++ .../openApi/ExchangeRateSearchFailedException.java | 12 ++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateIsFailedException.java create mode 100644 backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateSearchFailedException.java diff --git a/backend/src/main/java/com/isp/backend/global/exception/ErrorCode.java b/backend/src/main/java/com/isp/backend/global/exception/ErrorCode.java index 1b64695c..5d7e1037 100644 --- a/backend/src/main/java/com/isp/backend/global/exception/ErrorCode.java +++ b/backend/src/main/java/com/isp/backend/global/exception/ErrorCode.java @@ -32,7 +32,9 @@ public enum ErrorCode { SKY_SCANNER_GENERATE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR,"F002", "스카이스캐너 URL을 생성할 수 없습니다."), FLIGHT_NOT_FOUND(HttpStatus.NOT_FOUND, "F003", "해당 id의 항공권을 찾을 수 없습니다."), NOT_YOUR_FLIGHT(HttpStatus.UNAUTHORIZED, "F004", "사용자의 항공권이 아닙니다"), - OPEN_WEATHER_SEARCH_FAILED(HttpStatus.NOT_FOUND,"F005", "날씨 정보 파싱에 실패하였습니다"); + OPEN_WEATHER_SEARCH_FAILED(HttpStatus.NOT_FOUND,"F005", "날씨 정보 파싱에 실패하였습니다"), + EXCHANGE_RATE_SEARCH_FAILED(HttpStatus.INTERNAL_SERVER_ERROR,"F006", "환율 요청을 가져오는 중 오류를 발생했습니다."), + EXCHANGE_RATE_IS_FAILED(HttpStatus.BAD_REQUEST,"F007", "환율 DB를 저장하던 중 오류가 발생하였습니다."); private HttpStatus status; diff --git a/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateIsFailedException.java b/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateIsFailedException.java new file mode 100644 index 00000000..084a7b6c --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateIsFailedException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.openApi; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class ExchangeRateIsFailedException extends CustomException { + + public ExchangeRateIsFailedException() { + super(ErrorCode.EXCHANGE_RATE_IS_FAILED); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateSearchFailedException.java b/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateSearchFailedException.java new file mode 100644 index 00000000..8704f299 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateSearchFailedException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.openApi; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class ExchangeRateSearchFailedException extends CustomException { + + public ExchangeRateSearchFailedException() { + super(ErrorCode.EXCHANGE_RATE_SEARCH_FAILED); + } + +} \ No newline at end of file From 4e7dab4a348116397d74472bda59ab9fac8da569 Mon Sep 17 00:00:00 2001 From: Dayeon-Hong Date: Sun, 14 Jul 2024 02:18:36 +0900 Subject: [PATCH 4/6] =?UTF-8?q?fix=20:=20=ED=99=98=EC=9C=A8=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20table=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/country/entity/ExchangeRate.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java b/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java index 228bce07..8b5e974e 100644 --- a/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java +++ b/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java @@ -2,18 +2,16 @@ import com.isp.backend.global.common.BaseEntity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; + import java.math.BigDecimal; @Getter +@Setter @AllArgsConstructor -@Entity -@Builder @NoArgsConstructor +@Entity @Table(name = "exchange_rate") public class ExchangeRate extends BaseEntity { @@ -21,10 +19,16 @@ public class ExchangeRate extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String fromCurrency; + private String baseCurrency; + + private String targetCurrency; - private String toCurrency; + private Double rate; - private BigDecimal rate; + public ExchangeRate(String baseCurrency, String targetCurrency, Double rate) { + this.baseCurrency = baseCurrency; + this.targetCurrency = targetCurrency; + this.rate = rate; + } } \ No newline at end of file From 80eb5f50162cb920096eb3c4df3c979dc0ea6ce7 Mon Sep 17 00:00:00 2001 From: Dayeon-Hong Date: Sun, 14 Jul 2024 02:20:15 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat=20:=20=ED=99=98=EC=9C=A8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20API=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/ExchangeRateResponse.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 backend/src/main/java/com/isp/backend/domain/country/dto/response/ExchangeRateResponse.java diff --git a/backend/src/main/java/com/isp/backend/domain/country/dto/response/ExchangeRateResponse.java b/backend/src/main/java/com/isp/backend/domain/country/dto/response/ExchangeRateResponse.java new file mode 100644 index 00000000..a766cec8 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/dto/response/ExchangeRateResponse.java @@ -0,0 +1,18 @@ +package com.isp.backend.domain.country.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ExchangeRateResponse { + private String baseCurrency; + private String targetCurrency; + private BigDecimal rate; +} From ffa951aac0d2d3d069b7019084f75f798ab052fc Mon Sep 17 00:00:00 2001 From: Dayeon-Hong Date: Sun, 14 Jul 2024 02:20:56 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat=20:=20=ED=99=98=EC=9C=A8=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A0=9C=EA=B3=B5=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../country/controller/CountryController.java | 35 ++--- .../country/mapper/ExchangeRateMapper.java | 22 +++ .../repository/ExchageRateRespository.java | 8 -- .../repository/ExchangeRateRepository.java | 9 ++ .../country/service/ExchangeRateService.java | 136 +++++++++++++----- 5 files changed, 147 insertions(+), 63 deletions(-) create mode 100644 backend/src/main/java/com/isp/backend/domain/country/mapper/ExchangeRateMapper.java delete mode 100644 backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java create mode 100644 backend/src/main/java/com/isp/backend/domain/country/repository/ExchangeRateRepository.java diff --git a/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java b/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java index 67e585dd..dbd4fe3a 100644 --- a/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java +++ b/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java @@ -2,8 +2,10 @@ import com.isp.backend.domain.country.dto.request.LocationRequest; import com.isp.backend.domain.country.dto.response.DailyWeatherResponse; +import com.isp.backend.domain.country.dto.response.ExchangeRateResponse; import com.isp.backend.domain.country.dto.response.LocationResponse; import com.isp.backend.domain.country.dto.response.WeatherResponse; +import com.isp.backend.domain.country.entity.ExchangeRate; import com.isp.backend.domain.country.service.CountryService; import com.isp.backend.domain.country.service.ExchangeRateService; import lombok.RequiredArgsConstructor; @@ -58,24 +60,23 @@ public ResponseEntity> getWeeklyWeather(@RequestBody } - /** 특정 기준 통화에 대한 모든 환율 정보 API **/ -// @GetMapping("/exchange-rates") -// public ResponseEntity getExchangeRates(@RequestParam String baseCurrency) { -// logger.info("Received request for exchange rates with base currency: {}", baseCurrency); -// -// try { -// Map exchangeRates = exchangeRateService.getExchangeRates(baseCurrency); -// return ResponseEntity.ok(exchangeRates); -// } catch (Exception e) { -// logger.error("Error fetching exchange rates", e); -// return ResponseEntity.badRequest().body(Map.of( -// "errorMessage", "환율 정보를 가져오는데 실패했습니다.", -// "httpStatus", "BAD_REQUEST", -// "code", null -// )); -// } -// } + /** 환율 정보 업데이트 API **/ + @GetMapping("/exchange-rates/update") + public String updateExchangeRate() { + try { + exchangeRateService.updateExchangeRates(); + return "updated Successfully"; + } catch (Exception e) { + return "failed to update exchange rates: " + e.getMessage(); + } + } + /** 환율 정보 조회 API **/ + @GetMapping("/exchange-rates") + public ResponseEntity> getAllExchangeRates() { + List exchangeRates = exchangeRateService.getAllExchangeRates(); + return ResponseEntity.ok(exchangeRates); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/country/mapper/ExchangeRateMapper.java b/backend/src/main/java/com/isp/backend/domain/country/mapper/ExchangeRateMapper.java new file mode 100644 index 00000000..d082e329 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/mapper/ExchangeRateMapper.java @@ -0,0 +1,22 @@ +package com.isp.backend.domain.country.mapper; + +import com.isp.backend.domain.country.dto.response.ExchangeRateResponse; +import com.isp.backend.domain.country.entity.ExchangeRate; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; + +@Component +@RequiredArgsConstructor +public class ExchangeRateMapper { + + public ExchangeRateResponse convertToDto(ExchangeRate exchangeRate) { + ExchangeRateResponse dto = new ExchangeRateResponse(); + dto.setBaseCurrency(exchangeRate.getBaseCurrency()); + dto.setTargetCurrency(exchangeRate.getTargetCurrency()); + dto.setRate(BigDecimal.valueOf(exchangeRate.getRate())); + return dto; + } + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java b/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java deleted file mode 100644 index b79f39d0..00000000 --- a/backend/src/main/java/com/isp/backend/domain/country/repository/ExchageRateRespository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.isp.backend.domain.country.repository; - -import com.isp.backend.domain.country.entity.ExchangeRate; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ExchageRateRespository extends JpaRepository { - -} diff --git a/backend/src/main/java/com/isp/backend/domain/country/repository/ExchangeRateRepository.java b/backend/src/main/java/com/isp/backend/domain/country/repository/ExchangeRateRepository.java new file mode 100644 index 00000000..aa4381d3 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/repository/ExchangeRateRepository.java @@ -0,0 +1,9 @@ +package com.isp.backend.domain.country.repository; + +import com.isp.backend.domain.country.entity.ExchangeRate; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExchangeRateRepository extends JpaRepository { + ExchangeRate findByBaseCurrencyAndTargetCurrency(String baseCurrency, String targetCurrency); + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java index c2619055..047d1cd7 100644 --- a/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java +++ b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java @@ -1,56 +1,116 @@ package com.isp.backend.domain.country.service; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.isp.backend.domain.country.dto.response.ExchangeRateResponse; +import com.isp.backend.domain.country.entity.ExchangeRate; +import com.isp.backend.domain.country.mapper.ExchangeRateMapper; +import com.isp.backend.domain.country.repository.ExchangeRateRepository; +import com.isp.backend.global.exception.openApi.ExchangeRateIsFailedException; +import com.isp.backend.global.exception.openApi.ExchangeRateSearchFailedException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; +import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; -import java.util.HashMap; -import java.util.Map; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @Service public class ExchangeRateService { + private final ExchangeRateRepository exchangeRateRepository; + private final ExchangeRateMapper exchangeRateMapper ; @Value("${api-key.exchange-rate}") private String exchangeRateApiKey; - private final String BASE_URL = "https://v6.exchangerate-api.com/v6"; - - /** 특정 기준 통화에 대한 모든 환율 정보 가져오기 **/ -// public Map getExchangeRates(String baseCurrency) { -// String url = BASE_URL + exchangeRateApiKey + "/latest/" + baseCurrency; -// logger.info("Requesting exchange rates for base currency: {}", baseCurrency); -// -// try { -// String jsonResponse = restTemplate.getForObject(url, String.class); -// ObjectMapper objectMapper = new ObjectMapper(); -// JsonNode rootNode = objectMapper.readTree(jsonResponse); -// logger.info("Received response: {}", jsonResponse); -// -// String result = rootNode.path("result").asText(); -// if (!"success".equals(result)) { -// throw new RuntimeException("API 요청 실패: " + result); -// } -// -// JsonNode conversionRates = rootNode.path("conversion_rates"); -// Map rates = new HashMap<>(); -// conversionRates.fields().forEachRemaining(entry -> { -// rates.put(entry.getKey(), entry.getValue().asDouble()); -// }); -// -// return rates; -// } catch (IOException e) { -// logger.error("Error parsing JSON response", e); -// throw new RuntimeException("환율 정보를 가져오는데 실패했습니다.", e); -// } -// } + @Autowired + public ExchangeRateService(ExchangeRateRepository exchangeRateRepository, ExchangeRateMapper exchangeRateMapper) { + this.exchangeRateRepository = exchangeRateRepository; + this.exchangeRateMapper = exchangeRateMapper; + } + + /** 환율 데이터 가져와서 업데이트 하는 API 메서드 **/ + @Transactional + public void updateExchangeRates() { + // 한국과 미국 통화 환율 비율 저장 + updateRatesForBaseCurrency("KRW"); + updateRatesForBaseCurrency("USD"); + } + + + /** 환율 데이터 가져와서 업데이트 하는 API 메서드 **/ + public List getAllExchangeRates() { + try { + // DB에서 모든 환율 데이터를 가져옴 + List exchangeRates = exchangeRateRepository.findAll(); + + // baseCurrency가 "KRW" 또는 "USD"인 데이터만 필터링하고 DTO로 변환 + return exchangeRates.stream() + .filter(rate -> ("KRW".equals(rate.getBaseCurrency()) || "USD".equals(rate.getBaseCurrency())) + && !rate.getBaseCurrency().equals(rate.getTargetCurrency())) + .map(exchangeRateMapper::convertToDto) + .collect(Collectors.toList()); + } catch (Exception e) { + throw new ExchangeRateIsFailedException() ; + } + } + + + /** 측정 baseCurrency 통화에 대해 업데이트 하는 메서드**/ + private void updateRatesForBaseCurrency(String baseCurrency) { + String exchangeRateAPI_URL = "https://v6.exchangerate-api.com/v6/" + exchangeRateApiKey + "/latest/" + baseCurrency; + + try { + URL url = new URL(exchangeRateAPI_URL); + HttpURLConnection request = (HttpURLConnection) url.openConnection(); + request.connect(); + + // API 응답 데이터를 JsonObject로 변환 + Gson gson = new Gson(); + JsonObject jsonobj = gson.fromJson(new InputStreamReader(request.getInputStream()), JsonObject.class); + + // API 응답 결과 + String req_result = jsonobj.get("result").getAsString(); + + if ("success".equals(req_result)) { + JsonObject conversionRates = jsonobj.getAsJsonObject("conversion_rates"); + for (String targetCurrency : conversionRates.keySet()) { + // 필요한 통화만 가져온다 + if (isSupportedCurrency(targetCurrency)) { + double rate = conversionRates.get(targetCurrency).getAsDouble(); + + // DB에서 환율 데이터 존재 여부 확인 + ExchangeRate existingRate = exchangeRateRepository.findByBaseCurrencyAndTargetCurrency(baseCurrency, targetCurrency); + + if (existingRate == null) { + ExchangeRate newExchangeRate = new ExchangeRate(baseCurrency, targetCurrency, rate); + exchangeRateRepository.save(newExchangeRate); + } else { + // 기록이 이미 존재하면, rate만 수정한다. + existingRate.setRate(rate); + exchangeRateRepository.save(existingRate); + } + } + } + } else { + throw new ExchangeRateSearchFailedException(); + } + } catch (Exception e) { + throw new ExchangeRateIsFailedException() ; + } + } + + /** 지원하는 통화만 가져오는 메서드 **/ + private boolean isSupportedCurrency(String currencyCode) { + return Set.of("JPY", "GBP", "EURO", "CHF", "CZK", "USD", "SGD", "TWD", "LAK", "MYR", "VND", "THB", "IDR", "PHP", "KRW").contains(currencyCode); + } +