feat(analytics): POST /api/v1/analytics/selfstat ingest endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
guke
2026-07-08 16:53:18 +08:00
parent 66eaa40355
commit c386103809
2 changed files with 49 additions and 1 deletions
+16 -1
View File
@@ -6,13 +6,18 @@ POST /api/v1/analytics/events — 批量接收新手引导(及后续)埋点,appe
"""
from __future__ import annotations
from fastapi import APIRouter, Request
import logging
from fastapi import APIRouter, HTTPException, Request
from app.api.deps import DbSession
from app.repositories import analytics as analytics_repo
from app.repositories import analytics_selfstat as selfstat_repo
from app.schemas.analytics import AnalyticsBatchIn, AnalyticsIngestOut
from app.schemas.analytics_selfstat import SelfStatBatchIn, SelfStatIngestOut
router = APIRouter(prefix="/api/v1/analytics", tags=["analytics"])
logger = logging.getLogger("shagua.analytics")
def _client_ip(request: Request) -> str:
@@ -29,3 +34,13 @@ def ingest_events(
) -> AnalyticsIngestOut:
n = analytics_repo.record_batch(db, batch, client_ip=_client_ip(request))
return AnalyticsIngestOut(received=n)
@router.post("/selfstat", response_model=SelfStatIngestOut, summary="上报自报计数快照")
def ingest_selfstat(batch: SelfStatBatchIn, db: DbSession) -> SelfStatIngestOut:
try:
snap_id = selfstat_repo.record_selfstat(db, batch)
except Exception: # noqa: BLE001 — 计数链路要稳,落库失败不裸奔 500,记日志回明确错误
logger.exception("selfstat ingest failed device=%s epoch=%s", batch.device_id, batch.epoch_id)
raise HTTPException(status_code=503, detail="selfstat ingest failed") from None
return SelfStatIngestOut(snapshot_id=snap_id)
+33
View File
@@ -0,0 +1,33 @@
"""自报计数上报端点测试。"""
from __future__ import annotations
from fastapi.testclient import TestClient
def _payload(**over) -> dict:
base = {
"device_id": "dev-1", "epoch_id": "ep-1", "sent_at": 1700000000000,
"app_ver": "0.2.12(62)", "oem": "ColorOS", "os": "Android 14",
"batches_attempted": 10, "batches_ok": 9, "batches_fail": 1,
"retries": 1, "queue_depth": 2,
"events": [
{"event": "video_play", "attempted": 100, "drop_capture": 1,
"delivered": 95, "drop_undelivered": 2},
],
}
base.update(over)
return base
def test_selfstat_ingest_ok(client: TestClient) -> None:
r = client.post("/api/v1/analytics/selfstat", json=_payload())
assert r.status_code == 200, r.text
body = r.json()
assert body["ok"] is True
assert isinstance(body["snapshot_id"], int)
def test_selfstat_ingest_empty_events(client: TestClient) -> None:
r = client.post("/api/v1/analytics/selfstat", json=_payload(events=[]))
assert r.status_code == 200, r.text
assert r.json()["ok"] is True