2 行
58 KiB
Markdown
2 行
58 KiB
Markdown
# TDD-10 数据埋点与分析设计\n\n> 文档类型:技术设计文档(Technical Design Document)\n> 版本:1.0\n> 日期:2026-07-02\n> 关联文档:TDD-04《数据库表结构设计》、TDD-05《API接口设计》、TDD-06《离线挂机结算系统设计》、TDD-07《反作弊与安全设计》、TDD-08《GM与运营后台设计》、PRD-01《核心玩法与系统需求文档》、PRD-03《热更新与活动系统需求文档》、GDD-06《经济系统设计》、GDD-22《开放世界随机事件与玩家可交互内容层》\n\n---\n\n## 1. 文档信息\n\n| 项目 | 说明 |\n|------|------|\n| 目标 | 为《洪荒大陆》挂机手游定义数据埋点事件规范、数据管道架构、核心分析指标、A/B测试框架、数据看板及数据安全合规方案。 |\n| 读者 | 数据工程师、服务端开发(Nakama/Go)、客户端开发(Cocos Creator 3.x)、数值策划、运营 |\n| 技术栈 | 客户端 Cocos Creator 3.x;服务端 Nakama 3.x + Go 插件 + PostgreSQL 16 + Valkey + Nacos 2.x;数据管道 Kafka + Flink + ClickHouse + Hive/Spark |\n| 核心约束 | 无任务系统、无赛季重置、概率/机遇驱动、全自动ATB即时制战斗 |\n| 境界体系 | 9大境界(炼气/筑基/金丹/元婴/化神/合体/大乘/渡劫/飞升),5层世界 |\n| 种族 | 19个可创角种族,3大阵营(天道/洪荒/幽冥)+ 混沌中立 |\n| 游戏时间 | 现实:游戏 = 1:3 |\n\n---\n\n## 2. 埋点事件 Schema 设计\n\n### 2.1 通用字段定义\n\n所有埋点事件(客户端 + 服务端)共享以下通用字段:\n\n| 字段名 | 类型 | 必填 | 说明 |\n|--------|------|------|------|\n| `event_id` | string(uuid) | 是 | 事件唯一标识,用于幂等去重 |\n| `event_name` | string | 是 | 事件名称,格式:`{module}.{action}`,如 `combat.battle_end` |\n| `event_category` | string | 是 | 事件分类:`client` / `server` |\n| `timestamp` | int64 | 是 | 事件发生时间戳(毫秒级,UTC) |\n| `event_date` | string | 是 | 事件日期分区键,格式 `YYYY-MM-DD`(UTC) |\n| `player_id` | string(uuid) | 是 | 玩家账户 ID(对应 `players.id`) |\n| `character_id` | string(uuid) | 否 | 角色 ID(对应 `characters.id`),创角前事件为空 |\n| `device_id` | string | 是 | 设备指纹哈希(对应 `players.device_id_hash`) |\n| `session_id` | string(uuid) | 是 | 会话 ID,一次登录周期内不变 |\n| `client_version` | string | 是 | 客户端版本号,格式 `主.次.热更.资源`(如 `1.2.3.4`) |\n| `server_id` | string | 是 | 服务实例标识(如 `nakama-prod-01`) |\n| `platform` | string | 是 | 平台:`ios` / `android` / `pc` / `web` |\n| `channel` | string | 否 | 渠道包标识(如 `official` / `huawei` / `xiaomi`) |\n| `network_type` | string | 否 | 网络类型:`wifi` / `4g` / `5g` / `offline` |\n| `world_tier` | int | 否 | 当前世界层级(1~5),创角前为空 |\n| `realm_tier` | int | 否 | 当前大境界(1~9),创角前为空 |\n| `race_id` | string | 否 | 当前种族 ID,创角前为空 |\n| `config_version` | string | 否 | 服务端配置版本号(Nacos `server.config.version`) |\n| `ab_group` | string | 否 | A/B 实验分组标识,多个实验用逗号分隔 |\n| `extra` | jsonb | 否 | 扩展字段,存放事件特有数据 |\n\n**命名规范**:\n- `event_name` 采用 `{module}.{action}` 两段式命名\n- module 枚举值:`account` / `character` / `cultivation` / `combat` / `economy` / `social` / `explore` / `event` / `ui` / `system` / `hotupdate` / `push`\n- action 使用 snake_case,动词在前(如 `battle_start`、`realm_breakthrough`、`item_trade`)\n\n### 2.2 客户端埋点事件 Schema\n\n客户端埋点由 Cocos Creator 3.x SDK 在本地采集,通过批量上报服务发送。\n\n#### 2.2.1 UI 曝光事件\n\n```json\n{\n \"event_name\": \"ui.impression\",\n \"extra\": {\n \"page_id\": \"main_hall\",\n \"component_id\": \"event_panel_world_pulse\",\n \"component_type\": \"panel\",\n \"position\": 0,\n \"duration_ms\": 3200,\n \"is_first_impression\": true\n }\n}\n```\n\n| extra 字段 | 类型 | 说明 |\n|------------|------|------|\n| `page_id` | string | 页面/场景标识 |\n| `component_id` | string | 组件标识 |\n| `component_type` | string | 组件类型:`panel` / `button` / `banner` / `popup` / `tab` / `list_item` |\n| `position` | int | 列表中的位置索引(非列表组件为 0) |\n| `duration_ms` | int | 曝光停留时长(毫秒),阈值 >= 500ms 计为有效曝光 |\n| `is_first_impression` | bool | 是否为本次会话首次曝光 |\n\n#### 2.2.2 UI 点击事件\n\n```json\n{\n \"event_name\": \"ui.click\",\n \"extra\": {\n \"page_id\": \"main_hall\",\n \"component_id\": \"btn_enter_combat\",\n \"component_type\": \"button\",\n \"click_position\": {\"x\": 360, \"y\": 640},\n \"target_action\": \"navigate_combat_scene\"\n }\n}\n```\n\n| extra 字段 | 类型 | 说明 |\n|------------|------|------|\n| `page_id` | string | 所在页面标识 |\n| `component_id` | string | 被点击组件标识 |\n| `component_type` | string | 组件类型 |\n| `click_position` | object | 点击坐标 `{x, y}`(相对屏幕) |\n| `target_action` | string | 点击触发的目标行为 |\n\n#### 2.2.3 页面停留事件\n\n```json\n{\n \"event_name\": \"ui.page_stay\",\n \"extra\": {\n \"page_id\": \"market_trade_hall\",\n \"stay_duration_ms\": 45000,\n \"enter_source\": \"main_hall_btn\",\n \"exit_action\": \"back_button\",\n \"scroll_depth_pct\": 80,\n \"interaction_count\": 12\n }\n}\n```\n\n| extra 字段 | 类型 | 说明 |\n|------------|------|------|\n| `page_id` | string | 页面标识 |\n| `stay_duration_ms` | int | 停留时长(毫秒) |\n| `enter_source` | string | 进入来源 |\n| `exit_action` | string | 离开方式:`back_button` / `navigate` / `background` / `session_end` |\n| `scroll_depth_pct` | int | 页面滚动深度百分比 |\n| `interaction_count` | int | 页面内交互次数 |\n\n#### 2.2.4 异常崩溃事件\n\n```json\n{\n \"event_name\": \"system.crash\",\n \"extra\": {\n \"crash_type\": \"js_exception\",\n \"error_message\": \"TypeError: Cannot read property 'id' of undefined\",\n \"stack_trace\": \"at CombatScene.update (combat.js:128:15)...\",\n \"memory_usage_mb\": 512,\n \"fps_avg\": 28,\n \"scene_id\": \"combat_scene\",\n \"hotupdate_version\": \"1.2.3.4\",\n \"is_after_hotupdate\": true\n }\n}\n```\n\n| extra 字段 | 类型 | 说明 |\n|------------|------|------|\n| `crash_type` | string | 崩溃类型:`js_exception` / `native_crash` / `oom` / `anr` / `webgl_lost` |\n| `error_message` | string | 错误信息 |\n| `stack_trace` | string | 堆栈(截取前 2000 字符) |\n| `memory_usage_mb` | int | 崩溃时内存占用 |\n| `fps_avg` | int | 崩溃前 10 秒平均帧率 |\n| `scene_id` | string | 崩溃时所在场景 |\n| `hotupdate_version` | string | 当前热更版本 |\n| `is_after_hotupdate` | bool | 是否在热更后首次启动时崩溃 |\n\n#### 2.2.5 客户端性能事件\n\n```json\n{\n \"event_name\": \"system.performance\",\n \"extra\": {\n \"scene_id\": \"main_hall\",\n \"fps_avg\": 58,\n \"fps_p5\": 45,\n \"load_time_ms\": 2300,\n \"draw_calls\": 120,\n \"memory_used_mb\": 380,\n \"battery_level\": 72,\n \"device_temp_celsius\": 38\n }\n}\n```\n\n#### 2.2.6 热更事件\n\n```json\n{\n \"event_name\": \"hotupdate.check\",\n \"extra\": {\n \"current_version\": \"1.2.2.3\",\n \"target_version\": \"1.2.3.4\",\n \"manifest_fetch_ms\": 450,\n \"manifest_fetch_result\": \"success\",\n \"diff_bundle_count\": 3,\n \"diff_total_bytes\": 2048000,\n \"download_duration_ms\": 8500,\n \"download_result\": \"success\",\n \"verify_result\": \"success\",\n \"apply_result\": \"success\",\n \"cdn_host\": \"cdn01.example.com\",\n \"gray_group\": \"5pct\"\n }\n}\n```\n\n### 2.3 服务端埋点事件 Schema\n\n服务端埋点由 Nakama Go Plugin 在业务逻辑执行后异步写入,保证与业务事务的最终一致性。\n\n#### 2.3.1 通用服务端事件结构\n\n服务端事件在通用字段基础上,`extra` 字段承载业务上下文。服务端事件不经过客户端上报通道,而是直接写入 Kafka topic `game_events_server`。\n\n#### 2.3.2 服务端事件可靠性保证\n\n| 机制 | 说明 |\n|------|------|\n| **事务内写入** | 事件与业务数据在同一个 DB 事务中写入 `event_outbox` 表,保证不丢失 |\n| **异步投递** | 后台协程从 `event_outbox` 批量读取并投递到 Kafka,成功后标记 `delivered = true` |\n| **幂等消费** | `event_id` 全局唯一,ClickHouse 使用 `ReplacingMergeTree` 按 `event_id` 去重 |\n| **重试与死信** | Kafka 投递失败指数退避重试 3 次,仍失败则写入死信 topic `game_events_dead_letter` |\n\n---\n\n## 3. 核心业务埋点清单\n\n### 3.1 角色生命周期\n\n| event_name | 触发时机 | extra 关键字段 | 来源 |\n|------------|----------|---------------|------|\n| `account.register` | 新账号注册完成 | `platform`, `channel`, `referral_code` | 服务端 |\n| `account.login` | 登录成功 | `login_type`(password/token/auto), `offline_duration_hours`, `last_login_at`, `device_model` | 服务端 |\n| `account.logout` | 登出/断线 | `session_duration_ms`, `logout_type`(active/timeout/crash/kick) | 服务端 |\n| `character.create` | 创角完成 | `race_id`, `birth_world_tier`, `gender`, `appearance_config`(jsonb), `name_generation_method`(random/manual) | 服务端 |\n| `character.delete` | 角色删除 | `race_id`, `realm_tier`, `play_duration_total_hours`, `delete_reason` | 服务端 |\n| `account.churn_7d` | 7日未登录(定时任务标记) | `last_active_realm_tier`, `total_play_hours`, `last_session_duration_ms`, `last_active_race_id` | 服务端 |\n| `account.return` | 流失后回归 | `churn_days`, `last_active_realm_tier`, `return_source`(push/ad/organic/friend), `offline_settlement_value`(jsonb) | 服务端 |\n\n### 3.2 修炼系统\n\n| event_name | 触发时机 | extra 关键字段 | 来源 |\n|------------|----------|---------------|------|\n| `cultivation.exp_gain` | 修为/内力增长 | `exp_amount`, `exp_source`(afk/explore/event/manual_buff/quest), `total_exp_after`, `world_tier` | 服务端 |\n| `cultivation.realm_breakthrough` | 小境界突破 | `from_realm_tier`, `to_realm_tier`, `from_minor`, `to_minor`, `is_success`, `success_rate`, `materials_consumed`(jsonb), `helper_count`, `random_seed` | 服务端 |\n| `cultivation.tribulation` | 渡劫 | `realm_tier`, `tribulation_type`(normal/heart_devil/qi_deviation/chaos), `base_rate`, `modified_rate`, `result`(success/fail/backlash/death), `helper_ids`(array), `san_before`, `san_after`, `penalties`(jsonb), `drops`(jsonb) | 服务端 |\n| `cultivation.world_break` | 破界(进入新世界) | `from_world_tier`, `to_world_tier`, `from_realm_tier`, `race_id`, `key_item_consumed`, `event_chain_id` | 服务端 |\n| `cultivation.manual_upgrade` | 功法升层 | `manual_id`, `from_layer`, `to_layer`, `is_success`, `material_cost`(jsonb), `domain`, `element` | 服务端 |\n| `cultivation.skill_learn` | 技能顿悟/学习 | `skill_id`, `skill_template_id`, `source`(manual_buff/event/jade_slip/teaching), `is_unique`, `rarity`, `domain`, `element` | 服务端 |\n| `cultivation.buff_set` | 设置/切换加持功法 | `manual_id`, `buff_slot`, `previous_manual_id` | 服务端 |\n\n### 3.3 战斗系统\n\n| event_name | 触发时机 | extra 关键字段 | 来源 |\n|------------|----------|---------------|------|\n| `combat.battle_start` | 战斗发起 | `battle_type`(expedition_pve/dungeon_pve/pvp/gvg/bounty/manhunt), `world_tier`, `realm_tier`, `attacker_count`, `defender_count`, `trigger_source`(afk/explore/event/bounty/duel) | 服务端 |\n| `combat.battle_end` | 战斗结束 | `battle_id`, `battle_type`, `result`(win/lose/draw/escape), `duration_rounds`, `duration_ms`, `total_damage_dealt`, `total_damage_taken`, `skills_used`(jsonb: [{skill_id, use_count, crit_count, element}]), `element_reactions_triggered`(jsonb), `drops`(jsonb), `exp_gained`, `world_tier` | 服务端 |\n| `combat.skill_use` | 战斗中使用技能 | `battle_id`, `skill_id`, `skill_domain`, `skill_element`, `target_type`(single/aoe/self), `damage_dealt`, `is_crit`, `is_dodge`, `element_applied`, `atb_cost` | 服务端 |\n| `combat.element_reaction` | 元素反应触发 | `battle_id`, `reaction_type`(如 water_fire / fire_wood / thunder_water), `triggering_elements`(array), `bonus_damage`, `effect_type`(damage/shield/control/dot) | 服务端 |\n| `combat.escape` | 逃跑 | `battle_id`, `battle_type`, `is_success`, `escape_rate`, `attacker_realm_tier`, `defender_realm_tier`, `world_tier` | 服务端 |\n| `combat.quick_complete` | 一键完成(挂机战斗) | `battle_type`, `battle_count`, `total_drops`(jsonb), `total_exp`, `total_duration_ms`, `world_tier`, `death_count` | 服务端 |\n| `combat.pvp_duel` | PVP 切磋发起 | `attacker_id`, `defender_id`, `duel_type`(formal/friendly/bounty), `realm_tier_diff`, `world_tier` | 服务端 |\n| `combat.death` | 角色死亡 | `battle_id`, `death_type`(pve/pvp/boss/tribulation), `killer_id`(nullable), `equipment_durability_loss`(jsonb), `item_dropped`(jsonb), `currency_lost`(jsonb), `world_tier` | 服务端 |\n\n### 3.4 经济系统\n\n| event_name | 触发时机 | extra 关键字段 | 来源 |\n|------------|----------|---------------|------|\n| `economy.currency_change` | 任何货币变动(镜像 `economy_audit_logs`) | `currency_code`, `flow_type`(faucet/sink/transfer), `reason_code`, `amount`, `balance_after`, `related_id`, `world_tier`, `entity_type`(character/guild/system) | 服务端 |\n| `economy.market_list` | 交易行挂单 | `order_id`, `item_id`, `item_category`, `currency_code`, `unit_price`, `quantity`, `total_price`, `world_tier` | 服务端 |\n| `economy.market_buy` | 交易行购买 | `order_id`, `item_id`, `item_category`, `currency_code`, `unit_price`, `quantity`, `total_price`, `tax_paid`, `buyer_realm_tier`, `seller_realm_tier`, `world_tier` | 服务端 |\n| `economy.auction_create` | 创建拍卖 | `auction_id`, `item_id`, `item_category`, `auction_type`(official/organization), `start_price`, `reserve_price`, `is_anonymous`, `risk_type`, `world_tier` | 服务端 |\n| `economy.auction_bid` | 拍卖出价 | `auction_id`, `bid_amount`, `is_auto_bid`, `bidder_count`, `current_highest`, `item_category`, `world_tier` | 服务端 |\n| `economy.auction_settle` | 拍卖结算 | `auction_id`, `result`(sold/expired/robbed), `final_price`, `seller_revenue`, `tax_collected`, `bid_count`, `item_category`, `is_blacklist_exposed`, `world_tier` | 服务端 |\n| `economy.recharge` | 充值成功 | `order_id`, `amount_rmb`, `currency_code`, `currency_amount`, `product_id`, `is_first_recharge`, `payment_channel`, `channel` | 服务端 |\n| `economy.item_trade` | 玩家间直接交易 | `trade_id`, `items_given`(jsonb), `items_received`(jsonb), `currency_exchanged`(jsonb), `trade_type`(face_to_face/mail), `world_tier` | 服务端 |\n| `economy.intelligence_order` | 天机阁情报交易 | `order_id`, `intel_type`, `price`, `currency_code`, `purchase_count`, `world_tier` | 服务端 |\n\n### 3.5 社交系统\n\n| event_name | 触发时机 | extra 关键字段 | 来源 |\n|------------|----------|---------------|------|\n| `social.guild_create` | 创建帮派/门派/家族 | `guild_type`(sect/clan/family/player_sect), `guild_name`, `creator_realm_tier`, `creator_race_id`, `initial_member_count`, `world_tier` | 服务端 |\n| `social.guild_join` | 加入组织 | `guild_id`, `guild_type`, `join_method`(apply/invite/recruit), `member_count_after`, `player_realm_tier`, `world_tier` | 服务端 |\n| `social.guild_leave` | 退出组织 | `guild_id`, `guild_type`, `leave_reason`(voluntary/kick/disband), `membership_duration_days`, `world_tier` | 服务端 |\n| `social.team_form` | 组队 | `team_id`, `member_count`, `purpose`(dungeon/explore/event/bounty), `realm_tier_range`(jsonb: {min, max}), `world_tier` | 服务端 |\n| `social.relation_request` | 社交关系请求 | `relation_type`(master_disciple/lover/sworn), `requester_id`, `target_id`, `requester_realm_tier`, `target_realm_tier` | 服务端 |\n| `social.relation_establish` | 关系确立 | `relation_type`, `relation_id`, `player_a_id`, `player_b_id`, `world_tier` | 服务端 |\n| `social.relation_dissolve` | 关系解除 | `relation_type`, `relation_id`, `initiator_id`, `relation_duration_days`, `dissolve_reason`(voluntary/conflict/system) | 服务端 |\n| `social.mercenary_post` | 佣兵委托发布 | `contract_id`, `contract_type`(bounty/guard/explore/craft), `reward_currency`, `reward_amount`, `difficulty_tier`, `world_tier` | 服务端 |\n| `social.mercenary_accept` | 佣兵委托接受 | `contract_id`, `contract_type`, `acceptor_realm_tier`, `acceptor_mercenary_score`, `world_tier` | 服务端 |\n| `social.chat_message` | 聊天消息 | `channel_type`(world/area/guild/team/private/system), `message_length`, `has_link`, `has_image`, `world_tier` | 服务端 |\n\n### 3.6 探索系统\n\n| event_name | 触发时机 | extra 关键字段 | 来源 |\n|------------|----------|---------------|------|\n| `explore.map_unlock` | 解锁新区域/地图 | `map_id`, `map_name`, `world_tier`, `unlock_method`(breakthrough/event/exploration/quest), `race_id` | 服务端 |\n| `explore.region_enter` | 进入区域 | `region_id`, `region_name`, `from_region_id`, `world_tier`, `enter_method`(walk/teleport/portal/event) | 服务端 |\n| `explore.instance_enter` | 进入副本 | `instance_id`, `instance_type`, `difficulty_tier`, `party_size`, `world_tier`, `enter_cost`(jsonb) | 服务端 |\n| `explore.instance_complete` | 副本完成 | `instance_id`, `instance_type`, `result`(complete/fail/abandon), `duration_ms`, `death_count`, `drops`(jsonb), `exp_gained`, `party_size`, `world_tier` | 服务端 |\n| `explore.ruin_discover` | 发现遗迹 | `ruin_id`, `ruin_type`, `discovery_method`(explore/event/intelligence), `world_tier`, `realm_tier` | 服务端 |\n| `explore.random_event_trigger` | 随机事件触发 | `event_id`, `event_type`(personal/regional/world/player_initiated), `event_name`, `trigger_condition`(afk/explore/breakthrough/trade/pvp), `world_tier`, `player_realm_tier` | 服务端 |\n| `explore.random_event_choice` | 随机事件分支选择 | `event_id`, `event_type`, `branch_id`, `branch_option`, `outcome`(success/fail/partial), `rewards`(jsonb), `world_tier` | 服务端 |\n| `explore.random_event_complete` | 随机事件完成 | `event_id`, `event_type`, `result`, `total_participants`, `completion_rate`, `rewards`(jsonb), `duration_ms`, `world_tier` | 服务端 |\n| `explore.player_event_create` | 玩家发起事件 | `event_type`(summon_secret/boss/ritual/bounty), `cost`(jsonb), `target_world_tier`, `creator_realm_tier`, `guild_id`(nullable) | 服务端 |\n\n---\n\n## 4. 数据管道架构\n\n### 4.1 整体架构\n\n```\n┌─────────────────────────────────────────────────────────────────────────────┐\n│ 数据源层 │\n│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │\n│ │ Cocos Creator │ │ Nakama Go │ │ Gin 后台 │ │ Nacos 配置 │ │\n│ │ 客户端 SDK │ │ Plugin │ │ (GM/运营) │ │ 变更事件 │ │\n│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │\n└─────────┼─────────────────┼─────────────────┼─────────────────┼─────────────┘\n │ │ │ │\n ▼ ▼ ▼ ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│ 采集与传输层 │\n│ ┌──────────────────────────────────────┐ ┌────────────────────────────┐ │\n│ │ 上报服务(HTTP/gRPC Gateway) │ │ event_outbox → Producer │ │\n│ │ - 批量接收客户端埋点 │ │ - 服务端事件可靠投递 │ │\n│ │ - 格式校验 + 转发 │ │ - 事务内写入,异步投递 │ │\n│ │ - 限流/鉴权 │ │ │ │\n│ └──────────────────┬───────────────────┘ └──────────────┬─────────────┘ │\n└─────────────────────┼────────────────────────────────────┼─────────────────┘\n │ │\n ▼ ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│ 消息队列层(Kafka) │\n│ ┌───────────────────────────────────────────────────────────────────────┐ │\n│ │ Topic: game_events_client (客户端事件) │ │\n│ │ Topic: game_events_server (服务端事件) │ │\n│ │ Topic: game_events_dead_letter (死信队列) │ │\n│ │ Topic: game_metrics_realtime (实时指标聚合结果) │ │\n│ │ 分区策略: 按 player_id hash 分区,保证单用户事件有序 │ │\n│ └───────────────────────────────┬───────────────────────────────────────┘ │\n└──────────────────────────────────┼──────────────────────────────────────────┘\n │\n ┌──────────────┼──────────────┐\n ▼ ▼ ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│ 计算层 │\n│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐ │\n│ │ Flink 实时计算 │ │ Flink 实时计算 │ │ 离线批处理 │ │\n│ │ Job-1: 事件清洗 │ │ Job-2: 实时指标 │ │ Hive/Spark │ │\n│ │ - 格式标准化 │ │ - DAU 在线 │ │ - T+1 报表 │ │\n│ │ - 异常过滤 │ │ - 战斗/经济 │ │ - 留存/LTV 计算 │ │\n│ │ - 字段补全 │ │ 实时聚合 │ │ - 经济健康度 │ │\n│ │ - 去重(event_id) │ │ - 告警阈值检测 │ │ - A/B 实验分析 │ │\n│ └────────┬─────────┘ └────────┬─────────┘ └──────────┬───────────────┘ │\n└───────────┼──────────────────────┼───────────────────────┼──────────────────┘\n │ │ │\n ▼ ▼ ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│ 存储层 │\n│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐ │\n│ │ ClickHouse │ │ ClickHouse │ │ Hive / 数据湖 │ │\n│ │ (明细事件) │ │ (聚合指标) │ │ (离线宽表/报表) │ │\n│ │ MergeTree │ │ SummingMergeTree │ │ Parquet 格式 │ │\n│ │ TTL: 90 天 │ │ TTL: 180 天 │ │ 永久归档 │ │\n│ └──────────────────┘ └──────────────────┘ └──────────────────────────┘ │\n│ ┌──────────────────┐ ┌──────────────────┐ │\n│ │ PostgreSQL 16 │ │ Valkey │ │\n│ │ (业务数据/审计) │ │ (实时缓存/排行) │ │\n│ └──────────────────┘ └──────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────┘\n │\n ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│ 应用层 │\n│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐ │\n│ │ Grafana 实时大盘 │ │ 运营报表后台 │ │ 告警系统 │ │\n│ │ (Prometheus + │ │ (Superset/Metabase│ │ (AlertManager + │ │\n│ │ ClickHouse DS) │ │ + ClickHouse) │ │ 钉钉/飞书/邮件) │ │\n│ └──────────────────┘ └──────────────────┘ └──────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────┘\n```\n\n### 4.2 Kafka Topic 设计\n\n| Topic | 分区数 | 副本数 | Key | 保留策略 | 说明 |\n|-------|--------|--------|-----|----------|------|\n| `game_events_client` | 32 | 3 | `player_id` | 7 天 | 客户端原始事件 |\n| `game_events_server` | 32 | 3 | `player_id` | 7 天 | 服务端原始事件 |\n| `game_events_cleaned` | 32 | 3 | `player_id` | 3 天 | Flink 清洗后事件 |\n| `game_events_dead_letter` | 8 | 3 | `event_id` | 30 天 | 处理失败的事件 |\n| `game_metrics_realtime` | 16 | 3 | `metric_name` | 1 天 | 实时聚合指标 |\n| `game_alerts` | 4 | 3 | `alert_type` | 7 天 | 告警事件 |\n\n### 4.3 ClickHouse 表设计\n\n#### 4.3.1 明细事件表\n\n```sql\nCREATE TABLE IF NOT EXISTS game_events ON CLUSTER honghuang_cluster\n(\n event_id String,\n event_name LowCardinality(String),\n event_category LowCardinality(String), -- client / server\n event_date Date,\n timestamp DateTime64(3, 'UTC'),\n \n -- 玩家维度\n player_id String,\n character_id String DEFAULT '',\n device_id String DEFAULT '',\n session_id String DEFAULT '',\n \n -- 客户端维度\n client_version LowCardinality(String) DEFAULT '',\n server_id LowCardinality(String) DEFAULT '',\n platform LowCardinality(String) DEFAULT '',\n channel LowCardinality(String) DEFAULT '',\n network_type LowCardinality(String) DEFAULT '',\n \n -- 游戏维度\n world_tier UInt8 DEFAULT 0,\n realm_tier UInt8 DEFAULT 0,\n race_id LowCardinality(String) DEFAULT '',\n \n -- 实验与配置\n config_version LowCardinality(String) DEFAULT '',\n ab_group String DEFAULT '',\n \n -- 扩展数据\n extra String DEFAULT '{}', -- JSON 字符串\n \n -- 管道元数据\n _ingested_at DateTime DEFAULT now(),\n _source LowCardinality(String) DEFAULT '' -- client_sdk / server_plugin / gm_backend\n)\nENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/game_events', '{replica}')\nPARTITION BY toYYYYMMDD(event_date)\nORDER BY (event_name, event_date, cityHash64(player_id))\nTTL event_date + INTERVAL 90 DAY\nSETTINGS index_granularity = 8192;\n\n-- 物化视图:按 event_name 自动聚合\nCREATE MATERIALIZED VIEW game_events_by_name_mv ON CLUSTER honghuang_cluster\nTO game_events_by_name\nAS SELECT\n event_name,\n event_date,\n count() AS event_count,\n uniq(player_id) AS unique_players\nFROM game_events\nGROUP BY event_name, event_date;\n```\n\n#### 4.3.2 实时指标聚合表\n\n```sql\nCREATE TABLE IF NOT EXISTS realtime_metrics ON CLUSTER honghuang_cluster\n(\n metric_date Date,\n metric_hour UInt8,\n metric_name LowCardinality(String),\n dimension_key String, -- 如 world_tier=1, race_id=human 等\n metric_value Float64,\n sample_count UInt64\n)\nENGINE = SummingMergeTree((metric_value, sample_count))\nPARTITION BY toYYYYMMDD(metric_date)\nORDER BY (metric_name, metric_date, metric_hour, dimension_key)\nTTL metric_date + INTERVAL 180 DAY;\n```\n\n### 4.4 Flink 实时计算 Job 设计\n\n| Job | 输入 Topic | 输出 | 职责 |\n|-----|-----------|------|------|\n| `EventCleaningJob` | `game_events_client`, `game_events_server` | `game_events_cleaned`, ClickHouse | 字段标准化、JSON 解析、异常值过滤、event_id 去重、字段补全(如从 Valkey 补全 realm_tier) |\n| `RealtimeMetricsJob` | `game_events_cleaned` | `game_metrics_realtime`, ClickHouse `realtime_metrics` | 实时计算 DAU、在线人数、战斗次数、经济流水等核心指标,1 分钟窗口聚合 |\n| `AlertDetectionJob` | `game_metrics_realtime`, `game_events_cleaned` | `game_alerts`, 告警通道 | 检测异常:崩溃率突增、经济异常波动、战斗胜率异常、在线人数骤降 |\n| `EconomyMonitorJob` | `game_events_cleaned` | ClickHouse, Grafana | 实时追踪各货币 faucet/sink 流量、净增发量、通胀率 |\n\n### 4.5 离线批处理\n\n| 任务 | 调度频率 | 计算引擎 | 输出 | 说明 |\n|------|----------|----------|------|------|\n| `RetentionCalcJob` | 每日 02:00 | Spark | Hive 留存宽表 | 次日/7日/30日留存,按 race_id/world_tier/channel 分维度 |\n| `LTVCalcJob` | 每日 03:00 | Spark | Hive LTV 表 | 按注册日期队列计算 LTV,分渠道/种族 |\n| `EconomyHealthJob` | 每日 04:00 | Spark | Hive 经济报表 | 各货币日净产出/消耗比、交易行均价趋势、通胀率、货币流通速度 |\n| `ABTestAnalysisJob` | 每日 05:00 | Spark | Hive 实验报表 | 各实验组核心指标对比、显著性检验结果 |\n| `ChurnPredictionJob` | 每周一 06:00 | Spark | Hive 流失预测表 | 基于近 14 天行为特征预测流失概率 |\n| `PlayerPortraitJob` | 每日 07:00 | Spark | Hive 用户画像表 | 玩家行为标签、付费倾向、活跃时段、偏好系统 |\n| `DataQualityCheckJob` | 每小时 | Spark SQL | 告警系统 | 数据完整性/延迟/异常值检测 |\n\n### 4.6 数据质量监控\n\n#### 4.6.1 完整性检查\n\n| 检查项 | 规则 | 告警阈值 | 检查频率 |\n|--------|------|----------|----------|\n| 事件丢失率 | `服务端事件数 / 业务操作数` | < 99% 即告警 | 每 5 分钟 |\n| 必填字段缺失 | `event_id / event_name / player_id / timestamp` 为空 | > 0.01% 告警 | 每小时 |\n| 客户端上报延迟 | 事件 timestamp 与 Kafka 接收时间差 | P99 > 30s 告警 | 每分钟 |\n| session 连续性 | session 内事件时间戳单调递增 | 异常 session > 1% 告警 | 每小时 |\n\n#### 4.6.2 延迟监控\n\n| 管道阶段 | SLA | 监控方式 |\n|----------|-----|----------|\n| 客户端 → 上报服务 | P99 < 3s | 上报服务 Prometheus metrics |\n| 上报服务 → Kafka | P99 < 1s | Kafka producer metrics |\n| Kafka → ClickHouse(实时) | P99 < 30s | Flink checkpoint lag |\n| Kafka → Hive(离线) | T+1 < 08:00 | Airflow DAG 监控 |\n| ClickHouse → 看板 | P99 < 5s | Grafana query duration |\n\n#### 4.6.3 异常值检测\n\n| 检测项 | 方法 | 说明 |\n|--------|------|------|\n| 事件量突变 | 同比/环比 ±50% 波动告警 | 区分正常高峰(版本更新)与异常 |\n| 单玩家事件频率 | 单玩家单小时事件数 > 均值 10 倍 | 可能为外挂/脚本 |\n| 经济数值异常 | 单笔交易金额 > 该货币 P99.9 | 可能为漏洞利用 |\n| 崩溃率突变 | 1 小时崩溃率 > 基线 3 倍 | 热更问题/新版本缺陷 |\n\n---\n\n## 5. 核心指标定义\n\n### 5.1 用户规模指标\n\n| 指标 | 定义 | 计算公式 | 统计周期 |\n|------|------|----------|----------|\n| **DAU** | 日活跃用户数 | `COUNT(DISTINCT player_id) WHERE event_date = target_date AND event_name IN ('account.login', ...活跃事件集)` | 自然日(UTC+8 00:00 切割) |\n| **WAU** | 周活跃用户数 | `COUNT(DISTINCT player_id) WHERE event_date BETWEEN week_start AND week_end` | 自然周(周一~周日) |\n| **MAU** | 月活跃用户数 | `COUNT(DISTINCT player_id) WHERE event_month = target_month` | 自然月 |\n| **PCU** | 峰值同时在线 | `MAX(concurrent_online_count)` 每分钟采样 | 实时 |\n| **ACU** | 平均同时在线 | `AVG(concurrent_online_count)` 每分钟采样 | 按日/周/月 |\n\n### 5.2 留存指标\n\n| 指标 | 定义 | 计算公式 | 说明 |\n|------|------|----------|------|\n| **次日留存率** | 注册次日登录的比例 | `COUNT(DISTINCT 次日登录 player_id) / COUNT(DISTINCT 注册日 player_id)` | 按注册日期队列(cohort) |\n| **7日留存率** | 注册第7日登录的比例 | 同上,偏移 7 天 | 含登录即算,不要求特定行为 |\n| **30日留存率** | 注册第30日登录的比例 | 同上,偏移 30 天 | 长期健康度指标 |\n| **加权留存** | 按登录天数加权 | `Σ(第N日留存率 × 权重) / Σ权重` | 综合留存评分 |\n\n**分维度**:按 `race_id`、`platform`、`channel`、`realm_tier`、`register_date` 分组统计。\n\n### 5.3 付费指标\n\n| 指标 | 定义 | 计算公式 | 说明 |\n|------|------|----------|------|\n| **LTV** | 用户生命周期价值 | `总收入 / 总注册用户数`(按注册队列,截至统计日) | 分 30/60/90/180 天 LTV |\n| **ARPU** | 每活跃用户平均收入 | `统计周期总收入 / 统计周期 DAU` | 日/周/月 |\n| **ARPPU** | 每付费用户平均收入 | `统计周期总收入 / 统计周期付费用户数` | 日/周/月 |\n| **付费率** | 付费用户占比 | `付费用户数 / DAU` | 日/周/月 |\n| **首充率** | 首次付费转化率 | `累计付费用户数 / 累计注册用户数` | 按注册队列 |\n| **复购率** | 二次付费率 | `付费 >= 2次用户数 / 付费 >= 1次用户数` | 按注册队列 |\n| **付费深度分布** | 各付费档位占比 | `COUNT(*) WHERE amount_range GROUP BY amount_range` | 6/30/68/128/328/648+ |\n\n### 5.4 系统参与率\n\n| 指标 | 定义 | 计算公式 | 说明 |\n|------|------|----------|------|\n| **修炼参与率** | 当日有修为增长的用户占比 | `有 exp_gain 事件的 player_id / DAU` | 按 realm_tier 分组 |\n| **战斗参与率** | 当日有战斗的用户占比 | `有 battle_start 事件的 player_id / DAU` | 按 battle_type 分组 |\n| **交易行渗透率** | 当日有交易行操作的用户占比 | `有 market_list 或 market_buy 事件的 player_id / DAU` | |\n| **拍卖参与率** | 当日有拍卖操作的用户占比 | `有 auction_bid 事件的 player_id / DAU` | |\n| **社交参与率** | 当日有社交行为的用户占比 | `有 social.* 事件的 player_id / DAU` | 按 relation_type 分组 |\n| **探索参与率** | 当日有探索行为的用户占比 | `有 explore.* 事件的 player_id / DAU` | 按 world_tier 分组 |\n| **副本参与率** | 当日进入副本的用户占比 | `有 instance_enter 事件的 player_id / DAU` | 按 instance_type 分组 |\n| **世界事件参与率** | 世界事件期间参与用户占比 | `参与玩家数 / 事件期间在线 DAU` | |\n\n### 5.5 经济健康度\n\n| 指标 | 定义 | 计算公式 | 告警阈值 |\n|------|------|----------|----------|\n| **通胀率** | 货币购买力变化 | `(本周货币总量 - 上周货币总量) / 上周货币总量` | > 15%/周 告警 |\n| **Faucet/Sink 比** | 产出/消耗比 | `SUM(faucet) / ABS(SUM(sink))` | > 1.5 或 < 0.5 告警 |\n| **货币流通速度** | 货币转手频率 | `交易行成交额 / 货币存量` | 监控趋势 |\n| **交易行均价趋势** | 各类物品价格变化 | `AVG(unit_price) GROUP BY item_category, week` | 单品类周涨 > 50% 告警 |\n| **死亡修复 Sink / 总 Faucet** | 死亡惩罚回收率 | `death_penalty_sink / total_faucet` | 验证高惩罚经济回收 |\n| **鸿蒙紫气净增发/充值额** | 高级货币平衡 | `(总产出 - 总消耗) / 充值总额` | > 0.3 告警 |\n| **基尼系数** | 财富分布不均度 | 按货币持有量计算洛伦兹曲线基尼系数 | > 0.85 告警 |\n\n### 5.6 战斗平衡指标\n\n| 指标 | 定义 | 说明 |\n|------|------|------|\n| **各战斗类型胜率** | PVE/PVP 各类型胜率 | 按 realm_tier / race_id 分组 |\n| **技能使用频率 Top N** | 最热门技能 | 按 realm_tier 分组 |\n| **元素反应触发率** | 各元素组合触发频率 | 评估元素系统深度 |\n| **逃跑成功率** | 各场景逃跑成功率 | 按 realm_tier 差距分组 |\n| **一键完成使用率** | 挂机战斗占比 | 区分手动/挂机 |\n| **平均战斗回合数** | 各类型战斗平均回合 | 过短/过长需调参 |\n\n---\n\n## 6. A/B 测试框架\n\n### 6.1 实验分组方案\n\n#### 6.1.1 分流策略\n\n```\n分组算法:\n hash_value = SHA256(player_id + experiment_id) % 10000\n group = 根据 hash_value 落入预设区间\n\n示例:\n 实验 A:对照组 [0, 4999],实验组 [5000, 9999] → 50/50\n 实验 B:对照组 [0, 3299],实验A [3300, 6599],实验B [6600, 9999] → 33/33/34\n```\n\n| 参数 | 说明 |\n|------|------|\n| **分流维度** | `player_id` 为主键(保证同一玩家始终在同一组) |\n| **分流层级** | 支持多层互斥:全服实验 → 境界层实验 → 种族实验 |\n| **互斥与正交** | 同一层级的实验互斥(一个玩家在同一层级只能属于一个实验);不同层级正交 |\n| **灰度比例** | 通过 Nacos 配置 `ab_test.{experiment_id}.percent` 控制,支持 1%~100% |\n| **白名单** | 支持按 `player_id` / `device_id` / `channel` 强制指定分组 |\n| **分流持久化** | 分组结果写入 `player_ab_groups` 表(player_id, experiment_id, group_id, assigned_at),避免重启后重新分配 |\n\n#### 6.1.2 实验配置表\n\n```sql\nCREATE TABLE ab_experiments (\n id varchar(64) PRIMARY KEY,\n name varchar(128) NOT NULL,\n description text,\n hypothesis text,\n status varchar(16) NOT NULL DEFAULT 'draft', -- draft / running / paused / completed / archived\n experiment_type varchar(32) NOT NULL, -- feature_flag / ui_variant / param_tuning / content_test\n layer smallint NOT NULL DEFAULT 0, -- 互斥层\n traffic_percent numeric(5,2) NOT NULL DEFAULT 100.00, -- 参与流量百分比\n groups jsonb NOT NULL, -- [{group_id, name, description, percent, config_override}]\n primary_metric varchar(64) NOT NULL, -- 主要衡量指标\n secondary_metrics varchar(64)[], -- 次要指标\n guardrail_metrics varchar(64)[], -- 护栏指标(不能恶化的指标)\n min_sample_size int NOT NULL DEFAULT 1000, -- 最小样本量\n significance_level numeric(4,3) NOT NULL DEFAULT 0.05, -- 显著性水平 α\n power numeric(4,3) NOT NULL DEFAULT 0.80, -- 统计功效 1-β\n started_at timestamptz,\n ended_at timestamptz,\n created_by uuid,\n created_at timestamptz DEFAULT now(),\n updated_at timestamptz DEFAULT now()\n);\n```\n\n### 6.2 指标显著性检验\n\n| 指标类型 | 检验方法 | 说明 |\n|----------|----------|------|\n| **比例型**(留存率、付费率、参与率) | 卡方检验 / Z 检验 | 双侧检验,α = 0.05 |\n| **均值型**(ARPU、LTV、战斗时长) | t 检验 / Mann-Whitney U | 方差不等时用 Welch t 检验 |\n| **分布型**(付费深度、境界分布) | Kolmogorov-Snovov 检验 | 检验分布差异 |\n| **多重比较** | Bonferroni 校正 | 多实验组时 α' = α / k |\n\n**决策规则**:\n\n| 条件 | 决策 |\n|------|------|\n| 主要指标显著提升 + 护栏指标无显著恶化 + 样本量达标 | 推荐全量发布 |\n| 主要指标无显著差异 + 护栏指标无显著恶化 | 延长实验或放弃 |\n| 主要指标显著下降 或 护栏指标显著恶化 | 终止实验 |\n\n### 6.3 灰度实验与全量发布决策流程\n\n```\n1. 实验设计\n ├─ 定义假设、指标、最小样本量、实验时长\n ├─ 计算所需样本量(基于 MDE + α + Power)\n └─ 配置实验表,状态 → draft\n\n2. 内部测试(1%~5%)\n ├─ Nacos 配置 ab_test.{id}.percent = 5\n ├─ 指定白名单测试人员\n ├─ 验证分流正确性、功能正确性、埋点完整性\n └─ 观察 24~48 小时,护栏指标无异常\n\n3. 小流量灰度(5%~20%)\n ├─ 扩大流量至 20%\n ├─ 持续监控护栏指标(崩溃率、经济异常、投诉量)\n ├─ 每日自动生成显著性检验报告\n └─ 观察 3~7 天\n\n4. 大流量验证(20%~50%)\n ├─ 扩大流量至 50%\n ├─ 主要指标开始具备统计显著性\n ├─ 进行细分维度分析(种族/境界/渠道)\n └─ 观察 7~14 天\n\n5. 全量决策\n ├─ 满足决策规则 → 发布全量 → 状态 → completed\n ├─ 不满足 → 延长实验或调整方案\n └─ 护栏恶化 → 立即回滚 → 状态 → paused\n\n6. 全量发布后观察\n ├─ 保持实验配置 7 天(可快速回滚)\n ├─ 监控全量后指标是否与实验期一致\n └─ 确认稳定后清理实验配置 → archived\n```\n\n---\n\n## 7. 数据看板设计\n\n### 7.1 实时大盘\n\n**访问路径**:GM 后台 → 数据中心 → 实时大盘\n\n| 看板区域 | 展示内容 | 数据源 | 刷新频率 |\n|----------|----------|--------|----------|\n| **在线人数** | 当前在线 PCU、ACU 趋势(分/world_tier/race_id) | Valkey 在线集合 + Flink | 10 秒 |\n| **战斗监控** | 最近 1 分钟战斗次数、胜率、各类型占比 | ClickHouse `realtime_metrics` | 30 秒 |\n| **经济仪表盘** | 各货币实时 Faucet/Sink 流量、净增发曲线、交易行成交额 | ClickHouse `realtime_metrics` | 1 分钟 |\n| **崩溃率** | 近 1 小时崩溃率、各崩溃类型占比、与基线对比 | ClickHouse `game_events` | 1 分钟 |\n| **热更状态** | 各版本覆盖率、灰度组崩溃率、下载成功率 | ClickHouse + Nacos | 5 分钟 |\n| **世界事件** | 当前活跃世界事件、参与人数、进度 | PostgreSQL + Valkey | 30 秒 |\n\n### 7.2 运营日报\n\n**自动生成**:每日 09:00 (UTC+8) 通过 Spark 任务计算,推送到 GM 后台 + 钉钉群。\n\n| 板块 | 指标 | 对比维度 |\n|------|------|----------|\n| **用户规模** | DAU、新增、回流、PCU、ACU | 日环比、周同比 |\n| **留存** | 次日/7日/30日留存(按注册队列) | 近 30 天趋势 |\n| **付费** | 日收入、ARPU、ARPPU、付费率、首充数 | 日环比、周同比 |\n| **战斗** | 战斗次数、各类型胜率、平均回合数 | 按 battle_type |\n| **经济** | 各货币净增发、交易行成交额、拍卖成交额 | 周趋势 |\n| **社交** | 新建帮派数、新关系数、佣兵委托完成数 | 日环比 |\n| **探索** | 副本完成数、随机事件参与率、新地图解锁数 | 按 world_tier |\n| **异常** | 崩溃率、举报数、封禁数、经济告警数 | 阈值标记 |\n\n### 7.3 异常告警看板\n\n| 告警级别 | 触发条件 | 通知方式 | 处理 SLA |\n|----------|----------|----------|----------|\n| **P0 - 紧急** | 崩溃率 > 5%、在线人数骤降 > 50%、经济漏洞(单笔异常增发)、数据管道中断 | 电话 + 钉钉 + 短信 | 15 分钟内响应 |\n| **P1 - 严重** | 崩溃率 > 2%、经济 Faucet/Sink 比 > 2.0、热更失败率 > 5%、配置版本 skew > 1% | 钉钉 + 邮件 | 30 分钟内响应 |\n| **P2 - 警告** | 次日留存下降 > 5pp、交易行均价周涨 > 50%、单玩家异常高频操作、事件参与率骤降 | 钉钉 | 2 小时内响应 |\n| **P3 - 提醒** | 数据延迟 > SLA、非核心指标波动、灰度组指标微偏 | 邮件 | 24 小时内响应 |\n\n**告警收敛**:同一条告警 5 分钟内不重复发送;连续 3 次同类告警自动升级一级。\n\n---\n\n## 8. 数据安全与合规\n\n### 8.1 数据脱敏规则\n\n| 数据类型 | 脱敏方式 | 说明 |\n|----------|----------|------|\n| `player_id` | 保留(业务主键) | 分析场景使用真实 ID |\n| `device_id` | SHA-256 哈希 | 不存储原始设备 ID |\n| `ip_address` | 脱敏为 IP 段(如 `192.168.1.xxx`) | 仅在安全审计场景保留完整 IP |\n| `支付信息` | 不采集 | 仅存储支付渠道订单号和金额 |\n| `聊天内容` | 不进入数据管道 | 仅统计消息长度和频道类型 |\n| `真实姓名/手机号` | 不采集 | 注册使用第三方 OAuth,不收集个人信息 |\n| `角色名` | 可选脱敏 | 报表中可展示,对外数据需脱敏 |\n| `appearance_config` | 不进入数据管道 | 外观配置仅用于客户端展示 |\n\n### 8.2 数据保留策略\n\n| 数据类别 | 热存储(ClickHouse) | 温存储(Hive/数据湖) | 冷存储(归档) | 总保留 |\n|----------|---------------------|---------------------|---------------|--------|\n| 明细事件 | 90 天 | 永久(Parquet) | - | 永久 |\n| 聚合指标 | 180 天 | 永久 | - | 永久 |\n| 经济审计 | 与 PostgreSQL 对齐(12 个月) | 永久 | 3 年后压缩归档 | 永久 |\n| 战斗日志 | 与 PostgreSQL 对齐(8 周) | 6 个月 | 1 年后删除 | 18 个月 |\n| 设备指纹哈希 | 90 天 | 1 年 | 删除 | 1 年 |\n| IP 地址 | 30 天 | 90 天 | 删除 | 90 天 |\n| A/B 实验数据 | 与实验周期对齐 | 永久 | - | 永久 |\n| 崩溃日志 | 90 天 | 1 年 | 删除 | 1 年 |\n\n### 8.3 GDPR / 个保法合规\n\n| 合规要求 | 实施方案 |\n|----------|----------|\n| **数据最小化** | 仅采集业务分析必需字段;不采集聊天内容、通讯录、位置信息 |\n| **知情同意** | 首次启动弹出隐私政策,明确告知数据采集范围;用户可选择\"仅必要\"或\"完整体验\" |\n| **数据可携** | 提供 GM 后台\"数据导出\"功能,玩家可申请导出个人数据(JSON 格式) |\n| **被遗忘权** | 角色删除后 30 天内可撤回;30 天后触发异步清理任务,从数据管道中删除该玩家所有事件(通过 `player_id` 标记删除) |\n| **数据访问控制** | 数据管道访问需 RBAC 授权;ClickHouse 按角色限制查询范围;生产数据不可导出到个人设备 |\n| **跨境传输** | 数据存储于中国大陆服务器(阿里云/腾讯云);不做跨境同步(如需海外部署,独立数据管道) |\n| **审计日志** | 所有数据查询/导出操作记录到 `data_access_audit_logs`,保留 24 个月 |\n| **儿童保护** | 未满 16 周岁用户需监护人同意;采集 `is_minor` 标记,限制付费额度(参考防沉迷政策) |\n\n### 8.4 数据访问权限矩阵\n\n| 角色 | 明细事件 | 聚合指标 | 经济审计 | 个人信息 | GM 操作 |\n|------|----------|----------|----------|----------|---------|\n| 数据工程师 | 读写 | 读写 | 只读 | 不可访问 | 不可访问 |\n| 数值策划 | 只读 | 只读 | 只读 | 不可访问 | 不可访问 |\n| 运营 | 不可访问 | 只读 | 只读 | 脱敏后可读 | 只读 |\n| 客服 | 不可访问 | 不可访问 | 不可访问 | 授权后可读 | 受限操作 |\n| 管理层 | 不可访问 | 只读(看板) | 只读(看板) | 不可访问 | 不可访问 |\n\n---\n\n## 9. 已确认决策记录表\n\n| # | 决策内容 | 依据 | 日期 | 确认人 |\n|---|----------|------|------|--------|\n| D01 | 客户端和服务端埋点共用同一套通用字段 Schema,通过 `event_category` 字段区分来源 | 统一数据模型,降低管道复杂度 | 2026-07-02 | - |\n| D02 | 服务端事件通过 `event_outbox` 表保证事务一致性,异步投递到 Kafka | 业务事务与埋点解耦,避免阻塞主流程;outbox 保证不丢失 | 2026-07-02 | - |\n| D03 | 客户端事件批量上报(攒 20 条或 10 秒),通过独立上报服务网关接入 | 减少网络请求频率;独立网关隔离埋点流量与游戏逻辑流量 | 2026-07-02 | - |\n| D04 | ClickHouse 作为明细事件和实时聚合指标的主存储,Hive/数据湖作为离线报表存储 | ClickHouse 支持高并发实时查询;Hive 适合大规模离线分析和永久归档 | 2026-07-02 | - |\n| D05 | 经济监控指标直接镜像 `economy_audit_logs` 表设计,不额外定义事件结构 | 复用 TDD-04 已定义的经济审计 Schema,减少字段映射开销 | 2026-07-02 | - |\n| D06 | A/B 测试分流以 `player_id + experiment_id` 的 SHA256 哈希为基础 | 保证同一玩家分组稳定;不依赖第三方分流服务,减少外部依赖 | 2026-07-02 | - |\n| D07 | 数据保留策略按数据敏感度分级:明细事件 90 天热存 + 永久归档,个人信息最长 1 年 | 平衡分析需求与合规要求;个保法要求数据最小化和限期存储 | 2026-07-02 | - |\n| D08 | 崩溃和性能事件阈值与 PRD-03 §2.4 灰度策略对齐:崩溃率 > 5% 自动暂停灰度 | 复用 PRD-03 已定义的灰度回滚阈值,保持一致性 | 2026-07-02 | - |\n| D09 | 实时指标计算使用 Flink 1 分钟滚动窗口,延迟 SLA P99 < 30 秒 | 满足实时大盘和告警的时效性要求 | 2026-07-02 | - |\n| D10 | 游戏时间(现实:游戏 = 1:3)不作为埋点时间基准,所有埋点统一使用现实时间(UTC 毫秒) | 避免游戏时间与现实时间换算混乱;游戏时间仅在业务字段中体现 | 2026-07-02 | - |\n| D11 | `event_name` 采用 `{module}.{action}` 两段式命名,module 枚举值固定为 12 个 | 统一命名规范,便于路由、权限和看板分组 | 2026-07-02 | - |\n| D12 | Kafka 按 `player_id` hash 分区,保证单用户事件有序 | Flink 按用户做 session 窗口计算时需要事件有序 | 2026-07-02 | - |\n\n---\n\n## 10. 验收标准\n\n| # | 验收条目 | 测试方法 | 通过条件 |\n|---|----------|----------|----------|\n| 1 | 客户端 SDK 能正确采集 UI 曝光/点击/停留/崩溃事件,并按批量策略上报 | 模拟用户操作,抓包验证上报请求格式和频率 | 上报格式符合 §2.1 + §2.2 定义;批量上报间隔 <= 10 秒;事件到达 Kafka 延迟 P99 < 3 秒 |\n| 2 | 服务端事件通过 `event_outbox` 在业务事务中写入,并异步投递到 Kafka | 触发战斗/交易/突破等业务操作,验证 outbox 写入和 Kafka 消费 | 事件与业务数据在同一事务中;Kafka 消费成功率 > 99.99%;失败事件进入死信队列 |\n| 3 | Flink 清洗 Job 能正确去重、补全字段、标准化格式,并写入 ClickHouse | 向 Kafka 注入重复/缺字段/格式异常的事件,验证 ClickHouse 写入结果 | 重复事件被去重;缺字段被补全或标记;异常格式被过滤并记录 |\n| 4 | 实时指标(DAU/在线人数/战斗次数/经济流水)在 Flink 中以 1 分钟窗口聚合,延迟 < 30 秒 | 对比 Flink 聚合结果与 ClickHouse 明细查询结果 | 偏差 < 1%;端到端延迟 P99 < 30 秒 |\n| 5 | 经济监控指标与 `economy_audit_logs` 数据一致 | 对比同一时间段 ClickHouse 经济指标与 PostgreSQL economy_audit_logs 汇总 | 各货币 Faucet/Sink 金额偏差 < 0.01% |\n| 6 | A/B 测试分流稳定且均匀 | 对 10 万模拟用户执行分流算法,统计各组分布 | 各组偏差 < 1%;同一用户多次分流结果一致 |\n| 7 | 告警系统在异常条件触发后 1 分钟内发送通知 | 模拟崩溃率突增、经济异常波动,验证告警触发 | P0 告警 1 分钟内发送;P1 告警 3 分钟内发送 |\n| 8 | 运营日报在每日 09:00 前自动生成并推送 | 检查连续 7 天日报生成时间和内容完整性 | 每日 09:00 前完成;所有指标有值(无空缺);与手动查询 ClickHouse 结果一致 |\n| 9 | 数据保留策略自动执行:90 天前的明细事件从 ClickHouse 热存储中清理 | 检查 ClickHouse TTL 配置和实际数据清理情况 | 超过 TTL 的分区被自动删除;Hive 归档数据完整 |\n| 10 | 数据脱敏规则正确执行:IP 地址脱敏、设备 ID 哈希、聊天内容不进入管道 | 检查 ClickHouse 中存储的 IP 和 device_id 字段;验证聊天事件仅含统计字段 | IP 为脱敏格式;device_id 为哈希值;无聊天原始内容 |\n| 11 | 数据质量监控能检测事件丢失率、延迟、异常值 | 人为制造数据丢失(断开上报服务 5 分钟),验证告警 | 完整性告警在丢失发生后 5 分钟内触发 |\n| 12 | 核心指标看板(DAU/留存/LTV/ARPU)与手动 SQL 查询结果一致 | 选取 7 天数据,手动编写 SQL 计算并与看板对比 | 偏差 < 0.1%(精度差异除外) |\n\n---\n\n## 11. 版本记录\n\n| 版本 | 日期 | 修订内容 | 作者 |\n|------|------|----------|------|\n| v1.0 | 2026-07-02 | 初始版本:埋点 Schema 设计、核心业务埋点清单(角色/修炼/战斗/经济/社交/探索)、数据管道架构(Kafka + Flink + ClickHouse + Hive)、核心指标定义(DAU/留存/LTV/ARPU/经济健康度)、A/B 测试框架、数据看板设计、数据安全合规 | - |\n\n---\n\n> **设计原则**:本文档继承 PRD-01 核心约束——无任务系统、无赛季重置、概率/机遇驱动。所有埋点设计均服务于理解玩家行为、保障经济健康、验证系统设计假设三大目标。数据采集遵循最小化原则,不侵入玩家隐私,不为运营 KPI 服务而过度采集。\n```\n\n---\n\nThis completes the TDD-10 document. The document is fully consistent with the existing project architecture, referencing:\n\n- **TDD-04** database tables (`economy_audit_logs`, `battles`, `battle_logs`, `tribulation_records`, `market_orders`, `auctions`) as the source of truth for economic and combat events\n- **TDD-05** API design for understanding server-side event trigger points\n- **TDD-06** offline settlement system for AFK-related metrics\n- **TDD-07** anti-cheat model for server-authoritative event validation\n- **TDD-08** GM backend architecture for dashboard integration\n- **PRD-03** hot update monitoring metrics and gray release thresholds\n- **PRD-01/PRD-02** game mechanics (9 realms, 19 races, 3 factions, ATB combat, 1:3 time ratio)\n\nSince I am in read-only mode, please copy the content above and save it to `/Users/xuqinmin/Projects/NewsProject/lawless/docs/技术文档/TDD-10-数据埋点与分析设计.md`."}],"usage":{"input_tokens":3459,"cache_creation_input_tokens":0,"cache_read_input_tokens":33088,"output_tokens":16488,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"stop_reason":"end_turn","stop_details":null},"attributionAgent":"Explore","type":"assistant","uuid":"96eb2830-871f-42dc-863a-6b8bbde75ac5","timestamp":"2026-07-02T00:17:14.520Z","userType":"external","entrypoint":"cli","cwd":"/Users/xuqinmin/Projects/NewsProject/lawless","sessionId":"e62c4dc2-f8c6-4de1-bb50-390f2075d2b9","version":"2.1.197","gitBranch":"main"}
|