234 lines
9.9 KiB
Markdown
234 lines
9.9 KiB
Markdown
# CP关系服务端开发文档
|
||
|
||
本文定义当前 CP、兄弟、姐妹关系的服务端边界。关系事实归 `user-service`,送礼扣费归 `wallet-service`,房间送礼事实归 `room-service`,CP 相关 IM 由 `notice-service` 消费 `user_outbox` 后补偿投递。
|
||
|
||
## 1. 关系规则
|
||
|
||
| 类型 | 编码 | 说明 |
|
||
| --- | --- | --- |
|
||
| CP | `cp` | CP 关系 |
|
||
| 兄弟 | `brother` | 兄弟关系 |
|
||
| 姐妹 | `sister` | 姐妹关系 |
|
||
|
||
规则:
|
||
|
||
- 每个用户同时只能有一个 active 关系,不按类型拆数量,也不需要后台配置数量。
|
||
- A 和 B 同时只能存在一种 active 关系。
|
||
- A 可以给 B 发送 `cp`、`brother`、`sister` 多种 CP 礼物,形成多条 pending 申请。
|
||
- B 接受其中一个申请后,同一对用户其他 pending 申请会被置为 `blocked`,不能再接受。
|
||
- 申请默认 24 小时过期,当前不提供后台配置入口。
|
||
- 不提供佩戴 CP 功能;资料卡如果要展示关系,直接展示用户当前 active 关系。
|
||
|
||
## 2. 服务归属
|
||
|
||
| 服务 | 负责内容 |
|
||
| --- | --- |
|
||
| `wallet-service` | 礼物配置、扣费、热度值、礼物 icon 和动画快照 |
|
||
| `room-service` | 送礼命令、房间热度、房间礼物 IM、`RoomGiftSent` outbox |
|
||
| `user-service` | CP 申请、关系、亲密值、等级、等级奖励幂等、`user_outbox` |
|
||
| `gateway-service` | App HTTP 接口、鉴权、request_id 透传 |
|
||
| `notice-service` | 申请、同意、拒绝、关系成立的 C2C/房间/区域 IM |
|
||
| `cron-service` | 定时触发 user-service 过期申请处理 |
|
||
|
||
边界:
|
||
|
||
- `room-service` 不保存 CP 关系状态。
|
||
- `user-service` 不直连 `wallet-service`、`room-service` 或 `notice-service` 数据库。
|
||
- CP 申请和亲密值变化只能消费 `RoomGiftSent` MQ 事实,不能扫 `room_outbox`。
|
||
- 下游服务只能消费 owner service 发布到 RocketMQ 的 outbox。
|
||
|
||
## 3. CP礼物配置
|
||
|
||
礼物大类使用 `gift_type_code=cp`。CP 子类型使用发送时快照字段 `cp_relation_type`,值为 `cp`、`brother`、`sister`。
|
||
|
||
| 配置 | 说明 |
|
||
| --- | --- |
|
||
| 礼物类型 | `gift_type_code=cp`,礼物面板展示为 CP 礼物 Tab |
|
||
| CP 子类型 | `cp_relation_type=cp/brother/sister`,用于 CP Tab 二级分类 |
|
||
| 礼物 icon | 来自绑定资源的 `asset_url`,发送事件保存快照 |
|
||
| 礼物价格和热度 | 继续由 `wallet-service` 结算,客户端不能传价格或热度 |
|
||
|
||
`cp_relation_type` 写入礼物 `presentation_json`:
|
||
|
||
```json
|
||
{
|
||
"cp_relation_type": "cp",
|
||
"cp_relation_label": "CP礼物"
|
||
}
|
||
```
|
||
|
||
## 4. RoomGiftSent字段
|
||
|
||
`api/proto/events/room/v1/events.proto` 的 `RoomGiftSent` 必须带以下送礼快照:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `gift_type_code` | string | CP 礼物为 `cp` |
|
||
| `cp_relation_type` | string | `cp`、`brother`、`sister`;非 CP 礼物为空 |
|
||
| `gift_name` | string | 礼物名称 |
|
||
| `gift_icon_url` | string | 礼物 icon |
|
||
| `gift_animation_url` | string | 礼物动画,可为空 |
|
||
|
||
## 5. 送礼触发申请
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant F as Flutter
|
||
participant G as gateway-service
|
||
participant R as room-service
|
||
participant W as wallet-service
|
||
participant U as user-service
|
||
participant N as notice-service
|
||
participant IM as Tencent IM
|
||
|
||
F->>G: POST /api/v1/rooms/gift/send
|
||
G->>R: SendGift
|
||
R->>W: DebitGift
|
||
W-->>R: heat_value + gift_type_code + cp_relation_type + gift snapshot
|
||
R->>R: update heat/rank/command log
|
||
R-->>F: SendGiftResponse
|
||
R-->>U: RoomGiftSent MQ
|
||
U->>U: create or refresh CP application
|
||
U-->>N: UserCPApplicationCreated MQ
|
||
N->>IM: A -> B C2C cp_application_created
|
||
N->>IM: room cp_application_created, recipient_user_id=B
|
||
```
|
||
|
||
处理规则:
|
||
|
||
- 只有 `gift_type_code=cp` 且 `cp_relation_type` 有效时创建申请。
|
||
- 同一个 `RoomGiftSent.event_id` 只能消费一次。
|
||
- A 给 B 发送同一类型 CP 礼物时,如果已有 pending 申请,刷新礼物快照和 `expires_at_ms`。
|
||
- A 给 B 发送不同类型 CP 礼物时,可以各有一条 pending 申请。
|
||
- 如果任一方已经有 active 关系,申请可以存在,但接受时会被拒绝;已有关系双方互送礼物会增加亲密值。
|
||
|
||
## 6. 同意和拒绝
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant F as Flutter B
|
||
participant G as gateway-service
|
||
participant U as user-service
|
||
participant N as notice-service
|
||
participant IM as Tencent IM
|
||
|
||
F->>G: POST /api/v1/cp/applications/{id}/accept
|
||
G->>U: AcceptCPApplication
|
||
U->>U: lock pair + lock both users + create relationship
|
||
U-->>N: UserCPApplicationAccepted MQ
|
||
U-->>N: UserCPRelationshipCreated MQ
|
||
N->>IM: B -> A C2C cp_application_accepted
|
||
N->>IM: room cp_application_accepted, recipient_user_id=A
|
||
N->>IM: region cp_relationship_created
|
||
```
|
||
|
||
处理规则:
|
||
|
||
- 接受和拒绝都只能由申请目标用户 B 操作。
|
||
- pending 申请过期后不能接受,gateway envelope 返回 `CONFLICT`;内部 gRPC 语义是 FailedPrecondition。
|
||
- 接受时同时校验 A 和 B 是否已经有任意 active 关系;任一方已有关系就返回 `CONFLICT`。
|
||
- 同意成功后写 active 关系,关闭同一对用户其他 pending 申请。
|
||
- 同意或拒绝都向 A 发送 C2C IM 和房间 IM。
|
||
- 同意成功后额外发送区域 IM,区域取申请来源房间的 `visible_region_id`。
|
||
|
||
## 7. 亲密值和升级
|
||
|
||
任意礼物发送成功后,如果发送方和收礼方已经有 active 关系,则按本次礼物的 `heat_value` 增加亲密值。
|
||
|
||
规则:
|
||
|
||
- 普通礼物按 100% 热度值增加亲密值。
|
||
- 幸运礼物、超级幸运礼物直接使用钱包回执里的最终 `heat_value`,不重复计算比例。
|
||
- 多目标送礼时按每个目标的账务回执分别增加对应关系亲密值。
|
||
- 等级范围 1 到 5;当前使用 user-service 默认等级规则,不提供后台配置入口。
|
||
- 每个等级可绑定奖励资源组,升级时调用 `wallet-service` 给双方发放资源。
|
||
- 奖励发放幂等键为 `cp_level_reward:{relationship_id}:{level}:{user_id}`。
|
||
|
||
## 8. App HTTP接口
|
||
|
||
所有接口走 `gateway-service`,响应使用 `{code,message,request_id,data}`。
|
||
|
||
| 方法 | 地址 | 说明 |
|
||
| --- | --- | --- |
|
||
| `GET` | `/api/v1/cp/applications` | 查询我的申请 |
|
||
| `POST` | `/api/v1/cp/applications/{application_id}/accept` | 同意申请 |
|
||
| `POST` | `/api/v1/cp/applications/{application_id}/reject` | 拒绝申请 |
|
||
| `GET` | `/api/v1/cp/relationships` | 查询我的 active 关系 |
|
||
|
||
申请列表参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `direction` | string | 否 | `incoming` 或 `outgoing`,默认 `incoming` |
|
||
| `status` | string | 否 | `pending`、`accepted`、`rejected`、`expired`、`blocked` |
|
||
| `page` | int32 | 否 | 默认 1 |
|
||
| `page_size` | int32 | 否 | 默认 20 |
|
||
|
||
关系列表参数:
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `relation_type` | string | 否 | `cp`、`brother`、`sister`;不传返回全部 |
|
||
| `page` | int32 | 否 | 默认 1 |
|
||
| `page_size` | int32 | 否 | 默认 20 |
|
||
|
||
拒绝请求体:
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `reason` | string | 否 | 拒绝原因,只进入 IM/outbox payload |
|
||
|
||
## 9. 数据表
|
||
|
||
表放在 `user-service` 库,所有表带 `app_code`,时间使用 UTC epoch milliseconds。
|
||
|
||
| 表 | 说明 |
|
||
| --- | --- |
|
||
| `user_cp_relation_configs` | 历史兼容表;当前固定 `max_count_per_user=1`,不提供后台配置 |
|
||
| `user_cp_level_rules` | 默认等级阈值和奖励资源组 |
|
||
| `user_cp_gift_event_consumption` | `RoomGiftSent` 消费幂等 |
|
||
| `user_cp_applications` | CP 关系申请 |
|
||
| `user_cp_relationships` | 双人关系事实 |
|
||
| `user_cp_level_reward_grants` | 等级奖励发放幂等 |
|
||
| `user_outbox` | CP 事件事实 |
|
||
|
||
关键唯一约束:
|
||
|
||
- active 关系按用户对唯一:`(app_code, user_a_id, user_b_id, status='active')`。
|
||
- active 关系按单用户唯一:任一用户已有 active 关系就不能再接受新关系。
|
||
- pending 申请按用户对和类型唯一:`(app_code, requester_user_id, target_user_id, relation_type, status='pending')`。
|
||
|
||
## 10. 相关IM
|
||
|
||
| IM | 通道 | 发送时机 |
|
||
| --- | --- | --- |
|
||
| `cp_application_created` | C2C | A 送 CP 礼物触发申请后,A 发给 B |
|
||
| `cp_application_created` | 房间群 | A 送 CP 礼物触发申请后,发到来源房间,payload 带 `recipient_user_id=B` |
|
||
| `cp_application_accepted` | C2C | B 同意后,B 发给 A |
|
||
| `cp_application_accepted` | 房间群 | B 同意后,发到来源房间,payload 带 `recipient_user_id=A` |
|
||
| `cp_application_rejected` | C2C | B 拒绝后,B 发给 A |
|
||
| `cp_application_rejected` | 房间群 | B 拒绝后,发到来源房间,payload 带 `recipient_user_id=A` |
|
||
| `cp_relationship_created` | 区域群 | 关系建立后发送区域播报 |
|
||
|
||
C2C 使用腾讯云 IM `From_Account` 表达真实操作人:申请为 A,处理结果为 B。区域 IM 群 ID 使用 `hy_<app_code>_bc_r_<region_id>`,本地测试可通过 `group_id_prefix` 加前缀。
|
||
|
||
## 11. 错误码
|
||
|
||
| 场景 | code | 说明 |
|
||
| --- | --- | --- |
|
||
| 申请不存在 | `NOT_FOUND` | application_id 不存在或不属于当前用户 |
|
||
| 申请已过期 | `CONFLICT` | 不能再同意;内部 gRPC 语义是 FailedPrecondition |
|
||
| 已有关系 | `CONFLICT` | A/B 任一方已经有 active 关系 |
|
||
| 无权限处理 | `PERMISSION_DENIED` | 不是申请目标用户 |
|
||
| 幂等冲突 | `CONFLICT` | command_id 或事件消费 payload 不一致 |
|
||
|
||
## 12. 验证项
|
||
|
||
- CP 礼物发送成功后,`RoomGiftSent` 带 `gift_type_code=cp`、`cp_relation_type`、礼物 icon 快照。
|
||
- A 给 B 连发 `cp`、`brother`、`sister` 三种礼物,会生成三条 pending 申请。
|
||
- B 接受其中一条后,其他两条不能再接受。
|
||
- A/B 建立关系后,A 或 B 都不能再和第三人建立 active 关系。
|
||
- pending 申请超过默认 24 小时后不能接受。
|
||
- active 关系双方互送任意礼物,亲密值按 `heat_value` 增加。
|
||
- 同意、拒绝、关系成立的 IM 都写入 `notice_delivery_events`,失败可重试。
|