hyapp-server/docs/my-page-overview-api.md
2026-05-08 18:48:29 +08:00

317 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <access_token>
```
响应仍使用 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 为准。