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」的集成行为。正则本体与文档一致。
This commit is contained in:
@@ -1,10 +1,25 @@
|
||||
"""``POST /events/batch`` — 客户端埋点批量上报(骨架,T3 填充实现)。
|
||||
"""``POST /events/batch`` — 客户端埋点批量上报(FR-SYS-06 服务端入口)。
|
||||
|
||||
FR-SYS-06 服务端入口;实现契约见 docs/开发任务包/T3-埋点上报.md。
|
||||
埋点链路对客户端静默降级:非法批返回 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())
|
||||
|
||||
Reference in New Issue
Block a user