Files
meida_backend/apps/api/routers/events.py
T
xsl 64175a1ea1 feat(analytics): POST /events/batch with fail-close validation
ob_event 落库通道:形状校验 + props 字节上限 + client_ts 合理性 +
PII 键剔除,整批原子(FR-SYS-06 服务端部分)。

实现:packages/analytics/schemas.py(EventIn/EventBatchRequest/
EventBatchResponse + 事件名形状正则常量);service.py 拆出纯函数
validate_batch(批量/props 字节/事件名形状/client_ts 窗口/PII 剔除,
任一不过整批 40020),AnalyticsService.record 组装 ObEvent 逐条
add + 一次 flush;routers/events.py 提供 POST /events/batch
(/api/v1 与 /v1 别名,认证必需)。

测试:单测 tests/unit/test_events_validation.py 8 例(合法批、超批量、
props 超字节/不可序列化、事件名形状含尾随换行、client_ts 双边界、
naive 时区按 UTC、PII 键含大小写变体剔除);集成
tests/integration/test_events.py 3 例(两前缀可用+落库断言、
非法批 40020 且库无半条、无 token 40100)。门禁全绿:ruff
check/format、mypy(T2 列表 + domain strict)、lint-imports 4 契约、
单测 177 通过、集成 6 通过。

偏差说明:event_name 正则未写成 Field(pattern=...),而是作为
EVENT_NAME_PATTERN 常量放 schemas.py、由服务层 validate_batch 强制——
若挂在请求体校验上,非法事件名会先被 RequestValidationError 拦截、
返回通用 40001/422 信封,无法满足任务契约 §4「大写 event_name →
40020」的集成行为。正则本体与文档一致。
2026-08-14 16:16:26 +08:00

26 lines
976 B
Python

"""``POST /events/batch`` — 客户端埋点批量上报(FR-SYS-06 服务端入口)。
埋点链路对客户端静默降级:非法批返回 40020(无 hint),客户端丢弃本地
缓存即可,不打扰用户。校验规则见 packages/analytics/service.py。
"""
from __future__ import annotations
from fastapi import APIRouter
from apps.api.deps import AnalyticsServiceDep, CurrentUserId
from packages.analytics.schemas import EventBatchRequest, EventBatchResponse
from packages.shared.response import ok
router = APIRouter(prefix="/events", tags=["events"])
@router.post("/batch", summary="批量上报埋点事件(整批校验,任一非法整批拒绝)")
async def post_event_batch(
user_id: CurrentUserId,
analytics: AnalyticsServiceDep,
payload: EventBatchRequest,
) -> dict[str, object]:
accepted = await analytics.record(user_id=user_id, batch=payload.events)
return ok(EventBatchResponse(accepted=accepted).model_dump())