项目开发问题:需实现每5分钟仅调用一次机场API接口
Hey there! Let's fix your problem of ensuring your airport data is only fetched from the external API once every 5 minutes. The core idea here is to cache the retrieved data and check if enough time has passed before making a new API call. I'll walk you through two common approaches depending on your project type.
1. 使用ASP.NET Core的IMemoryCache(推荐Web项目)
If you're working with an ASP.NET Core app, leveraging the built-in IMemoryCache is clean and maintainable. We'll inject it along with IHttpClientFactory (a better practice than directly instantiating HttpClient) to manage our requests and caching.
完整代码示例
using Microsoft.Extensions.Caching.Memory; using System.Net.Http.Json; public class AirportService { private readonly IHttpClientFactory _httpClientFactory; private readonly IMemoryCache _memoryCache; private readonly string _airportApiUrl = @"https://raw.githubusercontent.com/jbrooksuk/JSON-Airports/master/airports.json"; private const string _cacheKey = "AirportListCache"; private static readonly TimeSpan _cacheExpiry = TimeSpan.FromMinutes(5); // 通过构造函数注入依赖 public AirportService(IHttpClientFactory httpClientFactory, IMemoryCache memoryCache) { _httpClientFactory = httpClientFactory; _memoryCache = memoryCache; } public async Task<IEnumerable<Airport>> GetAirportData() { // 先尝试从缓存读取数据 if (_memoryCache.TryGetValue(_cacheKey, out IEnumerable<Airport> cachedAirports)) { return cachedAirports; } // 缓存不存在/已过期,调用API获取新数据 var httpClient = _httpClientFactory.CreateClient(); var response = await httpClient.GetAsync(_airportApiUrl); response.EnsureSuccessStatusCode(); // 确保请求成功,否则抛出HttpRequestException var airports = await response.Content.ReadFromJsonAsync<IEnumerable<Airport>>(); // 将新数据存入缓存,并设置5分钟过期时间 var cacheOptions = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(_cacheExpiry); // 绝对过期:从存入时间开始算5分钟后过期 _memoryCache.Set(_cacheKey, airports, cacheOptions); return airports; } }
关键说明
IHttpClientFactory: Manages HttpClient instances efficiently, avoiding issues with socket exhaustion.IMemoryCache: Handles caching automatically, including expiration. We useSetAbsoluteExpirationto ensure the cache is refreshed exactly every 5 minutes.- Cache Check: The first thing the method does is check the cache—if data exists and isn't expired, it returns immediately without hitting the API.
2. 手动实现缓存(适合非Web项目,如控制台/桌面应用)
If you're not using ASP.NET Core, you can manually maintain a cache with a timestamp and thread-safe locks to prevent concurrent API calls.
完整代码示例
using System.Text.Json; public class AirportService { private readonly HttpClient _httpClient; private readonly string _airportApiUrl = @"https://raw.githubusercontent.com/jbrooksuk/JSON-Airports/master/airports.json"; private IEnumerable<Airport> _cachedAirports; private DateTime _lastFetchTime = DateTime.MinValue; private static readonly TimeSpan _refreshInterval = TimeSpan.FromMinutes(5); private readonly object _lockObject = new(); // 线程安全锁,防止多线程下重复请求 public AirportService() { _httpClient = new HttpClient(); } public async Task<IEnumerable<Airport>> GetAirportData() { // 检查缓存是否有效 lock (_lockObject) { if (_cachedAirports != null && DateTime.Now - _lastFetchTime < _refreshInterval) { return _cachedAirports; } } try { // 请求API获取新数据 var response = await _httpClient.GetAsync(_airportApiUrl); response.EnsureSuccessStatusCode(); var stream = await response.Content.ReadAsStreamAsync(); var airports = await JsonSerializer.DeserializeAsync<IEnumerable<Airport>>(stream); // 更新缓存和时间戳(加锁保证线程安全) lock (_lockObject) { _cachedAirports = airports; _lastFetchTime = DateTime.Now; } return airports; } catch (Exception ex) { // 可选:记录异常日志 // 如果API请求失败,返回旧缓存(如果存在),避免服务中断 lock (_lockObject) { if (_cachedAirports != null) { return _cachedAirports; } } // 没有旧缓存时,抛出异常(根据业务需求调整) throw new InvalidOperationException("Failed to fetch airport data and no cached data available.", ex); } } }
关键说明
- Thread Safety: The
lockstatements ensure that only one thread can check/update the cache at a time, preventing multiple concurrent API calls when the cache expires. - Fallback Logic: If the API request fails, we return the last cached data (if available) to keep your application functional.
- Manual Expiry Check: We compare the current time to
_lastFetchTimeto determine if a new API call is needed.
额外优化建议
- Add Retry Logic: Use libraries like
Pollyto add retry policies for transient API failures (e.g., network timeouts). - Sliding Expiration: If you want the cache to refresh only after 5 minutes of inactivity (instead of a fixed interval), use
SetSlidingExpirationwithIMemoryCache. - Cache Persistence: For long-running apps, consider using a distributed cache (like Redis) instead of in-memory cache to avoid losing data when the app restarts.
内容的提问来源于stack exchange,提问作者pmt665

