|
|
@@ -7,11 +7,13 @@ namespace economy.Models.Price.Global
|
|
|
{
|
|
|
private readonly Alpha_API _alphaAPI;
|
|
|
private readonly IMemoryCache _cache;
|
|
|
+ private readonly EconomyContext? _context;
|
|
|
|
|
|
- public GlobalModel(Alpha_API alphaAPI, IMemoryCache cache)
|
|
|
+ public GlobalModel(Alpha_API alphaAPI, IMemoryCache cache, EconomyContext? context = null)
|
|
|
{
|
|
|
_alphaAPI = alphaAPI;
|
|
|
_cache = cache;
|
|
|
+ _context = context;
|
|
|
}
|
|
|
|
|
|
// 천연가스 시세 조회
|
|
|
@@ -43,14 +45,14 @@ namespace economy.Models.Price.Global
|
|
|
|
|
|
parseData = JsonSerializer.Deserialize<NaturalGas.Response>(jsonString);
|
|
|
|
|
|
- // 호출 한도 초과 시 HTTP 200 + Information 응답이 와서 Data가 null이 됨 — 빈 응답은 캐시하지 않음
|
|
|
- if (parseData.Data is null)
|
|
|
+ if (parseData.Data is not null)
|
|
|
{
|
|
|
- return new NaturalGas.Response();
|
|
|
- }
|
|
|
+ // 캐시에 저장 (메모리 1일 + DB에 마지막 성공 응답 영구 보관)
|
|
|
+ _cache.Set(cacheKey, parseData, TimeSpan.FromDays(1));
|
|
|
+ await SaveDbCacheAsync(cacheKey, jsonString);
|
|
|
|
|
|
- // 캐시에 저장 (1일 동안 유지)
|
|
|
- _cache.Set(cacheKey, parseData, TimeSpan.FromDays(1));
|
|
|
+ return parseData;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
@@ -60,7 +62,10 @@ namespace economy.Models.Price.Global
|
|
|
Console.WriteLine($"Request error: {e.Message}");
|
|
|
}
|
|
|
|
|
|
- return parseData;
|
|
|
+ // 호출 실패/한도 초과 시 DB에 보관된 마지막 성공 응답으로 대체
|
|
|
+ var fallback = await LoadDbCacheAsync<NaturalGas.Response>(cacheKey);
|
|
|
+
|
|
|
+ return fallback ?? new NaturalGas.Response();
|
|
|
}
|
|
|
|
|
|
// 국제 원자재 시세 조회 (구리, 알루미늄, 밀, 섬유(면화), 설탕, 커피, 원자재 종합지수)
|
|
|
@@ -92,14 +97,14 @@ namespace economy.Models.Price.Global
|
|
|
|
|
|
parseData = JsonSerializer.Deserialize<Commodity.Response>(jsonString);
|
|
|
|
|
|
- // 호출 한도 초과 시 HTTP 200 + Information 응답이 와서 Data가 null이 됨 — 빈 응답은 캐시하지 않음
|
|
|
- if (parseData.Data is null)
|
|
|
+ if (parseData.Data is not null)
|
|
|
{
|
|
|
- return new Commodity.Response();
|
|
|
- }
|
|
|
+ // 캐시에 저장 (메모리 1일 + DB에 마지막 성공 응답 영구 보관)
|
|
|
+ _cache.Set(cacheKey, parseData, TimeSpan.FromDays(1));
|
|
|
+ await SaveDbCacheAsync(cacheKey, jsonString);
|
|
|
|
|
|
- // 캐시에 저장 (1일 동안 유지)
|
|
|
- _cache.Set(cacheKey, parseData, TimeSpan.FromDays(1));
|
|
|
+ return parseData;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
@@ -109,7 +114,62 @@ namespace economy.Models.Price.Global
|
|
|
Console.WriteLine($"Request error: {e.Message}");
|
|
|
}
|
|
|
|
|
|
- return parseData;
|
|
|
+ // 호출 실패/한도 초과 시 DB에 보관된 마지막 성공 응답으로 대체
|
|
|
+ var fallback = await LoadDbCacheAsync<Commodity.Response>(cacheKey);
|
|
|
+
|
|
|
+ return fallback ?? new Commodity.Response();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 성공 응답을 DB에 upsert (DB 장애 시에도 서비스에는 영향 없음)
|
|
|
+ private async Task SaveDbCacheAsync(string cacheKey, string payload)
|
|
|
+ {
|
|
|
+ if (_context is null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var row = await _context.ApiCache.FindAsync(cacheKey);
|
|
|
+
|
|
|
+ if (row is null)
|
|
|
+ {
|
|
|
+ _context.ApiCache.Add(new ApiCacheEntry { CacheKey = cacheKey, Payload = payload, UpdatedAt = DateTime.Now });
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ row.Payload = payload;
|
|
|
+ row.UpdatedAt = DateTime.Now;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _context.SaveChangesAsync();
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ Console.WriteLine($"ApiCache save error: {e.Message}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // DB에 보관된 마지막 성공 응답 조회 (없거나 DB 장애면 null)
|
|
|
+ private async Task<T?> LoadDbCacheAsync<T>(string cacheKey) where T : class
|
|
|
+ {
|
|
|
+ if (_context is null)
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var row = await _context.ApiCache.FindAsync(cacheKey);
|
|
|
+
|
|
|
+ return row is null ? null : JsonSerializer.Deserialize<T>(row.Payload);
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ Console.WriteLine($"ApiCache load error: {e.Message}");
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|