hyapp-server/docs/经理中心资源赠送架构.md
2026-05-11 19:26:01 +08:00

315 lines
14 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.

# Manager Center Resource Grant Architecture
本文定义后台资源列表的“赠送权限”开关、H5 经理中心资源赠送、以及可复用用户查询接口的服务边界。资源 catalog、用户资源权益和赠送事实仍按 [Resource And Resource Group Architecture](./资源组与礼物架构.md) 归属 `wallet-service`经理、Agency、BD 等业务身份仍按 [Host, Agency And BD App Architecture](./主播公会BD架构.md) 归属 `user-service`
## Goals
- 后台资源列表增加“赠送权限” switch新建资源默认打开关闭后资源仍可存在、可用于其他业务但不能出现在经理中心赠送列表也不能被经理中心赠送。
- `server/admin` 和后台前端支持读取、创建、编辑和切换该字段,但不成为资源配置事实 owner。
- H5 经理中心提供资源赠送列表、赠送给用户接口和通用用户查询入口。
- 经理身份必须由服务端校验,客户端不能自报 `is_manager`
- 用户查询接口按业务场景复用首个场景是经理中心赠送后续代理中心、申请成为主播、BD 中心可以复用同一个 user-service 查询能力,但必须传入明确 scene 并执行各自权限和字段裁剪。
## Non-Goals
- 不新增独立 `manager-service``resource-service`
- 不把用户资源余额、背包、赠送记录写入 `server/admin` 数据库。
- 不允许 H5 客户端提交资源价格、金币数量、权益有效期或赠送策略。
- 不支持经理中心赠送资源组;资源组仍只允许后台或明确的系统命令赠送。
- 不在 room-service 中校验经理身份或资源赠送权限。
- 不把通用用户查询做成无权限全局搜索;所有查询必须带 scene且返回字段按 scene 收敛。
## Service Ownership
| Service | Owns | Does Not Own |
| --- | --- | --- |
| `wallet-service` | `resources.manager_grant_enabled`、资源赠送白名单判断、资源赠送事务、钱包入账、用户资源权益、resource outbox | 用户是否经理、用户资料搜索、后台账号权限 |
| `user-service` | 用户主数据、短号解析、通用用户查询、经理/Agency/BD/主播/币商等业务身份能力判定 | 资源 catalog、钱包余额、用户资源权益 |
| `gateway-service` | H5 HTTP `/api/v1` envelope、App 鉴权、scene 参数、调用 user-service 校验能力和目标用户、调用 wallet-service 赠送命令 | 资源事实、用户身份事实、钱包/背包落账 |
| `server/admin` | 后台鉴权、资源列表/编辑表单、操作审计、调用 wallet-service 管理 RPC | 资源事实、H5 经理身份、用户资源背包 |
| H5 经理中心 | 展示资源列表、选择目标用户、提交赠送命令 | 权限判断、目标用户真实性、赠送数量和有效期计算 |
## Core Concepts
| Concept | Meaning |
| --- | --- |
| `manager_grant_enabled` | 资源是否允许由经理中心赠送。它不是后台能否赠送的开关;后台仍使用现有 `grantable` 和后台权限控制。 |
| `manager_center` | App H5 业务入口,由 gateway 鉴权后进入。首版经理身份映射为 active Agency owner后续如果引入 Agency manager 表,只扩展 user-service 能力判定。 |
| `business user lookup` | 通用用户查询能力。调用方必须传 `scene`user-service 按 scene 校验 actor 能力并裁剪返回字段。 |
| `manager resource grant` | 经理中心发起的单资源赠送命令。落库仍是 `resource_grants``grant_source=manager_center``operator_user_id=actor_user_id`。 |
## Admin Resource Switch
`resources` 增加字段:
```sql
ALTER TABLE resources
ADD COLUMN manager_grant_enabled BOOLEAN NOT NULL DEFAULT TRUE,
ADD KEY idx_resources_manager_grant (app_code, manager_grant_enabled, status, sort_order);
```
字段语义:
- 默认值为 `TRUE`,满足“后台资源列表赠送权限 switch 默认打开”。
- 关闭 switch 只影响经理中心赠送列表和经理中心赠送命令。
- `status=disabled` 的资源即使 `manager_grant_enabled=TRUE`,也不能出现在 H5 赠送列表。
- `grantable=FALSE` 的资源即使 `manager_grant_enabled=TRUE`,也不能被任何赠送命令发放。
- 历史已经赠送出去的权益不受该字段变更影响。
后台接口保持当前 `server/admin -> wallet-service` 边界:
```http
GET /api/v1/admin/resources
POST /api/v1/admin/resources
PUT /api/v1/admin/resources/{resource_id}
POST /api/v1/admin/resources/{resource_id}/enable
POST /api/v1/admin/resources/{resource_id}/disable
```
DTO/RPC 需要补充:
- `wallet.v1.Resource.manager_grant_enabled`
- `CreateResourceRequest.manager_grant_enabled`
- `UpdateResourceRequest.manager_grant_enabled`
- `ListResourcesRequest.manager_grant_only`
`server/admin` 只做字段透传和操作审计。新增或编辑资源时,如果请求体不传 `managerGrantEnabled`admin-server 必须向 wallet-service 传 `true`,避免前端漏字段导致默认关闭。
## Manager Capability Boundary
首版经理身份定义为:
- 当前用户拥有 active `Agency`,且 `agencies.owner_user_id = actor_user_id`
- `Agency.status=active`
- 该能力由 `user-service` host domain 判断gateway 不能只读 access token 或客户端传参。
建议在 user-service 增加通用能力接口:
```proto
message CheckBusinessCapabilityRequest {
RequestMeta meta = 1;
int64 actor_user_id = 2;
string capability = 3;
}
message CheckBusinessCapabilityResponse {
bool allowed = 1;
string reason = 2;
}
```
首版 capability
| Capability | Allowed When |
| --- | --- |
| `manager_center` | active Agency owner |
| `manager_resource_grant` | active Agency owner |
后续如果出现 Agency 授权管理员,不改 gateway 和 wallet-service只在 user-service host domain 增加 `agency_managers` 或对应 read model并扩展 capability 判定。
## H5 App APIs
所有接口走 gateway `/api/v1` envelope。access token 解析、`RequestMeta.request_id``app_code``client_ip``user_agent` 由 gateway 透传到内部 gRPC。
### 赠送列表
```http
GET /api/v1/manager-center/resource-grants/resources?resource_type=badge&page=1&page_size=20
```
处理规则:
1. gateway 从 token 取 `actor_user_id`
2. gateway 调 user-service `CheckBusinessCapability(capability=manager_resource_grant)`
3. gateway 调 wallet-service `ListResources(active_only=true, manager_grant_only=true, resource_type, page, page_size)`
4. wallet-service 只返回 `status=active AND grantable=TRUE AND manager_grant_enabled=TRUE` 的资源。
响应字段只返回 H5 展示和提交所需字段:
```json
{
"items": [
{
"resource_id": "101",
"resource_type": "badge",
"name": "VIP Badge",
"asset_url": "https://...",
"preview_url": "https://...",
"wallet_asset_type": "",
"wallet_asset_amount": 0,
"sort_order": 10
}
],
"total": 1,
"page": 1,
"page_size": 20
}
```
H5 不能从该接口获得后台审计字段、`created_by_user_id``updated_by_user_id` 或完整 `metadata_json`。如果某类资源需要额外展示配置,必须在 wallet-service 先定义稳定的展示 DTO不能把原始后台 metadata 透出。
### 赠送给用户
```http
POST /api/v1/manager-center/resource-grants
```
请求:
```json
{
"command_id": "mgr-grant-20260511-0001",
"target_user_id": "900001",
"resource_id": "101",
"reason": "manager_center"
}
```
处理规则:
1. gateway 校验当前用户拥有 `manager_resource_grant` capability。
2. gateway 调 user-service `GetUser(target_user_id)`,确认目标用户属于同一 `app_code`、存在、未禁用、未封禁。
3. gateway 不接受客户端提交 `quantity``duration_ms``wallet_asset_amount`
4. gateway 调 wallet-service `GrantResource`
- `grant_source=manager_center`
- `operator_user_id=actor_user_id`
- `target_user_id=resolved target`
- `quantity=1`
- `duration_ms=0`
- `reason=manager_center`
5. wallet-service 在同一事务中校验资源 `status=active``grantable=TRUE``manager_grant_enabled=TRUE`,再按现有资源策略落 `resource_grants``resource_grant_items`、钱包账本或用户资源权益。
wallet-service 不能只依赖赠送列表过滤。`GrantResource` 必须在写事务内根据 `grant_source=manager_center` 重新锁定资源并校验 `manager_grant_enabled`,防止客户端绕过列表直接提交资源 ID。
幂等规则沿用现有 `resource_grants.command_id`
- 同一 `app_code + command_id`、同一 payload 重试返回原 grant。
- 同一 `command_id` 换目标用户或资源返回冲突。
- `request_id` 只做链路追踪,不能当幂等键。
## Common User Lookup
通用用户查询不要挂在经理中心专属模块里。建议 gateway 暴露统一入口:
```http
GET /api/v1/business/users/lookup?scene=manager_resource_grant&keyword=123456&page_size=20
```
user-service 新增或扩展查询 RPC
```proto
message BusinessUserLookupRequest {
RequestMeta meta = 1;
int64 actor_user_id = 2;
string scene = 3;
string keyword = 4;
int32 page_size = 5;
}
message BusinessUserLookupItem {
int64 user_id = 1;
string display_user_id = 2;
string username = 3;
string avatar = 4;
UserStatus status = 5;
int64 region_id = 6;
string region_code = 7;
string region_name = 8;
}
message BusinessUserLookupResponse {
repeated BusinessUserLookupItem users = 1;
}
```
scene 权限矩阵:
| Scene | Actor Requirement | Query Scope |
| --- | --- | --- |
| `manager_resource_grant` | `manager_resource_grant` capability | active users in same `app_code` |
| `agency_application` | logged-in active user | active agencies or agency owner candidates,按后续申请流程定义 |
| `bd_invite_agency` | active BD or BD Leader | same-region active users,不能已有 active Agency owner |
| `bd_invite_bd` | active BD Leader | same-region active users,不能已有 active BD |
首版只实现 `manager_resource_grant`。未实现 scene 必须返回 `INVALID_ARGUMENT`,不能默认降级成全局用户搜索。
查询安全规则:
- `keyword` 必须是短号、用户 ID 或至少 2 个字符的昵称片段。
- 数字 keyword 优先按 current display_user_id 精确匹配,再按 user_id 精确匹配。
- `page_size` 最大 20。
- 只返回必要展示字段,不返回手机号、登录方式、设备、邀请码绑定、钱包余额或三方身份。
- 被禁用、封禁、跨 `app_code` 用户不返回。
- gateway 对 lookup 和 grant 都要做限流lookup 不能成为撞库枚举入口。
## Sequence
```mermaid
sequenceDiagram
participant H5 as H5 Manager Center
participant G as gateway-service
participant U as user-service
participant W as wallet-service
H5->>G: GET /manager-center/resource-grants/resources
G->>U: CheckBusinessCapability(actor, manager_resource_grant)
U-->>G: allowed
G->>W: ListResources(active_only, manager_grant_only)
W-->>G: grantable resources
G-->>H5: resources
H5->>G: GET /business/users/lookup?scene=manager_resource_grant
G->>U: BusinessUserLookup(actor, scene, keyword)
U-->>G: minimal users
G-->>H5: users
H5->>G: POST /manager-center/resource-grants
G->>U: CheckBusinessCapability(actor, manager_resource_grant)
U-->>G: allowed
G->>U: GetUser(target_user_id)
U-->>G: active target
G->>W: GrantResource(source=manager_center)
W-->>G: resource grant
G-->>H5: grant result
```
## Validation Rules
| Rule | Owner | Error |
| --- | --- | --- |
| actor is not active manager | user-service | `PERMISSION_DENIED` |
| lookup scene unknown | user-service | `INVALID_ARGUMENT` |
| target user not found, disabled, banned, or different app | user-service/gateway | `NOT_FOUND` or `PERMISSION_DENIED` |
| resource disabled | wallet-service | `CONFLICT` |
| resource `grantable=FALSE` | wallet-service | `CONFLICT` |
| resource `manager_grant_enabled=FALSE` and source is `manager_center` | wallet-service | `PERMISSION_DENIED` |
| H5 submits quantity or duration | gateway | ignore or reject;首版建议 reject `INVALID_ARGUMENT` |
| duplicate command_id with changed payload | wallet-service | `LEDGER_CONFLICT` |
## Event And Audit
- 后台切换 `manager_grant_enabled` 产出 `ResourceChanged` outbox便于 H5 资源列表缓存刷新。
- 经理中心赠送成功产出 `ResourceGranted` 和必要的 `UserResourceChanged`/`WalletBalanceChanged` 事件。
- `resource_grants.grant_source='manager_center'` 是 App 赠送审计来源。
- `operator_user_id` 记录发起赠送的经理用户 ID不记录后台 admin 用户。
- gateway 应在结构化日志记录 `request_id``actor_user_id``target_user_id``resource_id``grant_id` 和错误 reason。
## Implementation Order
1. `api/proto/wallet/v1/wallet.proto` 增加 `manager_grant_enabled``manager_grant_only`,运行 `make proto`
2. `wallet-service` MySQL 初始化脚本增加 `resources.manager_grant_enabled`repository list/create/update/status/GrantResource 路径全部带字段和校验。
3. `server/admin` resource DTO/request 增加 `managerGrantEnabled`;后台资源列表 switch 默认打开,编辑时透传。
4. `user-service` 增加 `CheckBusinessCapability``BusinessUserLookup`,首版只开放 `manager_resource_grant` scene。
5. `gateway-service` 增加 H5 经理中心三个入口:赠送资源列表、通用用户查询、赠送资源。
6. 补 OpenAPI、前端生成代码和 H5 页面联调。
7. 补测试wallet manager switch、admin 透传、user lookup 权限、gateway H5 流程和幂等冲突。
## Acceptance Criteria
- 新建资源不传 `managerGrantEnabled` 时,后台列表 switch 显示打开。
- 后台关闭某资源赠送权限后H5 经理中心赠送列表不返回该资源。
- H5 绕过列表直接提交关闭权限的 `resource_id`wallet-service 拒绝赠送。
- 非经理用户调用赠送列表、用户查询或赠送接口均返回权限错误。
- 经理用户可以查询目标用户并完成单资源赠送,`resource_grants.grant_source=manager_center``operator_user_id` 为经理用户 ID。
- 同一 `command_id` 重试不重复发放;换目标或资源返回冲突。