From 4b51e0dd151456a769c982a95c38f2454a789059 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Wed, 11 Mar 2026 04:23:13 +0800 Subject: [PATCH] feat: add 10min function-level cache to fetch_metar with stale fallback on error --- src/data_collection/weather_sources.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/data_collection/weather_sources.py b/src/data_collection/weather_sources.py index 5ead8b02..9cdba86a 100644 --- a/src/data_collection/weather_sources.py +++ b/src/data_collection/weather_sources.py @@ -80,10 +80,15 @@ class WeatherDataCollector: self._ensemble_cache_lock = threading.Lock() self._multi_model_cache_lock = threading.Lock() self.meteoblue_cache_ttl_sec = int( - os.getenv("METEOBLUE_CACHE_TTL_SEC", "1800") + os.getenv("METEOBLUE_CACHE_TTL_SEC", "7200") ) self._meteoblue_cache: Dict[str, Dict] = {} self._meteoblue_cache_lock = threading.Lock() + self.metar_cache_ttl_sec = int( + os.getenv("METAR_CACHE_TTL_SEC", "600") # 默认 10 分钟 + ) + self._metar_cache: Dict[str, Dict] = {} + self._metar_cache_lock = threading.Lock() # 设置代理 proxy = config.get("proxy") @@ -267,6 +272,14 @@ class WeatherDataCollector: logger.warning(f"未找到城市 {city} 对应的 ICAO 代码") return None + cache_key = f"{icao}:{utc_offset}:{use_fahrenheit}" + now_ts = time.time() + with self._metar_cache_lock: + cached = self._metar_cache.get(cache_key) + if cached and now_ts - cached["t"] < self.metar_cache_ttl_sec: + logger.debug(f"METAR cache hit {icao} age={int(now_ts - cached['t'])}s") + return cached["d"] + try: # NOAA Aviation Weather API (免费,无需 Key) url = "https://aviationweather.gov/api/data/metar" @@ -288,6 +301,7 @@ class WeatherDataCollector: if not data: return None + # 1. 取最新的观测作为当前状态 latest = data[0] temp_c = latest.get("temp") @@ -457,11 +471,17 @@ class WeatherDataCollector: f"✈️ METAR {icao}: {temp:.1f}°{'F' if use_fahrenheit else 'C'} " f"(obs: {obs_time})" ) - + with self._metar_cache_lock: + self._metar_cache[cache_key] = {"d": result, "t": now_ts} return result except requests.exceptions.RequestException as e: logger.error(f"METAR 请求失败 ({icao}): {e}") + with self._metar_cache_lock: + stale = self._metar_cache.get(cache_key) + if stale: + logger.warning(f"METAR {icao} 请求失败,使用缓存回退") + return stale["d"] return None except (KeyError, IndexError, TypeError) as e: logger.error(f"METAR 数据解析失败 ({icao}): {e}")