667cda566f
新手引导埋点的服务端: - 表 analytics_event(五维硬性列 + props JSON 扩展字段;event/device_id/user_id/session_id/created_at 带索引) - POST /api/v1/analytics/events:客户端批量上报(不鉴权、body 读可选 user_id、补 client_ip + server_at) - admin GET /admin/api/event-logs:列表 + 按 事件/设备/用户/会话/时间 筛选(offset 分页,照 list_feedbacks) - alembic migration 建表(autogenerate 顺带检出的 ad/cps 历史索引漂移已手动剔除) app 主后端 :8770 与 admin :8771 共用同一 SQLite,admin 同库直接查、无需跨库。 配套客户端五维上报 + admin 日志页(另两仓库 PR)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: zzhyyyyy <2685922758@qq.com> Reviewed-on: #81 Co-authored-by: zhuzihao <zhuzihao@wonderable.ai> Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
43 lines
1.6 KiB
Python
43 lines
1.6 KiB
Python
"""客户端埋点上报 schema(批量)。
|
|
|
|
客户端把「设备固定维度」(device_id / user_id / oem / os / model / app_ver / channel / sent_at)
|
|
放批次外层只传一次,events 列表里每条只带「事件维度」(event / client_ts / session_id / page /
|
|
network / props);服务端展开成多行 AnalyticsEvent 落库(见 app/repositories/analytics.py)。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class AnalyticsEventIn(BaseModel):
|
|
"""单条事件维度。"""
|
|
|
|
event: str = Field(max_length=64)
|
|
client_ts: int = Field(description="端事件发生时间 epoch ms")
|
|
session_id: str | None = Field(default=None, max_length=64)
|
|
page: str | None = Field(default=None, max_length=64)
|
|
network: str | None = Field(default=None, max_length=16)
|
|
props: dict[str, str] = Field(default_factory=dict)
|
|
|
|
|
|
class AnalyticsBatchIn(BaseModel):
|
|
"""一批上报:公共维度 + 事件列表。"""
|
|
|
|
# Who(整批共享)
|
|
device_id: str = Field(max_length=64)
|
|
user_id: int | None = None
|
|
# When(本批上报时刻 epoch ms)
|
|
sent_at: int | None = None
|
|
# How(设备固定维度,整批共享)
|
|
oem: str | None = Field(default=None, max_length=32)
|
|
os: str | None = Field(default=None, max_length=32)
|
|
model: str | None = Field(default=None, max_length=64)
|
|
app_ver: str | None = Field(default=None, max_length=32)
|
|
channel: str | None = Field(default=None, max_length=32)
|
|
events: list[AnalyticsEventIn] = Field(min_length=1, max_length=200)
|
|
|
|
|
|
class AnalyticsIngestOut(BaseModel):
|
|
ok: bool = True
|
|
received: int
|