feat(heartbeat): emit heartbeat event to SQLite
This commit is contained in:
@@ -13,6 +13,7 @@ router = APIRouter(tags=["monitor"])
|
|||||||
class HeartbeatRequest(BaseModel):
|
class HeartbeatRequest(BaseModel):
|
||||||
account_id: str = Field(..., description="渠道账号唯一标识")
|
account_id: str = Field(..., description="渠道账号唯一标识")
|
||||||
account_name: str | None = Field(None)
|
account_name: str | None = Field(None)
|
||||||
|
tenant_code: str = Field("default", description="业务系统租户编码")
|
||||||
risk_level: int = Field(0, ge=0, le=100)
|
risk_level: int = Field(0, ge=0, le=100)
|
||||||
sent_count_today: int = Field(0, ge=0)
|
sent_count_today: int = Field(0, ge=0)
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ from __future__ import annotations
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from app.clients.pywechat_client import ClientRegistry, OnlineStatus, registry
|
from app.clients.pywechat_client import ClientRegistry, OnlineStatus, registry
|
||||||
|
from app.models.event import EventType, OutboundEvent
|
||||||
|
from app.repositories.event_store import EventStore
|
||||||
|
|
||||||
|
|
||||||
class HeartbeatService:
|
class HeartbeatService:
|
||||||
def __init__(self, registry: ClientRegistry):
|
def __init__(self, registry: ClientRegistry):
|
||||||
self.registry = registry
|
self.registry = registry
|
||||||
|
self.store: EventStore | None = None
|
||||||
|
|
||||||
def heartbeat(self, account_id: str, payload: dict) -> dict:
|
def heartbeat(self, account_id: str, payload: dict) -> dict:
|
||||||
client = self.registry.get_or_create(account_id, payload.get("account_name"))
|
client = self.registry.get_or_create(account_id, payload.get("account_name"))
|
||||||
@@ -16,6 +19,20 @@ class HeartbeatService:
|
|||||||
client.state.risk_level = payload["risk_level"]
|
client.state.risk_level = payload["risk_level"]
|
||||||
if "sent_count_today" in payload:
|
if "sent_count_today" in payload:
|
||||||
client.state.sent_count_today = payload["sent_count_today"]
|
client.state.sent_count_today = payload["sent_count_today"]
|
||||||
|
|
||||||
|
event = OutboundEvent(
|
||||||
|
event_type=EventType.HEARTBEAT,
|
||||||
|
account_id=account_id,
|
||||||
|
tenant_code=payload.get("tenant_code", "default"),
|
||||||
|
data={
|
||||||
|
"login_status": client.state.login_status.value,
|
||||||
|
"online_status": client.state.online_status.value,
|
||||||
|
"risk_level": client.state.risk_level,
|
||||||
|
"sent_count_today": client.state.sent_count_today,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if self.store is not None:
|
||||||
|
self.store.save(event)
|
||||||
return {"success": True, "account_id": account_id}
|
return {"success": True, "account_id": account_id}
|
||||||
|
|
||||||
def check_offline_accounts(self, timeout_seconds: int = 120) -> list[dict]:
|
def check_offline_accounts(self, timeout_seconds: int = 120) -> list[dict]:
|
||||||
@@ -30,3 +47,5 @@ class HeartbeatService:
|
|||||||
|
|
||||||
|
|
||||||
heartbeat_service = HeartbeatService(registry)
|
heartbeat_service = HeartbeatService(registry)
|
||||||
|
from app.repositories.event_store import event_store # noqa: E402
|
||||||
|
heartbeat_service.store = event_store
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from app.main import app
|
from app.main import app
|
||||||
|
from app.models.event import EventType
|
||||||
|
from app.repositories.event_store import event_store
|
||||||
|
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
|
|
||||||
@@ -64,6 +66,18 @@ def test_heartbeat_and_monitor():
|
|||||||
assert any(c["account_id"] == "bot-003" and c["risk_level"] == 10 for c in clients)
|
assert any(c["account_id"] == "bot-003" and c["risk_level"] == 10 for c in clients)
|
||||||
|
|
||||||
|
|
||||||
|
def test_heartbeat_creates_event():
|
||||||
|
event_store.cleanup(0)
|
||||||
|
client.post("/v1/heartbeat", json={
|
||||||
|
"account_id": "bot-heartbeat",
|
||||||
|
"tenant_code": "rescue",
|
||||||
|
"risk_level": 10,
|
||||||
|
"sent_count_today": 5,
|
||||||
|
})
|
||||||
|
pending = event_store.fetch_pending(limit=10)
|
||||||
|
assert any(e.event_type == EventType.HEARTBEAT and e.tenant_code == "rescue" for e in pending)
|
||||||
|
|
||||||
|
|
||||||
def test_monitor_page():
|
def test_monitor_page():
|
||||||
r = client.get("/monitor")
|
r = client.get("/monitor")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|||||||
Reference in New Issue
Block a user