12 KiB
CP关系服务端开发文档
本文定义当前 CP、兄弟、姐妹关系的服务端边界。关系事实归 user-service,送礼扣费归 wallet-service,房间送礼事实归 room-service,CP 相关 IM 由 notice-service 消费 user_outbox 后补偿投递。
1. 关系规则
| 类型 | 编码 | 说明 |
|---|---|---|
| CP | cp |
CP 关系 |
| 兄弟 | brother |
兄弟关系 |
| 姐妹 | sister |
姐妹关系 |
规则:
- CP 关系每个用户最多 1 个,不提供可调数量。
- 兄弟和姐妹的每人 active 数量由后台 CP 配置控制。
- 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 申请和亲密值变化只能消费
RoomGiftSentMQ 事实,不能扫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:
{
"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. 送礼触发申请
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. 同意和拒绝
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: A -> B 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 申请。
- 同意会发两条 C2C:
B -> A表示“我同意了你”,A -> B表示“对方已同意你的申请”。 - 同意和拒绝都会向 A 发送房间 IM,用
recipient_user_id=A限定展示对象。 - 拒绝仍只向 A 发送一条 C2C 和一条房间 IM。
- 同意成功后额外发送区域 IM,区域取申请来源房间的
visible_region_id。
7. 亲密值和升级
任意礼物发送成功后,如果发送方和收礼方已经有 active 关系,则按本次礼物的 heat_value 增加亲密值。
规则:
- 普通礼物按 100% 热度值增加亲密值。
- 幸运礼物、超级幸运礼物直接使用钱包回执里的最终
heat_value,不重复计算比例。 - 多目标送礼时按每个目标的账务回执分别增加对应关系亲密值。
- 等级范围 1 到 5;等级亲密值阈值和奖励资源组由后台 CP 配置维护。
- 每个等级可绑定奖励资源组,升级时调用
wallet-service给双方发放资源。 - 奖励发放幂等键为
cp_level_reward:{relationship_id}:{level}:{user_id}。 - App 关系查询返回当前等级阈值、下一等级阈值、距离下一级和进度百分比。
- 资料卡弹窗只能使用
/api/v1/cp/relationships返回的阈值字段展示进度;客户端不能读取后台配置或硬编码等级表。
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 关系 |
/api/v1/cp/relationships 的 relationship item 包含:
| 字段 | 说明 |
|---|---|
relationship_id |
关系 ID |
relation_type |
cp、brother、sister |
status |
当前为 active |
me |
当前用户快照 |
partner |
对方用户快照 |
intimacy_value |
当前亲密值 |
level |
当前等级 |
current_level_threshold |
当前等级起始亲密值 |
next_level_threshold |
下一等级所需亲密值;满级时为 0 |
needed_for_next_level |
距离下一级还需要多少亲密值;满级时为 0 |
level_progress_percent |
当前等级内进度百分比,0 到 100 |
max_level |
是否已经没有下一等级 |
formed_at_ms |
关系形成时间 |
updated_at_ms |
最近更新时间 |
这些字段由 user-service 从 user_cp_level_rules 计算,gateway 只做透传。客户端资料卡不能根据本地表推算这些值。
申请列表参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
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 |
CP/兄弟/姐妹数量与申请过期配置;CP 数量服务端固定为 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')。 - CP active 关系按单用户唯一:任一用户已有 CP 关系就不能再接受新的 CP 关系。
- 兄弟/姐妹 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,payload 带 c2c_notice_role=accepted_to_requester、message_text=我同意了你 |
cp_application_accepted |
C2C | B 同意后,A 发给 B,payload 带 c2c_notice_role=accepted_to_acceptor、message_text=对方已同意你的申请 |
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;同意会发 B -> A 和 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,失败可重试。