feat: add 10min function-level cache to fetch_metar with stale fallback on error
This commit is contained in:
@@ -80,10 +80,15 @@ class WeatherDataCollector:
|
|||||||
self._ensemble_cache_lock = threading.Lock()
|
self._ensemble_cache_lock = threading.Lock()
|
||||||
self._multi_model_cache_lock = threading.Lock()
|
self._multi_model_cache_lock = threading.Lock()
|
||||||
self.meteoblue_cache_ttl_sec = int(
|
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: Dict[str, Dict] = {}
|
||||||
self._meteoblue_cache_lock = threading.Lock()
|
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")
|
proxy = config.get("proxy")
|
||||||
@@ -267,6 +272,14 @@ class WeatherDataCollector:
|
|||||||
logger.warning(f"未找到城市 {city} 对应的 ICAO 代码")
|
logger.warning(f"未找到城市 {city} 对应的 ICAO 代码")
|
||||||
return None
|
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:
|
try:
|
||||||
# NOAA Aviation Weather API (免费,无需 Key)
|
# NOAA Aviation Weather API (免费,无需 Key)
|
||||||
url = "https://aviationweather.gov/api/data/metar"
|
url = "https://aviationweather.gov/api/data/metar"
|
||||||
@@ -288,6 +301,7 @@ class WeatherDataCollector:
|
|||||||
if not data:
|
if not data:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# 1. 取最新的观测作为当前状态
|
# 1. 取最新的观测作为当前状态
|
||||||
latest = data[0]
|
latest = data[0]
|
||||||
temp_c = latest.get("temp")
|
temp_c = latest.get("temp")
|
||||||
@@ -457,11 +471,17 @@ class WeatherDataCollector:
|
|||||||
f"✈️ METAR {icao}: {temp:.1f}°{'F' if use_fahrenheit else 'C'} "
|
f"✈️ METAR {icao}: {temp:.1f}°{'F' if use_fahrenheit else 'C'} "
|
||||||
f"(obs: {obs_time})"
|
f"(obs: {obs_time})"
|
||||||
)
|
)
|
||||||
|
with self._metar_cache_lock:
|
||||||
|
self._metar_cache[cache_key] = {"d": result, "t": now_ts}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logger.error(f"METAR 请求失败 ({icao}): {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
|
return None
|
||||||
except (KeyError, IndexError, TypeError) as e:
|
except (KeyError, IndexError, TypeError) as e:
|
||||||
logger.error(f"METAR 数据解析失败 ({icao}): {e}")
|
logger.error(f"METAR 数据解析失败 ({icao}): {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user