feat(analytics): POST /api/v1/analytics/selfstat ingest endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+16
-1
@@ -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)
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user