From 2f4783f1f914add0aaa2da3e84d962257f042abd Mon Sep 17 00:00:00 2001 From: marsal Date: Fri, 10 Jul 2026 12:11:18 +0800 Subject: [PATCH] feat(heartbeat): emit heartbeat event to SQLite --- channel-service/app/api/monitor.py | 1 + .../app/services/heartbeat_service.py | 19 +++++++++++++++++++ channel-service/tests/test_api.py | 14 ++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/channel-service/app/api/monitor.py b/channel-service/app/api/monitor.py index 3058ad6..e447083 100644 --- a/channel-service/app/api/monitor.py +++ b/channel-service/app/api/monitor.py @@ -13,6 +13,7 @@ router = APIRouter(tags=["monitor"]) class HeartbeatRequest(BaseModel): account_id: str = Field(..., description="渠道账号唯一标识") account_name: str | None = Field(None) + tenant_code: str = Field("default", description="业务系统租户编码") risk_level: int = Field(0, ge=0, le=100) sent_count_today: int = Field(0, ge=0) diff --git a/channel-service/app/services/heartbeat_service.py b/channel-service/app/services/heartbeat_service.py index e62f3d3..b38dd36 100644 --- a/channel-service/app/services/heartbeat_service.py +++ b/channel-service/app/services/heartbeat_service.py @@ -3,11 +3,14 @@ from __future__ import annotations import time 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: def __init__(self, registry: ClientRegistry): self.registry = registry + self.store: EventStore | None = None def heartbeat(self, account_id: str, payload: dict) -> dict: 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"] if "sent_count_today" in payload: 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} def check_offline_accounts(self, timeout_seconds: int = 120) -> list[dict]: @@ -30,3 +47,5 @@ class HeartbeatService: heartbeat_service = HeartbeatService(registry) +from app.repositories.event_store import event_store # noqa: E402 +heartbeat_service.store = event_store diff --git a/channel-service/tests/test_api.py b/channel-service/tests/test_api.py index 1c3c22e..04ffa78 100644 --- a/channel-service/tests/test_api.py +++ b/channel-service/tests/test_api.py @@ -1,6 +1,8 @@ from fastapi.testclient import TestClient from app.main import app +from app.models.event import EventType +from app.repositories.event_store import event_store 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) +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(): r = client.get("/monitor") assert r.status_code == 200