This commit is contained in:
243
docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md
Normal file
243
docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# 后端用户行为埋点覆盖方案
|
||||
|
||||
更新时间:`2026-05-09`
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本方案用于补齐后端可直接观测的用户行为埋点入口,统一写入 SpacetimeDB 的 `tracking_event` 与 `tracking_daily_stat`,为任务系统、运营看板与后续漏斗分析提供事实数据。
|
||||
|
||||
本轮明确不纳入以下范围:
|
||||
|
||||
- 后台管理入口:`/admin/...`
|
||||
- RPG 相关入口
|
||||
- 大鱼吃小鱼相关入口
|
||||
- Visual Novel 相关入口
|
||||
- Story 相关入口
|
||||
- Combat 相关入口
|
||||
|
||||
上述范围后续若需要埋点,应单独定义事件口径,避免把后台运营审计或特定玩法内行为混入本轮通用用户行为埋点。
|
||||
|
||||
## 2. 写入链路
|
||||
|
||||
### 2.1 SpacetimeDB 通用 procedure
|
||||
|
||||
新增通用 procedure:
|
||||
|
||||
- `record_tracking_event_and_return(input: RuntimeTrackingEventInput)`
|
||||
|
||||
该入口复用既有运行态埋点写入能力:
|
||||
|
||||
1. 写入原始事实 `tracking_event`。
|
||||
2. 更新聚合投影 `tracking_daily_stat`。
|
||||
3. 触发依赖事件进度的个人任务刷新。
|
||||
|
||||
每日登录 `daily_login` 也必须走该通用 procedure:认证链路仍保留 `record_daily_login_tracking_event_after_auth_success(...)` 作为业务语义 helper,但 helper 内部构造 `TrackingEventDraft` 后调用 `record_tracking_event_after_success(...)`,不再绕到每日登录专用 SpacetimeDB procedure。
|
||||
|
||||
### 2.2 spacetime-client 封装
|
||||
|
||||
`spacetime-client` 提供薄封装:
|
||||
|
||||
- `SpacetimeRuntimeClient::record_tracking_event(...)`
|
||||
|
||||
API Server 只依赖该 facade,不在 handler 中直接拼接 SpacetimeDB procedure 调用。
|
||||
|
||||
### 2.3 api-server helper 与中间件
|
||||
|
||||
API Server 新增统一 helper:
|
||||
|
||||
- `tracking::TrackingEventDraft`
|
||||
- `tracking::record_tracking_event_after_success(...)`
|
||||
- `tracking::record_route_tracking_event_after_success(...)`
|
||||
|
||||
路由级中间件 `record_api_tracking_after_success` 挂在最终响应链路上,只在最终 HTTP status 为 2xx 时写入埋点。埋点失败只写 `warn` 日志,不阻断认证、充值、发布、任务领取等主业务流程。
|
||||
|
||||
## 3. metadata 口径
|
||||
|
||||
当前通用路由埋点仅记录低敏字段:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `route` | 请求路径,不包含 query string |
|
||||
| `method` | HTTP Method |
|
||||
| `status` | 最终成功响应状态码 |
|
||||
| `operation` | `RequestContext` 中的操作名 |
|
||||
| `asset` | 仅资产类事件写入的低敏资产/操作信息,包含 `operation`、`operationFamily`、`assetObjectId`、`assetKind`、`objectKey`、`bucket`、`contentType`、`contentLength`、`version`、`bindingId`、`entityKind`、`entityId`、`slot`、`ownerUserId`、`profileId` 等可用于定位资产事实的字段;不写签名 URL、表单签名、OSS policy、token 或完整请求体。 |
|
||||
| `assetOperation` | 资产类路由兜底事件的操作 key,用于不读取请求体时仍能按操作族聚合。 |
|
||||
|
||||
禁止在通用埋点 metadata 中写入手机号、token、cookie、邀请码、请求体、密钥、连接串、外部凭证、OSS 签名 URL、PostObject policy 或签名表单字段。
|
||||
|
||||
### 3.1 作品级游玩埋点
|
||||
|
||||
所有已接入后端正式试玩/播放入口的作品类型统一写 `work_play_start`:
|
||||
|
||||
- `scope_kind = work`。
|
||||
- `scope_id = 稳定作品 ID`,优先使用 `profile_id`;大鱼吃小鱼沿用 `session_id` 作为作品 ID。
|
||||
- `user_id = 当前认证用户`。
|
||||
- `owner_user_id = 作品作者/拥有者`,无法从入口直接确认作者时可为空,但 `metadata.userId` 仍保留当前玩家。
|
||||
- `profile_id = 作品 profile_id`,大鱼吃小鱼这类 session 型作品可为空。
|
||||
- `module_key = play_type`,例如 `puzzle`、`match3d`、`square-hole`、`custom-world`、`big-fish`、`visual-novel`。
|
||||
- `metadata` 固定包含 `operation = work_play_start`、`playType`、`workId`、`sourceRoute`,并按入口补充 `runId`、`ownerUserId`、`profileId`、`levelId`、`mode` 等低敏字段。
|
||||
|
||||
该事件用于“某个作品被多少不同用户玩过”等作品级分析;权威去重统计仍建议优先使用业务投影(如 `profile_played_world`),埋点侧用于分析与漏斗联动。
|
||||
|
||||
## 4. 事件清单
|
||||
|
||||
### 4.1 认证与会话
|
||||
|
||||
| 事件 | 入口 |
|
||||
| --- | --- |
|
||||
| `auth_login_options_view` | `GET /api/auth/login-options` |
|
||||
| `auth_phone_code_send` | `POST /api/auth/phone/send-code` |
|
||||
| `daily_login` | 认证成功与 refresh 续期后由 `record_daily_login_tracking_event_after_auth_success(...)` 主动写入,事件 ID 按 `daily-login:{user_id}:{day_key}` 幂等 |
|
||||
| `auth_phone_login_success` | `POST /api/auth/phone/login` |
|
||||
| `auth_me_view` | `GET /api/auth/me` |
|
||||
| `auth_sessions_view` | `GET /api/auth/sessions` |
|
||||
| `auth_refresh_success` | `POST /api/auth/refresh` |
|
||||
| `auth_logout` | `POST /api/auth/logout` |
|
||||
| `auth_logout_all` | `POST /api/auth/logout-all` |
|
||||
| `auth_wechat_bind_phone_success` | `POST /api/auth/wechat/bind-phone` |
|
||||
|
||||
### 4.2 个人中心、账户运营与任务
|
||||
|
||||
| 事件 | 入口 |
|
||||
| --- | --- |
|
||||
| `profile_identity_update` | `PATCH /api/profile/me` |
|
||||
| `profile_dashboard_view` | `GET /api/profile/dashboard` |
|
||||
| `wallet_ledger_view` | `GET /api/profile/wallet-ledger` |
|
||||
| `recharge_center_view` | `GET /api/profile/recharge-center` |
|
||||
| `recharge_order_create` | `POST /api/profile/recharge/orders` |
|
||||
| `feedback_submit` | `POST /api/profile/feedback` |
|
||||
| `invite_center_view` | `GET /api/profile/referrals/invite-center` |
|
||||
| `referral_invite_code_redeem` | `POST /api/profile/referrals/redeem-code` |
|
||||
| `redeem_code_submit` | `POST /api/profile/redeem-codes/redeem` |
|
||||
| `task_center_view` | `GET /api/profile/tasks` |
|
||||
| `task_reward_claim` | `POST /api/profile/tasks/{task_id}/claim` |
|
||||
| `save_archive_list_view` | `GET /api/profile/save-archives` |
|
||||
| `save_archive_detail_view` | `GET /api/profile/save-archives/{archive_id}` |
|
||||
| `browse_history_view` | `GET /api/profile/browse-history` |
|
||||
| `browse_history_record` | `POST /api/profile/browse-history` |
|
||||
| `browse_history_clear` | `DELETE /api/profile/browse-history` |
|
||||
| `play_stats_view` | `GET /api/profile/play-stats` |
|
||||
| `profile_analytics_metric_view` | `GET /api/profile/analytics/metric` |
|
||||
|
||||
### 4.3 AI、资产、LLM 与语音
|
||||
|
||||
资产操作统一按用户级事件写入:`scope_kind = user`、`scope_id = 当前认证 user_id`、`user_id/owner_user_id = 当前认证 user_id`。其中 `asset_upload_ticket_create`、`asset_upload_confirm`、`asset_bind` 在 handler 成功后主动记录资产 metadata,避免只依赖路由兜底;其余资产工坊入口通过路由级兜底保留用户级操作事实。
|
||||
|
||||
| 事件 | 入口 |
|
||||
| --- | --- |
|
||||
| `ai_task_create` | `POST /api/ai/tasks` |
|
||||
| `ai_task_start` | `POST /api/ai/tasks/{task_id}/start` |
|
||||
| `ai_task_stage_start` | `POST /api/ai/tasks/{task_id}/stages/{stage_id}/start` |
|
||||
| `ai_task_chunk_append` | `POST /api/ai/tasks/{task_id}/chunks` |
|
||||
| `ai_task_stage_complete` | `POST /api/ai/tasks/{task_id}/stages/{stage_id}/complete` |
|
||||
| `ai_task_reference_attach` | `POST /api/ai/tasks/{task_id}/references` |
|
||||
| `ai_task_complete` | `POST /api/ai/tasks/{task_id}/complete` |
|
||||
| `ai_task_fail` | `POST /api/ai/tasks/{task_id}/fail` |
|
||||
| `ai_task_cancel` | `POST /api/ai/tasks/{task_id}/cancel` |
|
||||
| `asset_upload_ticket_create` | `POST /api/assets/direct-upload-tickets` |
|
||||
| `asset_sts_credentials_create` | `POST /api/assets/sts-upload-credentials` |
|
||||
| `asset_upload_confirm` | `POST /api/assets/objects/confirm` |
|
||||
| `asset_bind` | `POST /api/assets/objects/bind` |
|
||||
| `asset_character_visual_generate` | `POST /api/assets/character-visual/generate` |
|
||||
| `asset_character_visual_publish` | `POST /api/assets/character-visual/publish` |
|
||||
| `asset_character_animation_generate` | `POST /api/assets/character-animation/generate` |
|
||||
| `asset_character_animation_publish` | `POST /api/assets/character-animation/publish` |
|
||||
| `asset_character_animation_import` | `POST /api/assets/character-animation/import-video` |
|
||||
| `asset_character_workflow_cache_save` | `POST /api/assets/character-workflow-cache` |
|
||||
| `asset_history_view` | `GET /api/assets/history` |
|
||||
| `llm_request` | `POST /api/llm/chat/completions` |
|
||||
| `speech_config_view` | `GET /api/speech/volcengine/config` |
|
||||
| `asr_stream_start` | `GET /api/speech/volcengine/asr/stream` |
|
||||
| `tts_bidirection_start` | `GET /api/speech/volcengine/tts/bidirection` |
|
||||
| `tts_sse_start` | `POST /api/speech/volcengine/tts/sse` |
|
||||
|
||||
### 4.4 运行态与创作入口
|
||||
|
||||
| 事件 | 入口 |
|
||||
| --- | --- |
|
||||
| `runtime_settings_view` | `GET /api/runtime/settings` |
|
||||
| `runtime_settings_update` | `PUT /api/runtime/settings` |
|
||||
| `runtime_snapshot_view` | `GET /api/runtime/save/snapshot` |
|
||||
| `runtime_snapshot_save` | `PUT /api/runtime/save/snapshot` |
|
||||
| `runtime_snapshot_delete` | `DELETE /api/runtime/save/snapshot` |
|
||||
| `puzzle_route_success` | `/api/runtime/puzzle/...` 成功响应兜底 |
|
||||
| `match3d_route_success` | `/api/creation/match3d/...` 与 `/api/runtime/match3d/...` 成功响应兜底 |
|
||||
| `square_hole_route_success` | `/api/creation/square-hole/...` 与 `/api/runtime/square-hole/...` 成功响应兜底 |
|
||||
| `custom_world_route_success` | `/api/runtime/custom-world...` 成功响应兜底 |
|
||||
| `creative_agent_route_success` | `/api/runtime/creative-agent...` 成功响应兜底 |
|
||||
| `work_play_start` | 拼图、抓大鹅、方洞挑战、自定义世界、大鱼吃小鱼、Visual Novel 的正式开始游玩/播放入口;写 `scope_kind = work`、`scope_id = 作品 ID` |
|
||||
|
||||
2048、Survivor、Moku 等未被排除的模板/玩法,如果经由上述 runtime、creative、custom-world、puzzle、match3d 或 square-hole 后端入口,会被路由级兜底事件覆盖。
|
||||
|
||||
## 5. 查询与验收建议
|
||||
|
||||
按每日登录核查原始事实:
|
||||
|
||||
```sql
|
||||
SELECT event_id, event_key, scope_kind, scope_id, user_id, module_key, metadata_json, occurred_at
|
||||
FROM tracking_event
|
||||
WHERE event_key = 'daily_login'
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
按作品级游玩核查原始事实:
|
||||
|
||||
```sql
|
||||
SELECT event_key, scope_kind, scope_id, user_id, owner_user_id, profile_id, module_key, metadata_json, occurred_at
|
||||
FROM tracking_event
|
||||
WHERE event_key = 'work_play_start'
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
按某个作品统计不同游玩用户:
|
||||
|
||||
```sql
|
||||
SELECT scope_id, COUNT(DISTINCT user_id) AS player_count
|
||||
FROM tracking_event
|
||||
WHERE event_key = 'work_play_start'
|
||||
AND scope_kind = 'work'
|
||||
AND scope_id = '<profile_id_or_work_id>'
|
||||
GROUP BY scope_id;
|
||||
```
|
||||
|
||||
按资产操作核查原始事实:
|
||||
|
||||
```sql
|
||||
SELECT event_key, scope_kind, scope_id, user_id, owner_user_id, module_key, metadata_json, occurred_at
|
||||
FROM tracking_event
|
||||
WHERE module_key = 'asset'
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
按事件核查原始事实:
|
||||
|
||||
```sql
|
||||
SELECT event_key, scope_kind, scope_id, user_id, module_key, metadata_json, occurred_at
|
||||
FROM tracking_event
|
||||
WHERE event_key = 'task_center_view'
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
按日聚合核查:
|
||||
|
||||
```sql
|
||||
SELECT day_key, event_key, scope_kind, scope_id, count
|
||||
FROM tracking_daily_stat
|
||||
WHERE event_key = 'task_center_view'
|
||||
ORDER BY day_key DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
验收重点:
|
||||
|
||||
1. 成功请求写入 `tracking_event` 并刷新 `tracking_daily_stat`。
|
||||
2. `daily_login` 由认证成功/refresh 续期链路主动写入,且走 `record_tracking_event_and_return` 通用 procedure。
|
||||
3. 非 2xx 响应不记录通用成功事件。
|
||||
4. 后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 路由不写入本轮通用埋点。
|
||||
5. 埋点写入失败时主接口仍返回原业务结果,只记录后端 warning。
|
||||
6. metadata 不包含凭证、请求体或敏感业务字段。
|
||||
@@ -7,7 +7,7 @@
|
||||
- `record_daily_login_tracking_event_and_return`
|
||||
- `spacetime-client` 方法:`record_daily_login_tracking_event(user_id)`
|
||||
|
||||
但认证成功链路还没有调用该方法,因此当前只完成了“任务中心读取不污染登录埋点”,没有完成“用户真实登录写入每日登录埋点”。
|
||||
但认证成功链路当时还没有调用该方法,因此当时只完成了“任务中心读取不污染登录埋点”,没有完成“用户真实登录写入每日登录埋点”。后续后端通用埋点能力落地后,`daily_login` 已进一步改为通过统一 `record_tracking_event_and_return(RuntimeTrackingEventInput)` procedure 写入,旧 `record_daily_login_tracking_event_and_return` 不再作为认证链路的目标入口。
|
||||
|
||||
## 现象
|
||||
|
||||
@@ -53,7 +53,9 @@ record_daily_login_tracking_event_after_auth_success(
|
||||
|
||||
该 helper:
|
||||
|
||||
- 调用 `state.spacetime_client().record_daily_login_tracking_event(user_id.to_string()).await`
|
||||
- 构造 `TrackingEventDraft::user("daily_login", "profile", user_id)`
|
||||
- 使用 `daily-login:{user_id}:{day_key}` 作为事件 ID,保持北京时间自然日幂等
|
||||
- 调用统一 `record_tracking_event_after_success(...)`,最终进入 `record_tracking_event_and_return(RuntimeTrackingEventInput)`
|
||||
- 成功时记录 info
|
||||
- 失败时记录 warn,并明确“登录流程继续”
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
| reward_points | `10` |
|
||||
| enabled | `true` |
|
||||
|
||||
用户打开任务中心时,后端会幂等记录当日 `daily_login` 埋点并刷新任务进度。用户点击领取时,后端校验当日进度、领奖记录和配置状态,然后同事务写入领奖记录与钱包流水。
|
||||
用户成功登录时,认证链路会通过统一后端埋点 helper 幂等记录当日 `daily_login` 并刷新任务进度;用户打开任务中心只记录 `task_center_view` 浏览事件,不再承担每日登录事实写入。用户点击领取时,后端校验当日进度、领奖记录和配置状态,然后同事务写入领奖记录与钱包流水。
|
||||
|
||||
后台任务配置页的 `Event Key` 使用可搜索下拉控件,选项来自前端后台的埋点定义注册表。当前注册表默认包含 `daily_login`,展示中文名称和备注;后续新增任务依赖的埋点时,应先补充注册表,再开放运营配置。
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
|
||||
### 用户侧
|
||||
|
||||
- `GET /api/profile/tasks`:读取任务中心,同时记录当日登录埋点。
|
||||
- `POST /api/profile/tasks/{task_id}/claim`:领取任务奖励。
|
||||
- `GET /api/profile/tasks`:读取任务中心,并记录 `task_center_view` 浏览事件;不在此入口写入 `daily_login`。
|
||||
- `POST /api/profile/tasks/{task_id}/claim`:领取任务奖励,并记录 `task_reward_claim` 成功事件。
|
||||
|
||||
### 后台侧
|
||||
|
||||
@@ -70,9 +70,14 @@
|
||||
|
||||
不要把任务进度、领奖记录或钱包对账查询塞进 `docs/tracking/`,它们不是埋点系统本身。
|
||||
|
||||
## 8. 验收
|
||||
## 8. 通用后端埋点覆盖
|
||||
|
||||
后端用户行为埋点统一按 `docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md` 执行。该文档维护通用 procedure、api-server 中间件、事件清单、排除范围与查询验收口径;每日登录也走该统一路径,仅保留认证 helper 作为业务语义入口。
|
||||
|
||||
## 9. 验收
|
||||
|
||||
1. `profile_task_config` 默认存在 `daily_login`,后台可修改奖励、阈值、标题和启用状态。
|
||||
2. “我的”页可以打开每日任务面板,登录后任务可领取 `10` 光点。
|
||||
3. 重复打开任务中心不会重复增加领取资格,重复领奖不会重复发放。
|
||||
4. 表目录、迁移白名单、Rust/TypeScript 契约和前端入口同步更新。
|
||||
3. 登录成功会幂等记录 `daily_login`;重复打开任务中心只记录 `task_center_view`,不会重复增加领取资格。
|
||||
4. 重复领奖不会重复发放。
|
||||
5. 表目录、迁移白名单、Rust/TypeScript 契约和前端入口同步更新。
|
||||
|
||||
Reference in New Issue
Block a user