feat: implement SSE-based real-time event distribution system with replay support and heartbeat functionality

This commit is contained in:
2569718930@qq.com
2026-05-26 22:27:55 +08:00
parent 5c6ffa4742
commit 645e304b3e
20 changed files with 1449 additions and 119 deletions
+13 -6
View File
@@ -11,11 +11,12 @@ import csv
import os
import io
import time
from datetime import datetime
from datetime import datetime, timezone
from typing import Any, Dict, Optional
from loguru import logger
# pyrefly: ignore [missing-import]
from src.utils.metrics import record_source_call
HKO_BASE_URL = os.getenv("HKO_BASE_URL", "").strip() or "https://data.weather.gov.hk/weatherAPI/hko_data/regional-weather"
@@ -34,11 +35,17 @@ HKO_STATIONS = {
class HkoObsSourceMixin:
session: Any
timeout: Any
_hko_obs_cache: Dict[str, Any]
_hko_obs_cache_lock: Any
hko_obs_cache_ttl_sec: Any
def _hko_http_get(self, url: str) -> str:
getter = getattr(self, "_http_get", None)
if callable(getter):
resp = getter(url)
return resp.text if hasattr(resp, "text") else resp
return str(resp.text) if hasattr(resp, "text") else str(resp)
resp = self.session.get(url, timeout=self.timeout)
resp.raise_for_status()
return resp.text
@@ -49,7 +56,7 @@ class HkoObsSourceMixin:
use_fahrenheit: bool = False,
) -> Optional[Dict[str, Any]]:
started = time.perf_counter()
city_key = str(city or "").strip().lower()
city_key = (city or "").strip().lower()
meta = HKO_STATIONS.get(city_key) or {}
if not meta:
return None
@@ -95,11 +102,11 @@ class HkoObsSourceMixin:
result = {
"source": "hko_obs",
"timestamp": datetime.utcnow().isoformat(),
"timestamp": datetime.now(timezone.utc).isoformat(),
"station_code": meta["code"],
"station_name": meta["label"],
"icao": meta["icao"],
"obs_time": obs_iso or datetime.utcnow().isoformat(),
"obs_time": obs_iso or datetime.now(timezone.utc).isoformat(),
"current": {
"temp": temp,
},
@@ -132,7 +139,7 @@ class HkoObsSourceMixin:
current = self.fetch_hko_obs_current(city, use_fahrenheit=use_fahrenheit)
if not current:
return []
meta = HKO_STATIONS.get(str(city or "").strip().lower()) or {}
meta = HKO_STATIONS.get((city or "").strip().lower()) or {}
return [
{
"name": meta.get("label") or "HKO Station",
+24
View File
@@ -339,6 +339,30 @@ class DBManager:
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_payment_audit_events_created_at ON payment_audit_events(created_at DESC)"
)
conn.execute("""
CREATE TABLE IF NOT EXISTS observation_patch_events (
revision INTEGER PRIMARY KEY AUTOINCREMENT,
schema_type TEXT NOT NULL,
schema_version INTEGER NOT NULL,
city TEXT NOT NULL,
source TEXT NOT NULL,
obs_time TEXT,
payload_json TEXT NOT NULL,
created_at TEXT NOT NULL
)
""")
conn.execute(
"""
CREATE INDEX IF NOT EXISTS idx_observation_patch_events_city_revision
ON observation_patch_events(city, revision)
"""
)
conn.execute(
"""
CREATE INDEX IF NOT EXISTS idx_observation_patch_events_created_at
ON observation_patch_events(created_at)
"""
)
conn.execute("""
CREATE TABLE IF NOT EXISTS app_analytics_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,