hyapp-server/docs/CP关系服务端开发文档.md
2026-06-08 14:12:23 +08:00

234 lines
9.9 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.

# 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`,失败可重试。