feat(fx): 환율 캐시 시각 노출 — updatedAt 응답 추가
Deploy / deploy (push) Failing after 15m14s

- FxService: Cached 레코드에 fetchedAt(Instant) 추가, getCachedAt() 공개
- FxController: GET /api/fx/rate 응답에 updatedAt(ISO 문자열) 포함

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-07-05 14:51:13 +09:00
parent c5f16795ef
commit 655af7af63
2 changed files with 14 additions and 2 deletions
@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
@@ -28,10 +29,12 @@ public class FxController {
public Map<String, Object> rate(@RequestParam String from,
@RequestParam(defaultValue = "KRW") String to) {
BigDecimal rate = fxService.getRate(from, to);
Instant updatedAt = fxService.getCachedAt(from);
Map<String, Object> res = new HashMap<>();
res.put("from", from == null ? null : from.toUpperCase());
res.put("to", to == null ? null : to.toUpperCase());
res.put("rate", rate); // 조회 실패 시 null
res.put("updatedAt", updatedAt != null ? updatedAt.toString() : null);
return res;
}
}
@@ -6,6 +6,7 @@ import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -21,9 +22,17 @@ public class FxService {
private static final String URL = "https://open.er-api.com/v6/latest/{base}";
private final RestClient restClient = RestClient.builder().build();
private record Cached(LocalDate date, JsonNode rates) {}
private record Cached(LocalDate date, Instant fetchedAt, JsonNode rates) {}
private final Map<String, Cached> cache = new ConcurrentHashMap<>();
/** from 통화 1단위 → to 통화 환율. 조회 실패 시 null. */
/** 해당 통화 환율이 마지막으로 캐시된 시각. 캐시 없으면 null. */
public Instant getCachedAt(String from) {
if (from == null) return null;
Cached c = cache.get(from.trim().toUpperCase());
return c != null ? c.fetchedAt() : null;
}
/** from 통화 1단위 → to 통화 환율. 조회 실패 시 null. */
public BigDecimal getRate(String from, String to) {
if (from == null || to == null) return null;
@@ -54,7 +63,7 @@ public class FxService {
if (rates.isMissingNode() || !rates.isObject()) {
return c != null ? c.rates() : null;
}
cache.put(base, new Cached(today, rates));
cache.put(base, new Cached(today, Instant.now(), rates));
return rates;
} catch (Exception e) {
log.warn("환율 조회 실패 base={}: {}", base, e.toString());