# My Page Overview API 本文定义 App 底部 `我的` 页首屏聚合接口。该接口只返回首屏渲染需要的轻量摘要:用户资料、我的邀请码、核心资产余额、身份开关、消息红点和入口显隐。流水、成员列表、背包完整列表、工资明细、邀请列表和团队统计必须放到二级页接口。 ## Goals - `我的` 页首屏一次请求拿到稳定摘要,减少 App 冷启动后的接口数量。 - 聚合已有轻量能力:`/users/me`、`/wallet/me/balances`、`/users/me/host-identity`、`/messages/tabs`。 - 避免实时 `COUNT(*)`、扫流水、扫成员、扫背包、现算工资。 - 后端控制入口显隐,App 不硬编码复杂身份组合。 - 所有数据按 `app_code` 和当前 JWT `user_id` 隔离。 ## Endpoint ```http GET /api/v1/users/me/overview Authorization: Bearer ``` 响应仍使用 gateway `/api/v1` envelope: ```json { "code": "OK", "message": "ok", "request_id": "req_01", "data": { "profile": {}, "wallet": {}, "invite": {}, "roles": {}, "badges": {}, "entries": [], "server_time_ms": 1777996800000 } } ``` ## Response Shape ```json { "profile": { "user_id": "10001", "display_user_id": "163000", "default_display_user_id": "163000", "display_user_id_kind": "default", "display_user_id_expires_at_ms": 0, "username": "Alice", "avatar": "https://cdn.example.com/avatar.png", "gender": "female", "birth": "2000-01-01", "country": "US", "country_id": 1, "country_name": "United States", "country_display_name": "United States", "region_id": 10, "region_code": "north_america", "region_name": "North America", "language": "en", "profile_completed": true, "onboarding_status": "completed" }, "wallet": { "balances": [ { "asset_type": "COIN", "available_amount": 12000, "frozen_amount": 0, "version": 8 }, { "asset_type": "DIAMOND", "available_amount": 300, "frozen_amount": 0, "version": 3 }, { "asset_type": "USD_BALANCE", "available_amount": 2500, "frozen_amount": 500, "version": 2 } ], "recharge_enabled": true, "diamond_exchange_enabled": true, "withdraw_enabled": true }, "invite": { "my_invite_code": "A1B2C3", "invite_enabled": true, "invite_count": 12, "valid_invite_count": 3, "valid_invite_threshold_coin": 80000 }, "roles": { "is_host": true, "is_agent": false, "is_bd": false, "is_bd_leader": false, "is_coin_seller": false }, "badges": { "unread_system_messages": 2, "unread_activity_messages": 1, "pending_role_invitations": 0 }, "entries": [ { "key": "wallet", "title": "Wallet", "visible": true, "badge_count": 0 }, { "key": "host_center", "title": "Host Center", "visible": true, "badge_count": 0 }, { "key": "coin_seller_center", "title": "Coin Seller", "visible": false, "badge_count": 0 }, { "key": "backpack", "title": "Backpack", "visible": true, "badge_count": 0 }, { "key": "settings", "title": "Settings", "visible": true, "badge_count": 0 } ], "server_time_ms": 1777996800000 } ``` ## Field Sources | Field | Source | Query Pattern | | --- | --- | --- | | `profile` | `user-service.GetUser` | single row by `(app_code, user_id)` | | `wallet.balances` | `wallet-service.GetBalances` | point read for `COIN`、`DIAMOND`、`USD_BALANCE` | | `invite.my_invite_code` | `user-service` invite code module | point read active primary code by `(app_code, user_id)` | | `invite.invite_count` | invite read model | users that successfully filled this invite code; without read model return `0` or omit | | `invite.valid_invite_count` | invite read model | invited users whose cumulative eligible recharge reaches the configured threshold | | `invite.valid_invite_threshold_coin` | invite validity policy | current configured threshold, default `80000` `COIN` | | `roles` | `user-service` host identity module | point read role facts or future role summary read model | | `badges.unread_*` | message inbox owner | counter/read model, not message list scan | | `entries` | gateway composition rules | computed from `roles` and feature flags | | `server_time_ms` | gateway | current server clock | 当前实现已有 `/users/me`、`/wallet/me/balances`、`/users/me/host-identity`、`/messages/tabs`。`/users/me/overview` 可以先在 `gateway-service` 聚合这些能力;后续如果角色判断的三次 not-found RPC 变慢,再在 `user-service` 增加 `GetUserRoleSummary` 内部 RPC。 ## Asset Rules 首屏只返回三类资产: | Asset | Show Rule | | --- | --- | | `COIN` | 所有用户展示 | | `DIAMOND` | 所有用户展示,但文案不能暗示可直接消费 | | `USD_BALANCE` | 有余额、冻结金额、host/agent/bd/coin_seller 身份时展示 | 不要返回 `GIFT_POINT`。积分是后台工资/活动计算输入,不是普通用户钱包可直接操作资产。 ## Entry Rules 入口显隐由后端返回,App 只负责渲染。 | Entry Key | Visible Rule | | --- | --- | | `wallet` | always true | | `host_center` | `roles.is_host == true` | | `apply_host` | `roles.is_host == false` | | `agency_center` | `roles.is_agent == true` | | `bd_center` | `roles.is_bd == true` | | `bd_leader_center` | `roles.is_bd_leader == true` | | `coin_seller_center` | `roles.is_coin_seller == true` | | `backpack` | always true | | `settings` | always true | | `support` | always true | 同一个用户可能同时是 BD 和 host。不要用互斥枚举表达角色,必须使用 bool flags。 ## Expensive Fields To Exclude 不要在 `overview` 中实时查询这些字段: | Field | Reason | Replacement | | --- | --- | --- | | 邀请用户列表 | 需要分页,可能扫 `user_invite_relations` | 二级页 `GET /api/v1/invites/users` | | 实时邀请人数 `COUNT(*)` | 高活跃邀请人会让首页变慢 | read model counter 或返回 `0` | | 邀请奖励流水 | 属于钱包/活动明细 | 二级页分页 | 这些字段不要放进首屏 overview: | Field | Reason | Replacement | | --- | --- | --- | | `period_mic_minutes` | 需要统计聚合,可能扫 stats | Host Center 二级页 | | `period_gift_points` | 需要统计聚合,可能跨日汇总 | Host Center 二级页 | | `estimated_earning_usd_minor` | 不能首屏现算工资 | 后台工资 read model 或收益页 | | `agency_member_count` | 容易 `COUNT(*)` members | Agency Center 二级页或 summary table | | `bd_host_count` | 团队规模统计昂贵 | BD Center 二级页或 summary table | | `pending_withdrawals` | 扫提现/流水表 | wallet badge counter | | `recharge_count` | 扫充值记录 | wallet 二级页 | | `resource_count` | 扫背包权益表 | backpack 二级页 | | `latest_transactions` | 扫交易流水 | wallet 二级页 | | `inbox_items` | 扫消息列表 | messages 二级页 | 首屏接口禁止为了红点实时扫大表。需要红点就维护 counter/read model;没有 counter 时先不展示该红点。 ## Performance Budget 目标: - P95 backend latency: `< 120ms` in normal region deployment. - Gateway fanout RPC count: max 4. - No list scan. - No transaction history query. - No member `COUNT(*)`. - No resource entitlement list. - No salary calculation. 推荐并行 fanout: ```mermaid sequenceDiagram participant C as Client participant G as gateway-service participant U as user-service participant W as wallet-service participant M as message inbox owner C->>G: GET /api/v1/users/me/overview par profile and roles G->>U: GetUser(user_id) G->>U: GetUserRoleSummary(user_id) and wallet G->>W: GetBalances(COIN, DIAMOND, USD_BALANCE) and badges G->>M: ListMessageTabs(user_id) end G->>G: compose entries and feature flags G-->>C: overview ``` MVP 可以复用已有 `GetHostProfile`、`GetBDProfile`、`GetCoinSellerProfile` 聚合角色;优化版应增加单个 `GetUserRoleSummary`,减少 not-found RPC 和错误映射成本。 ## Degraded Responses 首屏接口可以部分降级,但必须清楚标记。 | Upstream Failure | Response Rule | | --- | --- | | `user-service.GetUser` fail | 整体失败,资料是首屏主数据 | | `wallet-service.GetBalances` fail | 返回空 balances,并加 `wallet.unavailable=true` | | role summary fail | 返回 all false,并加 `roles.unavailable=true`,不要误展示特权入口 | | message tabs fail | unread 置 0,并加 `badges.unavailable=true` | 如果某个上游超时,gateway 不应无限等待。overview 是首屏接口,应该有比普通操作接口更短的聚合超时预算。 ## HTTP Handler Shape 新增路由: ```go mux.Handle(apiV1Prefix+"/users/me/overview", h.profileAPIHandler(jwtVerifier, h.getMyOverview)) ``` handler 只做协议聚合: 1. 从 JWT context 取 `user_id` 和 `app_code`。 2. 并行请求 profile、balances、roles、message tabs。 3. 把资产补齐为固定三类,缺失资产返回 0。 4. 根据 roles 和 feature flags 生成 entries。 5. 返回 envelope data。 不要在 gateway 写 SQL,也不要在 gateway 做工资、成员、资源统计。 ## Verification Matrix | Scenario | Expected | | --- | --- | | 普通用户打开我的页 | 返回 profile、COIN/DIAMOND/USD_BALANCE、普通入口 | | host 打开我的页 | `roles.is_host=true`,展示 `host_center` | | BD Leader 打开我的页 | `is_bd=true` 且 `is_bd_leader=true`,展示 leader 入口 | | 币商打开我的页 | `is_coin_seller=true`,展示 coin seller 入口 | | 钱包没有账户行 | 三类资产都返回 0 投影 | | 消息服务失败 | 我的页仍可展示,消息红点降级为 0 | | 用户资料服务失败 | overview 整体失败 | | 大量成员/流水存在 | overview 延迟不受影响,因为不查成员和流水 | ## Implementation Order 1. 新增本文档,确认字段边界和慢查询排除规则。 2. 在 `gateway-service` 增加 `GET /api/v1/users/me/overview` handler。 3. 复用现有 `GetUser`、`GetBalances`、`GetHostProfile/GetBDProfile/GetCoinSellerProfile`、`ListMessageTabs`。 4. 增加 handler 单元测试,覆盖普通用户、host、BD Leader、币商、钱包缺失、消息降级。 5. 如果角色 RPC 成本偏高,再在 `user-service` 增加 `GetUserRoleSummary`。 6. 如果红点需要更多类型,先补 counter/read model,再把 badge 加进 overview。 ## Critical Rules - `我的` 页 overview 是轻聚合接口,不是用户全量详情接口。 - 首屏不做 `COUNT(*)`、不扫流水、不扫成员、不查完整背包、不现算工资。 - 钱包余额以 `wallet-service` 为准。 - 用户资料以 `user-service` 为准。 - 角色入口以服务端返回的 `roles` 和 `entries` 为准。 - 消息红点以 message inbox counter/read model 为准,用户私聊未读仍以腾讯云 IM SDK 为准。