Repository: prgrms-be-devcourse/NBE5-6-2-Team03
Pull request #17: Feature: 환율 API 연결 및 데이터 받아오기, 가장 최근 환율 꺼내오는 기능 추가
Author: login: hyeunS-P
Created: 2025-05-15T09:22:09Z | Merged: 2025-05-16T06:47:12Z

<!--
  템플릿은 아직 PR 작성이 익숙하지 않으신 분들을 위해서 제공하는 가이드입니다!
  리뷰어 또는 이 PR을 보게 될 다른 사람들이 이 PR을 보는데 참고할 수 있는 내용이 있다면 포함해서 작성해주시면 됩니다.
-->

## 📌 과제 설명 <!-- 어떤 걸 만들었는지 대략적으로 설명해주세요 -->
 - 환율 데이터 API로 23개 국가에 대한 환율 정보 받아옴
 - 환율 기준 날짜에 따라 원하는 통화의 가장 최근 환율 가져옴
 - 데이터가 원하는 방식으로 들어오지 않는 것에 대한 예외 처리

## 👩‍💻 요구 사항과 구현 내용 <!-- 기능을 Commit 별로 잘개 쪼개고, Commit 별로 설명해주세요 -->
- 환율 데이터를 수출입은행 API에서 받아옴
- 통화 코드, 통화 이름, 기준 환율, 외화->원화 가격, 원화 -> 외화 가격. 날짜 데이터 받아옴
- 영업일이 아니거나 11시 이전이라 당일에 대한 데이터 존재하지 않는다면 DB에 추가하지 않는 검증 로직 추가
- 통화 코드를 기준으로 가장 최근 환율을 가져오는 기능 추가
- 쿼리 annotation 보다 JPA형식에 맞춘 코드로 수정

## ✅ 피드백 반영사항  <!-- 지난 코드리뷰에서 고친 사항을 적어주세요. 재PR 시에만 사용해 주세요! (재PR 아닌 경우 삭제) -->

## ✅ PR 포인트 & 궁금한 점 <!-- 리뷰어 분들이 집중적으로 보셨으면 하는 내용을 적어주세요 -->
![image](https://github.com/user-attachments/assets/a79b3756-3fc6-4bdc-a707-45bce840de20)
이런식으로 데이터가 들어옵니다.

![image](https://github.com/user-attachments/assets/d3e52fed-5252-4c5f-898e-bbd49b2dbb71)
이렇게 JSON 형식으로 API 응답이 옵니다.

그리고 TODO로 작성해놓았긴 합니다만, 스케줄로 DB에 실행 시점에 추가하도록 했고 그 이후에 주기적으로 추가하도록 설정해두어서 하루에 한번 추가하고, 해당 부분은 주석처리 해 주시면 되겠습니다.


Diff:
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/Nbe562Team03Application.java b/src/main/java/grepp/NBE5_6_2_Team03/Nbe562Team03Application.java
index f67197f..bc1ada5 100644
--- a/src/main/java/grepp/NBE5_6_2_Team03/Nbe562Team03Application.java
+++ b/src/main/java/grepp/NBE5_6_2_Team03/Nbe562Team03Application.java
@@ -2,8 +2,10 @@
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 @SpringBootApplication
+@EnableScheduling
 public class Nbe562Team03Application {
 
 	public static void main(String[] args) {
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/Exchange.java b/src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/Exchange.java
deleted file mode 100644
index 086cbb3..0000000
--- a/src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/Exchange.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package grepp.NBE5_6_2_Team03.api.controller.exchange;
-
-public class Exchange {
-
-}
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java b/src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java
new file mode 100644
index 0000000..2186b18
--- /dev/null
+++ b/src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java
@@ -0,0 +1,24 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeResponse;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/exchange")
+public class ExchangeController {
+
+    private final ExchangeService exchangeService;
+
+    @GetMapping("/latest")
+    public ExchangeResponse getLatestRate(@RequestParam String curUnit){
+        return exchangeService.getLatest(curUnit);
+    }
+
+}
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeResponse.java b/src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeResponse.java
new file mode 100644
index 0000000..5a49098
--- /dev/null
+++ b/src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeResponse.java
@@ -0,0 +1,60 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+public class ExchangeResponse {
+
+    @JsonProperty("cur_unit")
+    private String curUnit;
+
+    @JsonProperty("cur_nm")
+    private String curName;
+
+    @JsonProperty("deal_bas_r")
+    private String baseRate;
+
+    @JsonProperty("ttb")
+    private String ttbRate;     // 외화 -> 원화
+
+    @JsonProperty("tts")
+    private String ttsRate;     // 원화 -> 외화
+
+    protected ExchangeResponse() {
+    }
+
+    @Builder
+    private ExchangeResponse(String curUnit, String curName, String baseRate, String ttbRate,
+        String ttsRate) {
+        this.curUnit = curUnit;
+        this.curName = curName;
+        this.baseRate = baseRate;
+        this.ttbRate = ttbRate;
+        this.ttsRate = ttsRate;
+    }
+
+    public static ExchangeResponse fromDto(ExchangeRateEntity entity) {
+        return ExchangeResponse.builder()
+            .curUnit(entity.getCurUnit())
+            .curName(entity.getCurName())
+            .baseRate(entity.getBaseRate())
+            .ttbRate(entity.getTtbRate())
+            .ttsRate(entity.getTtsRate())
+            .build();
+    }
+
+    public ExchangeRateEntity toEntity(String searchDate) {
+        return ExchangeRateEntity.builder()
+            .curUnit(this.curUnit)
+            .curName(this.curName)
+            .baseRate(this.baseRate)
+            .ttbRate(this.ttbRate)
+            .ttsRate(this.ttsRate)
+            .date(searchDate)
+            .build();
+    }
+
+}
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/entity/ExchangeRateEntity.java b/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/entity/ExchangeRateEntity.java
new file mode 100644
index 0000000..1ae46e7
--- /dev/null
+++ b/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/entity/ExchangeRateEntity.java
@@ -0,0 +1,40 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Builder;
+import lombok.Getter;
+
+@Table(name = "exchange_rate")
+@Getter
+@Entity
+public class ExchangeRateEntity {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    private String curUnit;
+    private String curName;
+    private String baseRate;
+    private String ttbRate;
+    private String ttsRate;
+    private String date;
+
+    protected ExchangeRateEntity() {
+    }
+
+    @Builder
+    private ExchangeRateEntity(String curUnit, String curName, String baseRate,
+        String ttbRate,
+        String ttsRate, String date) {
+        this.curUnit = curUnit;
+        this.curName = curName;
+        this.baseRate = baseRate;
+        this.ttbRate = ttbRate;
+        this.ttsRate = ttsRate;
+        this.date = date;
+    }
+}
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/repository/ExchangeRateRepository.java b/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/repository/ExchangeRateRepository.java
new file mode 100644
index 0000000..2e2ea6b
--- /dev/null
+++ b/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/repository/ExchangeRateRepository.java
@@ -0,0 +1,14 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.repository;
+
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import java.util.Optional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+public interface ExchangeRateRepository extends JpaRepository<ExchangeRateEntity, Long> {
+
+    @Query("SELECT e FROM ExchangeRateEntity e WHERE e.curUnit = :curUnit ORDER BY e.date DESC")
+    Optional<ExchangeRateEntity> findLatestByCurUnit(@Param("curUnit") String curUnit);
+
+}
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java b/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
new file mode 100644
index 0000000..d659e4b
--- /dev/null
+++ b/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
@@ -0,0 +1,43 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeResponse;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+
+    private boolean isEmptyExchange(ExchangeResponse[] exchanges){
+        return exchanges == null || exchanges.length == 0;
+    }
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+        log.info("Fetch exchange rates start");
+        try {
+            LocalDate today = LocalDate.now();
+            ExchangeResponse[] exchanges = exchangeService.getCurrentExchanges(today);
+
+            if (isEmptyExchange(exchanges)) {
+                log.info("Not Exist Exchange Rate Data Now");
+                return;
+            }
+
+            String formattedToday = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+            exchangeService.saveAllExchangeRates(exchanges,formattedToday);
+
+            log.info("Fetch exchange exchanges done");
+        } catch(Exception e){
+            log.error("Cannot get exchange rates", e);
+        }
+    }
+}
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/service/ExchangeService.java b/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/service/ExchangeService.java
new file mode 100644
index 0000000..8f132bd
--- /dev/null
+++ b/src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/service/ExchangeService.java
@@ -0,0 +1,60 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.service;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeResponse;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.server.ResponseStatusException;
+
+@Service
+@RequiredArgsConstructor
+public class ExchangeService {
+
+    private final RestTemplate restTemplate = new RestTemplate();
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @Value("${exchange.api.key}")
+    private String apiKey;
+
+    @Value("${exchange.api.url}")
+    private String apiUrl;
+
+    public ExchangeResponse[] getCurrentExchanges(LocalDate today) {
+        String accessUrl = createAccessUrl(today);
+        ResponseEntity<ExchangeResponse[]> exchangeResponses = restTemplate.getForEntity(accessUrl, ExchangeResponse[].class);
+        return exchangeResponses.getBody();
+    }
+
+    public ExchangeResponse getLatest(String curUnit){
+        ExchangeRateEntity entity = exchangeRateRepository.findLatestByCurUnit(curUnit)
+            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+
+        return ExchangeResponse.fromDto(entity);
+    }
+
+    public void saveAllExchangeRates(ExchangeResponse[] exchanges, String formattedToday) {
+        List<ExchangeRateEntity> entities = convertToEntities(exchanges, formattedToday);
+        exchangeRateRepository.saveAll(entities);
+    }
+
+    private String createAccessUrl(LocalDate today) {
+        String formattedToday = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        return String.format("%s?authkey=%s&apikey=%s", apiUrl, apiKey,formattedToday);
+    }
+
+    private List<ExchangeRateEntity> convertToEntities(ExchangeResponse[] exchanges, String formattedToday) {
+        return Arrays.stream(exchanges)
+            .map(request -> request.toEntity(formattedToday))
+            .collect(Collectors.toList());
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/global/handler/CustomLoginSuccessHandler.java b/src/main/java/grepp/NBE5_6_2_Team03/global/config/security/CustomLoginSuccessHandler.java
similarity index 82%
rename from src/main/java/grepp/NBE5_6_2_Team03/global/handler/CustomLoginSuccessHandler.java
rename to src/main/java/grepp/NBE5_6_2_Team03/global/config/security/CustomLoginSuccessHandler.java
index a76d93a..fb23bad 100644
--- a/src/main/java/grepp/NBE5_6_2_Team03/global/handler/CustomLoginSuccessHandler.java
+++ b/src/main/java/grepp/NBE5_6_2_Team03/global/config/security/CustomLoginSuccessHandler.java
@@ -1,4 +1,4 @@
-package grepp.NBE5_6_2_Team03.global.handler;
+package grepp.NBE5_6_2_Team03.global.config.security;
 
 import grepp.NBE5_6_2_Team03.domain.user.Role;
 import jakarta.servlet.ServletException;
@@ -12,9 +12,9 @@
 @Component
 public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {
 
-    private final String ADMIN_REDIRECT_URL = "/admin/dashboard";
-    private final String USER_REDIRECT_URL = "/users/home";
-    private final String DEFAULT_REDIRECT_URL = "/";
+    private static final String ADMIN_REDIRECT_URL = "/admin/dashboard";
+    private static final String USER_REDIRECT_URL = "/users/home";
+    private static final String DEFAULT_REDIRECT_URL = "/";
 
     @Override
     public void onAuthenticationSuccess(HttpServletRequest request,
diff --git a/src/main/java/grepp/NBE5_6_2_Team03/global/config/security/SecurityConfig.java b/src/main/java/grepp/NBE5_6_2_Team03/global/config/security/SecurityConfig.java
index 7747df6..c52eae3 100644
--- a/src/main/java/grepp/NBE5_6_2_Team03/global/config/security/SecurityConfig.java
+++ b/src/main/java/grepp/NBE5_6_2_Team03/global/config/security/SecurityConfig.java
@@ -1,7 +1,6 @@
 package grepp.NBE5_6_2_Team03.global.config.security;
 
 import grepp.NBE5_6_2_Team03.domain.user.service.CustomUserDetailsService;
-import grepp.NBE5_6_2_Team03.global.handler.CustomLoginSuccessHandler;
 import lombok.RequiredArgsConstructor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -39,7 +38,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                 .authorizeHttpRequests(
                         (auth) -> auth
                                 .requestMatchers("/", "/users/**").permitAll()
-                                .requestMatchers("/css/**", "/assets/**", "/js/**","/api/ai/recommend").permitAll()
+                                .requestMatchers("/css/**", "/assets/**", "/js/**","/api/ai/**","/trip-chat").permitAll()
+                                .requestMatchers("/api/exchange/**").permitAll()
+
                                 .anyRequest().authenticated()
                 );
 


Reviews:
user: login: Tokwasp
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T10:29:11Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T12:44:54Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T12:46:40Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T12:46:47Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T12:52:08Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T13:08:02Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T13:08:56Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T13:09:48Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T13:29:51Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T13:37:18Z
user: login: Tokwasp
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T13:42:59Z
user: login: Tokwasp
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T13:56:30Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T00:12:30Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T00:16:17Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T00:44:52Z
user: login: NoviceWyatt19
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-16T01:17:28Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T01:27:36Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T01:28:49Z
user: login: doyun-a
body: 23국가 환율을 가져오는데 테이블에 207행까지 있는게  하루에 한 번 23개씩추가해서 
나중에 평균값을 알기위해서 그런건가요??
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T02:15:08Z
user: login: Tokwasp
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T02:21:02Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T02:27:16Z
user: login: hyeunS-P
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T02:27:29Z
user: login: pia01190
body: 코드 잘 짜신것 같습니다.
환율을 string으로 가져오신 이유는 api 호출할때 string으로 받아와서일까요?
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-16T02:41:46Z
user: login: Tokwasp
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T02:47:00Z
user: login: Tokwasp
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:35:39Z

Review comments:
diff_hunk: @@ -0,0 +1,34 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.global.mapper.ExchangeMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+@Slf4j
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java
user: login: Tokwasp
body: 테스트 용도 일까요? 테스트 용도 라면 삭제 하셔도 될것 같습니다.
created_at: 2025-05-15T09:28:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,34 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.global.mapper.ExchangeMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/exchange")
+public class ExchangeController {
+
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @GetMapping("/latest")
+    public ExchangeDto getLatest(@RequestParam String code){
+        ExchangeRateEntity entity = exchangeRateRepository.findTopByCurUnitOrderByDateDesc(code)
+            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+
+        log.info("{}", entity);
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java
user: login: Tokwasp
body: 마찬가지 입니다
created_at: 2025-05-15T09:28:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,24 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeDto.java
user: login: Tokwasp
body: Data 어노테이션을 사용 하지 않고 필요한 어노테이션만 사용하는 것은 어떻게 생각하시나요?
created_at: 2025-05-15T09:29:32Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,34 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Entity
+@Table(name = "exchange_rate")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ToString
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/entity/ExchangeRateEntity.java
user: login: Tokwasp
body: ㅠㅠ
created_at: 2025-05-15T09:29:54Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,12 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.repository;
+
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import java.util.Optional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+public interface ExchangeRateRepository extends JpaRepository<ExchangeRateEntity, Long> {
+
+    Optional<ExchangeRateEntity> findTopByCurUnitOrderByDateDesc(String curUnit);
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/repository/ExchangeRateRepository.java
user: login: Tokwasp
body: 쿼리 이름이 너무 길이서 이해가 어려운것 같아요
@Query 쿼리를 직접 작성하는 것은 어떻게 생각하시나요?
created_at: 2025-05-15T09:31:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: Tokwasp
body: 사용하는 로그 어노테이션 일까요?
created_at: 2025-05-15T09:33:40Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+        try{
+            log.info("Fetch exchange rates start");
+
+            ExchangeDto[] rates = exchangeService.getCurrentExchanges();
+
+            if(rates == null || rates.length == 0) {
+                log.info("현재 환율 데이터가 없습니다.");
+                return;
+            }
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: Tokwasp
body: 환율 데이터가 존재하지 않는다는걸 추상화해서 표현하면 좋을것 같아요
reate == nul || 길이 == 0 은 너무 구체적이라고 생각합니다.
created_at: 2025-05-15T09:34:58Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+        try{
+            log.info("Fetch exchange rates start");
+
+            ExchangeDto[] rates = exchangeService.getCurrentExchanges();
+
+            if(rates == null || rates.length == 0) {
+                log.info("현재 환율 데이터가 없습니다.");
+                return;
+            }
+
+            String searchDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+            Arrays.stream(rates).forEach(dto -> {
+            ExchangeRateEntity entity = ExchangeRateEntity.builder()
+                .curUnit(dto.getCurUnit())
+                .curName(dto.getCurName())
+                .baseRate(dto.getBaseRate())
+                .ttbRate(dto.getTtbRate())
+                .ttsRate(dto.getTtsRate())
+                .date(searchDate)
+                .build();
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: Tokwasp
body: RateEntity 만드는 과정을 따로 추출 하거나
Dto 내부에서 Entity를 만들면 가독성이 더 좋아질것 같아요!
created_at: 2025-05-15T09:37:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,28 @@
+package grepp.NBE5_6_2_Team03.global.mapper;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+
+public class ExchangeMapper {
+
+    public static ExchangeRateEntity toEntity(ExchangeDto dto) {
+        return ExchangeRateEntity.builder()
+            .curUnit(dto.getCurUnit())
+            .curName(dto.getCurName())
+            .baseRate(dto.getBaseRate())
+            .ttbRate(dto.getTtbRate())
+            .ttsRate(dto.getTtsRate())
+            .build();
+    }
+
+    public static ExchangeDto toDto(ExchangeRateEntity entity) {
+        ExchangeDto dto = new ExchangeDto();
+        dto.setCurUnit(entity.getCurUnit());
+        dto.setCurName(entity.getCurName());
+        dto.setBaseRate(entity.getBaseRate());
+        dto.setTtbRate(entity.getTtbRate());
+        dto.setTtsRate(entity.getTtsRate());
+        return dto;
+    }
+
+}
path: src/main/java/grepp/NBE5_6_2_Team03/global/mapper/ExchangeMapper.java
user: login: Tokwasp
body: 매퍼란 클래스는 없어도 될것 같다고 생각 하는데 어떻게 생각하시나요?
created_at: 2025-05-15T10:29:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,12 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.repository;
+
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import java.util.Optional;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+public interface ExchangeRateRepository extends JpaRepository<ExchangeRateEntity, Long> {
+
+    Optional<ExchangeRateEntity> findTopByCurUnitOrderByDateDesc(String curUnit);
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/repository/ExchangeRateRepository.java
user: login: hyeunS-P
body: 네 쿼리 직접 작성하는 것으로 수정했습니다.
created_at: 2025-05-15T12:44:54Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,34 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.global.mapper.ExchangeMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+@Slf4j
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java
user: login: hyeunS-P
body: 로그를 확인해보려는 어노테이션이었으나 삭제해도 될 것 같습니다.
created_at: 2025-05-15T12:46:40Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,34 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.global.mapper.ExchangeMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/exchange")
+public class ExchangeController {
+
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @GetMapping("/latest")
+    public ExchangeDto getLatest(@RequestParam String code){
+        ExchangeRateEntity entity = exchangeRateRepository.findTopByCurUnitOrderByDateDesc(code)
+            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+
+        log.info("{}", entity);
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java
user: login: hyeunS-P
body: 삭제 처리 했습니다.
created_at: 2025-05-15T12:46:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: hyeunS-P
body: 이건 좀 생각해봐야 할 것 같은게, 원래 11시 01분에 데이터를 가져오는 것을 고려했는데 그럼 서버 쪽에 로그를 남겨주는 것이 좋지 않을까요?
created_at: 2025-05-15T12:52:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+        try{
+            log.info("Fetch exchange rates start");
+
+            ExchangeDto[] rates = exchangeService.getCurrentExchanges();
+
+            if(rates == null || rates.length == 0) {
+                log.info("현재 환율 데이터가 없습니다.");
+                return;
+            }
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: hyeunS-P
body: 네 추가했습니다.
created_at: 2025-05-15T13:08:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,28 @@
+package grepp.NBE5_6_2_Team03.global.mapper;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+
+public class ExchangeMapper {
+
+    public static ExchangeRateEntity toEntity(ExchangeDto dto) {
+        return ExchangeRateEntity.builder()
+            .curUnit(dto.getCurUnit())
+            .curName(dto.getCurName())
+            .baseRate(dto.getBaseRate())
+            .ttbRate(dto.getTtbRate())
+            .ttsRate(dto.getTtsRate())
+            .build();
+    }
+
+    public static ExchangeDto toDto(ExchangeRateEntity entity) {
+        ExchangeDto dto = new ExchangeDto();
+        dto.setCurUnit(entity.getCurUnit());
+        dto.setCurName(entity.getCurName());
+        dto.setBaseRate(entity.getBaseRate());
+        dto.setTtbRate(entity.getTtbRate());
+        dto.setTtsRate(entity.getTtsRate());
+        return dto;
+    }
+
+}
path: src/main/java/grepp/NBE5_6_2_Team03/global/mapper/ExchangeMapper.java
user: login: hyeunS-P
body: toEntity 메서드는 사용하고 있지 않아서 삭제했고, toDto는 Dto 클래스 내부에 넣어놓았습니다.
created_at: 2025-05-15T13:08:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,34 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Entity
+@Table(name = "exchange_rate")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ToString
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/entity/ExchangeRateEntity.java
user: login: hyeunS-P
body: 이게 저도 쓰면서 참 고민이 많은 지점인데.. 일단 ToString은 빠져도 괜찮은게 맞는데, 나머지는 다 사용되는 곳이 있어서...

created_at: 2025-05-15T13:09:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+        try{
+            log.info("Fetch exchange rates start");
+
+            ExchangeDto[] rates = exchangeService.getCurrentExchanges();
+
+            if(rates == null || rates.length == 0) {
+                log.info("현재 환율 데이터가 없습니다.");
+                return;
+            }
+
+            String searchDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+            Arrays.stream(rates).forEach(dto -> {
+            ExchangeRateEntity entity = ExchangeRateEntity.builder()
+                .curUnit(dto.getCurUnit())
+                .curName(dto.getCurName())
+                .baseRate(dto.getBaseRate())
+                .ttbRate(dto.getTtbRate())
+                .ttsRate(dto.getTtsRate())
+                .date(searchDate)
+                .build();
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: hyeunS-P
body: Dto 내부에서 searchDate를 매개변수로 받아서 추가 가능하게 메서드 리팩터링 했습니다.
created_at: 2025-05-15T13:29:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,24 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeDto.java
user: login: hyeunS-P
body: Dto 클래스에서는 사용해도 크게 문제되지 않는다고 해서 사용하긴 했습니다만.. 일단 Getter Setter NoArgConstructor만 붙여놓겠습니다.
created_at: 2025-05-15T13:37:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: Tokwasp
body: 그럼 있어도 괜찮을것 같아요!
created_at: 2025-05-15T13:42:59Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/exchange")
+public class ExchangeController {
+
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @GetMapping("/latest")
+    public ExchangeDto getLatest(@RequestParam String code){
+        ExchangeRateEntity entity = exchangeRateRepository.findLatestByCurUnit(code)
+            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+
+        return ExchangeDto.toDto(entity);
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java
user: login: Tokwasp
body: 컨트롤러 엔티티 의존성이 있는 문제가 있습니다.
Repository에서 바로 Dto로 조회 하거나 혹은 서비스를 두시는 것은 어떤가요?
created_at: 2025-05-15T13:45:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,49 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter @Setter
+@NoArgsConstructor
+public class ExchangeDto {
+
+    @JsonProperty("cur_unit")
+    private String curUnit;
+
+    @JsonProperty("cur_nm")
+    private String curName;
+
+    @JsonProperty("deal_bas_r")
+    private String baseRate;
+
+    @JsonProperty("ttb")
+    private String ttbRate;     // 외화 -> 원화
+
+    @JsonProperty("tts")
+    private String ttsRate;     // 원화 -> 외화
+
+    public static ExchangeDto toDto(ExchangeRateEntity entity) {
+        ExchangeDto dto = new ExchangeDto();
+        dto.setCurUnit(entity.getCurUnit());
+        dto.setCurName(entity.getCurName());
+        dto.setBaseRate(entity.getBaseRate());
+        dto.setTtbRate(entity.getTtbRate());
+        dto.setTtsRate(entity.getTtsRate());
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeDto.java
user: login: Tokwasp
body: 빌더로 만들어주시면 좀더 좋을듯 합니다 만약 setter가 여기서만 사용된다면 지우면 좋을것 같아요!
created_at: 2025-05-15T13:46:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+
+        try {
+            log.info("Fetch exchange rates start");
+
+            ExchangeDto[] rates = exchangeService.getCurrentExchanges();
+
+            boolean isExistData = rates == null || rates.length == 0;
+
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: Tokwasp
body: 현재도 가독성이 좋지 않은 문제가 있는데 추상화 하는 방법은 안될까요?
ex) isExistExchange(reate);
created_at: 2025-05-15T13:48:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+
+        try {
+            log.info("Fetch exchange rates start");
+
+            ExchangeDto[] rates = exchangeService.getCurrentExchanges();
+
+            boolean isExistData = rates == null || rates.length == 0;
+
+            if (isExistData) {
+                log.info("Not Exist Exchange Rate Data Now");
+                return;
+            }
+
+            String searchDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+            Arrays.stream(rates)
+                .map(dto -> dto.toEntity(searchDate))
+                .forEach(exchangeRateRepository::save);
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: Tokwasp
body: request -> Entity 변환과 저장이 같이 일어나고 있습니다. 둘을 분리 하면 명확 하지 않을까요?
List<Entity> entities = convertToEntity(rates);
exchangeRateRepository.saveAll(entities);
created_at: 2025-05-15T13:52:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,33 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.service;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+@RequiredArgsConstructor
+public class ExchangeService {
+
+    @Value("${exchange.api.key}")
+    private String apiKey;
+
+    @Value("${exchange.api.url}")
+    private String apiUrl;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    public ExchangeDto[] getCurrentExchanges() {
+        String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        String url = String.format("%s?authkey=%s&searchdate=%s&data=AP01",apiUrl,apiKey,today);
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/service/ExchangeService.java
user: login: Tokwasp
body: 만약 테스트코드를 짠다면 LocalDate.now()를 비지니스 코드에서 사용하고 있기 때문에
현재 날짜로만 테스트를 할수 있을것 같아요. 파라미터로 오늘 날짜를 받아오는것은 어떤가요?
created_at: 2025-05-15T13:56:25Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,30 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/exchange")
+public class ExchangeController {
+
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @GetMapping("/latest")
+    public ExchangeDto getLatest(@RequestParam String code){
+        ExchangeRateEntity entity = exchangeRateRepository.findLatestByCurUnit(code)
+            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+
+        return ExchangeDto.toDto(entity);
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java
user: login: hyeunS-P
body: 네 서비스 쪽으로 로직을 뺴놓고 컨트롤러에서 받아오기만 하는 형식으로 수정해보겠습니다.
created_at: 2025-05-16T00:12:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,49 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter @Setter
+@NoArgsConstructor
+public class ExchangeDto {
+
+    @JsonProperty("cur_unit")
+    private String curUnit;
+
+    @JsonProperty("cur_nm")
+    private String curName;
+
+    @JsonProperty("deal_bas_r")
+    private String baseRate;
+
+    @JsonProperty("ttb")
+    private String ttbRate;     // 외화 -> 원화
+
+    @JsonProperty("tts")
+    private String ttsRate;     // 원화 -> 외화
+
+    public static ExchangeDto toDto(ExchangeRateEntity entity) {
+        ExchangeDto dto = new ExchangeDto();
+        dto.setCurUnit(entity.getCurUnit());
+        dto.setCurName(entity.getCurName());
+        dto.setBaseRate(entity.getBaseRate());
+        dto.setTtbRate(entity.getTtbRate());
+        dto.setTtsRate(entity.getTtsRate());
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeDto.java
user: login: hyeunS-P
body: setter를 없애고 빌더로 변경했습니다. 이 과정에서 setter 생성자와 Builder 어노테이션이 추가 되는데 dto에 setter를 사용하지 않는 것이 유의미 하기 때문에 변경하는 것이 맞을까요?
created_at: 2025-05-16T00:16:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+
+        try {
+            log.info("Fetch exchange rates start");
+
+            ExchangeDto[] rates = exchangeService.getCurrentExchanges();
+
+            boolean isExistData = rates == null || rates.length == 0;
+
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: hyeunS-P
body: 음.. 수정을 했습니다만 이 방향이 맞는건지 모르겠네요
created_at: 2025-05-16T00:44:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.repository.ExchangeRateRepository;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+    private final ExchangeRateRepository exchangeRateRepository;
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+
+        try {
+            log.info("Fetch exchange rates start");
+
+            ExchangeDto[] rates = exchangeService.getCurrentExchanges();
+
+            boolean isExistData = rates == null || rates.length == 0;
+
+            if (isExistData) {
+                log.info("Not Exist Exchange Rate Data Now");
+                return;
+            }
+
+            String searchDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+            Arrays.stream(rates)
+                .map(dto -> dto.toEntity(searchDate))
+                .forEach(exchangeRateRepository::save);
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: hyeunS-P
body: 분리 해 두었습니다.
created_at: 2025-05-16T01:27:36Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,33 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.service;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+@RequiredArgsConstructor
+public class ExchangeService {
+
+    @Value("${exchange.api.key}")
+    private String apiKey;
+
+    @Value("${exchange.api.url}")
+    private String apiUrl;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    public ExchangeDto[] getCurrentExchanges() {
+        String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        String url = String.format("%s?authkey=%s&searchdate=%s&data=AP01",apiUrl,apiKey,today);
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/service/ExchangeService.java
user: login: hyeunS-P
body: 파라미터로 받아오도록 변경했습니다.
created_at: 2025-05-16T01:28:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,25 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/exchange")
+public class ExchangeController {
+
+    private final ExchangeService exchangeService;
+
+    @GetMapping("/latest")
+    public ExchangeDto getLatestRate(@RequestParam String code){
+
+        return exchangeService.getLatest(code);
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/ExchangeController.java
user: login: Tokwasp
body: 줄바꿈을 안해도 될것 같습니다.
created_at: 2025-05-16T02:05:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter @Builder
+@AllArgsConstructor
+@NoArgsConstructor
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeDto.java
user: login: Tokwasp
body: 어노테이션 대신 직접 생성자를 만들면 어노테이션도 줄어들어 가독성이 좋다고 생각합니다.
created_at: 2025-05-16T02:06:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Builder;
+import lombok.Getter;
+
+@Table(name = "exchange_rate")
+@Getter @Entity
+@Builder
+public class ExchangeRateEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    private String curUnit;
+    private String curName;
+    private String baseRate;
+    private String ttbRate;
+    private String ttsRate;
+    private String date;
+
+    public ExchangeRateEntity() {
+
+    }
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/entity/ExchangeRateEntity.java
user: login: Tokwasp
body: 생성자는 Protected로 해야 한다고 생각 합니다. public이면 다른 패키지에서도 해당 객체를 생성할 수 있습니다.
created_at: 2025-05-16T02:08:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter @Builder
+@AllArgsConstructor
+@NoArgsConstructor
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeDto.java
user: login: hyeunS-P
body: 수정했습니다
created_at: 2025-05-16T02:27:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,43 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Builder;
+import lombok.Getter;
+
+@Table(name = "exchange_rate")
+@Getter @Entity
+@Builder
+public class ExchangeRateEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    private String curUnit;
+    private String curName;
+    private String baseRate;
+    private String ttbRate;
+    private String ttsRate;
+    private String date;
+
+    public ExchangeRateEntity() {
+
+    }
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/entity/ExchangeRateEntity.java
user: login: hyeunS-P
body: 수정했습니다!
created_at: 2025-05-16T02:27:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,60 @@
+package grepp.NBE5_6_2_Team03.api.controller.exchange.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import grepp.NBE5_6_2_Team03.domain.exchange.entity.ExchangeRateEntity;
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+public class ExchangeDto {
+
path: src/main/java/grepp/NBE5_6_2_Team03/api/controller/exchange/dto/ExchangeDto.java
user: login: Tokwasp
body: 요청인지 응답인지 명확하게 바꾸면 좋을것 같습니다.
ex) request, response
created_at: 2025-05-16T02:42:28Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,42 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Builder;
+import lombok.Getter;
+
+@Table(name = "exchange_rate")
+@Getter @Entity
+public class ExchangeRateEntity {
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/entity/ExchangeRateEntity.java
user: login: Tokwasp
body: @Entity 어노테이션이 중요한 정보라 밑줄에 두시면 좋을것 같습니다.
created_at: 2025-05-16T02:42:59Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,48 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+
+    private boolean isExistExchange(ExchangeDto[] rates){
+        return rates == null || rates.length == 0;
+    }
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+
+        try {
+            log.info("Fetch exchange rates start");
+
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: Tokwasp
body: 로그가 try catch 밖에 있어도 되지않을까요?
created_at: 2025-05-16T02:43:32Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,48 @@
+package grepp.NBE5_6_2_Team03.domain.exchange.scheduler;
+
+import grepp.NBE5_6_2_Team03.api.controller.exchange.dto.ExchangeDto;
+import grepp.NBE5_6_2_Team03.domain.exchange.service.ExchangeService;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ExchangeRateScheduler {
+
+    private final ExchangeService exchangeService;
+
+    private boolean isExistExchange(ExchangeDto[] rates){
+        return rates == null || rates.length == 0;
+    }
+
+    @Scheduled(initialDelay = 0, fixedRate=1000000)     // TODO 이거 한번 데이터 받은 이후에 주석처리 해야합니다.
+    public void fetchExchangeRates() {
+
+        try {
+            log.info("Fetch exchange rates start");
+
+            LocalDate today = LocalDate.now();
+            ExchangeDto[] rates = exchangeService.getCurrentExchanges(today);
+
+            if (isExistExchange(rates)) {
+                log.info("Not Exist Exchange Rate Data Now");
+                return;
+            }
+
+            String searchDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
path: src/main/java/grepp/NBE5_6_2_Team03/domain/exchange/scheduler/ExchangeRateScheduler.java
user: login: Tokwasp
body: totady로 현재 날짜를 받아오고 있기 때문에 totay.format으로 하는것이 좋다고 생각하는데
따로 now를 선언 하신 이유가 있을까요?
created_at: 2025-05-16T02:45:12Z
author_association: COLLABORATOR

PR comments:
user: login: NoviceWyatt19
created_at: 2025-05-16T00:08:55Z
body: 저도 Controller에서 Entity를 사용하는 것에 대해 DTO로 바꾸는 게 좋을 것같단 말씀을 드리고 싶었는데 먼저 피드백을 드렸네요
저는 그것 이외엔 잘 짜셨다고 생각했습니다.
author_association: COLLABORATOR
user: login: hyeunS-P
created_at: 2025-05-16T01:29:25Z
body: 추가적으로 스케줄러도 컨트롤러라는 이야기를 들어서, 이 부분에 엔티티 의존을 서비스로 옮겨 놓았습니다.
author_association: COLLABORATOR
user: login: hyeunS-P
created_at: 2025-05-16T02:28:36Z
body: > 23국가 환율을 가져오는데 테이블에 207행까지 있는게 하루에 한 번 23개씩추가해서 나중에 평균값을 알기위해서 그런건가요??

환율 가져오는 기능이 제대로 동작하는지 확인하기 위해 몇번 가져와서 그렇습니다.
author_association: COLLABORATOR
user: login: hyeunS-P
created_at: 2025-05-16T02:43:42Z
body: > 코드 잘 짜신것 같습니다. 환율을 string으로 가져오신 이유는 api 호출할때 string으로 받아와서일까요?

네 받아오는 타입이 String인데, 이거 정산 할 때 숫자로 변환하는 과정 필요합니다.
author_association: COLLABORATOR