14 KiB
14 KiB
CP关系Flutter对接
本文只写 App 需要对接的地址、参数、返回值和相关 IM。CP 关系包含 cp、brother、sister 三种类型;CP 关系每个用户最多 1 个,兄弟和姐妹数量由后台 CP 配置控制;不需要佩戴接口。
1. 礼物面板CP Tab
地址:
GET /api/v1/rooms/{room_id}/gift-panel
Authorization: Bearer <access_token>
X-App-Code: lalu
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
room_id |
string | 是 | 当前房间 ID |
返回值:
tabs 里会有 gift_type_code=cp 的 Tab。CP 礼物会带:
| 字段 | 类型 | 说明 |
|---|---|---|
gift_id |
string | 送礼时使用 |
gift_type_code |
string | CP 礼物为 cp |
cp_relation_type |
string | cp、brother、sister |
name |
string | 礼物名 |
coin_price |
int64 | 展示价格,扣费以后端为准 |
resource.asset_url |
string | 礼物 icon |
resource.animation_url |
string | 礼物动效,可为空 |
Flutter 在 CP 礼物 Tab 下按 cp_relation_type 分成 CP、兄弟、姐妹三个二级 Tab。
2. 发送CP礼物
地址:
POST /api/v1/rooms/gift/send
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json
参数:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
room_id |
string | 是 | 当前房间 ID |
command_id |
string | 是 | 本次送礼幂等键,重试必须复用 |
target_type |
string | 否 | 当前传 user |
target_user_ids |
int64[] | 是 | CP 礼物只传 1 个收礼人 |
gift_id |
string | 是 | CP 礼物 ID |
gift_count |
int32 | 是 | 必须大于 0 |
示例:
{
"room_id": "room_1001",
"command_id": "cp_gift_1779259000000_10001_10002",
"target_type": "user",
"target_user_ids": [10002],
"gift_id": "cp_ring",
"gift_count": 1
}
返回值:
沿用普通送礼响应。关系申请通过 IM 到达,送礼响应不保证返回申请 ID。
| 字段 | 类型 | 说明 |
|---|---|---|
result.applied |
bool | 本次命令是否落地 |
billing_receipt_id |
string | 扣费回执 |
room_heat |
int64 | 最新房间热度 |
gift_rank |
array | 房间礼物榜 |
room |
object | 房间快照 |
lucky_gift |
object | 幸运礼物结果,普通 CP 礼物为空 |
3. 查询我的申请
地址:
GET /api/v1/cp/applications?direction=incoming&status=pending&page=1&page_size=20
Authorization: Bearer <access_token>
X-App-Code: lalu
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
direction |
string | 否 | incoming 我收到的;outgoing 我发出的 |
status |
string | 否 | pending、accepted、rejected、expired、blocked |
page |
int32 | 否 | 默认 1 |
page_size |
int32 | 否 | 默认 20 |
返回值:
{
"applications": [
{
"application_id": "cp_app_10001_10002_cp",
"relation_type": "cp",
"status": "pending",
"requester": {
"user_id": "10001",
"display_user_id": "123456",
"username": "Alice",
"avatar": "https://cdn.example/a.png"
},
"target": {
"user_id": "10002",
"display_user_id": "654321",
"username": "Bob",
"avatar": "https://cdn.example/b.png"
},
"room_id": "room_1001",
"room_region_id": 86,
"gift": {
"gift_id": "cp_ring",
"gift_name": "CP Ring",
"gift_icon_url": "https://cdn.example/cp-ring.png",
"gift_animation_url": "",
"gift_count": 1,
"gift_value": 520,
"billing_receipt_id": "gift_tx_1"
},
"created_at_ms": 1779259000000,
"updated_at_ms": 1779259000000,
"expires_at_ms": 1779345400000,
"decided_at_ms": 0
}
],
"total": 1,
"server_time_ms": 1779259001000
}
4. 同意申请
地址:
POST /api/v1/cp/applications/{application_id}/accept
Authorization: Bearer <access_token>
X-App-Code: lalu
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
application_id |
string | 是 | 申请 ID |
返回值:
{
"application": {
"application_id": "cp_app_10001_10002_cp",
"relation_type": "cp",
"status": "accepted",
"decided_at_ms": 1779259100000
},
"relationship": {
"relationship_id": "cp_rel_10001_10002",
"relation_type": "cp",
"status": "active",
"me": {
"user_id": "10002",
"display_user_id": "654321",
"username": "Bob",
"avatar": "https://cdn.example/b.png"
},
"partner": {
"user_id": "10001",
"display_user_id": "123456",
"username": "Alice",
"avatar": "https://cdn.example/a.png"
},
"intimacy_value": 0,
"level": 1,
"formed_at_ms": 1779259100000,
"updated_at_ms": 1779259100000
}
}
错误处理:
| code | 处理 |
|---|---|
CONFLICT |
A/B 任一方已经有 active 关系,或申请已过期;刷新申请和关系列表 |
NOT_FOUND |
申请不存在,刷新申请列表 |
PERMISSION_DENIED |
当前用户不是申请目标用户 |
5. 拒绝申请
地址:
POST /api/v1/cp/applications/{application_id}/reject
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
application_id |
string | 是 | 申请 ID |
reason |
string | 否 | 拒绝原因 |
返回值:
{
"application": {
"application_id": "cp_app_10001_10002_cp",
"relation_type": "cp",
"status": "rejected",
"decided_at_ms": 1779259100000
}
}
6. 查询我的关系
地址:
GET /api/v1/cp/relationships?relation_type=cp&page=1&page_size=20
Authorization: Bearer <access_token>
X-App-Code: lalu
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
relation_type |
string | 否 | cp、brother、sister;不传返回全部 |
page |
int32 | 否 | 默认 1 |
page_size |
int32 | 否 | 默认 20 |
返回值:
{
"relationships": [
{
"relationship_id": "cp_rel_10001_10002",
"relation_type": "cp",
"status": "active",
"me": {
"user_id": "10001",
"display_user_id": "123456",
"username": "Alice",
"avatar": "https://cdn.example/a.png"
},
"partner": {
"user_id": "10002",
"display_user_id": "654321",
"username": "Bob",
"avatar": "https://cdn.example/b.png"
},
"intimacy_value": 1888,
"level": 2,
"current_level_threshold": 1000,
"next_level_threshold": 3000,
"needed_for_next_level": 1112,
"level_progress_percent": 44,
"max_level": false,
"breakup_cost_coins": 88,
"formed_at_ms": 1779259100000,
"ended_at_ms": 0,
"updated_at_ms": 1779259200000
}
],
"total": 1
}
说明:
- CP 关系每个用户最多 1 个;兄弟和姐妹可拥有数量以后台配置为准。
- A/B 两个用户之间同一时间只能存在一种 active 关系,不能同时是 CP、兄弟或姐妹。
- 不需要佩戴接口,资料卡直接展示当前 active 关系。
- 资料卡弹窗只使用后端返回的
intimacy_value、level、current_level_threshold、next_level_threshold、needed_for_next_level、level_progress_percent和max_level。 max_level=true或next_level_threshold=0时表示没有下一等级,隐藏“距离下一级”和进度条。- Flutter 不要用本地常量、礼物价格或后台配置接口自行推算进度条和“距离下一级”。
breakup_cost_coins是解除当前关系需要支付的金币数,弹窗和二次确认直接使用该值。
7. 解除关系
地址:
POST /api/v1/cp/relationships/{relationship_id}/break
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json
{
"command_id": "break_cp_rel_10001_10002_1779259300000"
}
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
relationship_id |
string | 是 | URL path,关系 ID |
command_id |
string | 是 | 解除关系幂等键,同一次重试必须复用 |
返回值:
{
"relationship": {
"relationship_id": "cp_rel_10001_10002",
"relation_type": "cp",
"status": "ended",
"breakup_cost_coins": 88,
"formed_at_ms": 1779259100000,
"ended_at_ms": 1779259300000,
"updated_at_ms": 1779259300000
},
"breakup_cost_coins": 88,
"wallet_transaction_id": "wallet_tx_break_1",
"coin_balance_after": 912
}
说明:
- 解除前先展示
/api/v1/cp/relationships返回的breakup_cost_coins。 - 金币不足时接口返回
INSUFFICIENT_BALANCE,关系仍保持 active。 - 解除成功不发 C2C、房间 IM 或区域 IM;Flutter 使用接口返回刷新资料卡和关系列表。
8. 相关IM
所有 IM 都是 Tencent TIMCustomElem,MsgContent.Desc 等于下表事件名,MsgContent.Data 是 JSON 字符串。客户端按 event_id 去重。
8.1 收到关系申请
房间 IM 和 C2C IM 都会收到:
| 字段 | 值 |
|---|---|
MsgContent.Desc |
cp_application_created |
Data.event_type |
cp_application_created |
Data:
{
"event_id": "cp_application:created:10002:1",
"event_type": "cp_application_created",
"application_id": "cp_app_10001_10002_cp",
"relation_type": "cp",
"status": "pending",
"requester": {
"user_id": "10001",
"display_user_id": "123456",
"username": "Alice",
"avatar": "https://cdn.example/a.png"
},
"target": {
"user_id": "10002",
"display_user_id": "654321",
"username": "Bob",
"avatar": "https://cdn.example/b.png"
},
"room_id": "room_1001",
"room_region_id": 86,
"gift": {
"gift_id": "cp_ring",
"gift_name": "CP Ring",
"gift_icon_url": "https://cdn.example/cp-ring.png",
"gift_animation_url": "",
"gift_count": 1,
"gift_value": 520,
"billing_receipt_id": "gift_tx_1"
},
"expires_at_ms": 1779345400000,
"decided_at_ms": 0,
"source_created_at_ms": 1779259000000,
"recipient_user_id": "10002"
}
处理:
- 房间 IM 只有
recipient_user_id等于当前用户时显示同意/拒绝按钮。 - C2C IM 在私聊里显示同意/拒绝按钮。
expires_at_ms <= server_time_ms时按钮置灰并刷新申请列表。
8.2 申请被同意
C2C 会收到双方互发的两条消息,房间 IM 仍然只给申请发起人 A 展示:
| 字段 | 值 |
|---|---|
MsgContent.Desc |
cp_application_accepted |
Data.event_type |
cp_application_accepted |
Data 字段和申请消息一致,差异是:
| 字段 | 值 |
|---|---|
status |
accepted |
decided_at_ms |
处理时间 |
recipient_user_id |
房间 IM 中为 A 的 user_id |
C2C 差异:
| 方向 | c2c_notice_role |
message_text |
|---|---|---|
| B -> A | accepted_to_requester |
我同意了你 |
| A -> B | accepted_to_acceptor |
对方已同意你的申请 |
处理:
- A 收到后刷新
/api/v1/cp/relationships。 - B 操作成功后也刷新关系列表。
- 同一对用户其他 pending 申请需要置灰。
8.3 申请被拒绝
C2C 和房间 IM 都会收到:
| 字段 | 值 |
|---|---|
MsgContent.Desc |
cp_application_rejected |
Data.event_type |
cp_application_rejected |
Data 字段和申请消息一致,差异是:
| 字段 | 值 |
|---|---|
status |
rejected |
reason |
拒绝原因,可为空 |
decided_at_ms |
处理时间 |
recipient_user_id |
房间 IM 中为 A 的 user_id |
8.4 区域关系成立播报
用户组成任意关系后,会发送区域 IM:
| 字段 | 值 |
|---|---|
MsgContent.Desc |
cp_relationship_created |
Data.event_type |
cp_relationship_created |
Data:
{
"event_id": "cp_relationship:created:10002:1",
"event_type": "cp_relationship_created",
"relationship_id": "cp_rel_10001_10002",
"application_id": "cp_app_10001_10002_cp",
"relation_type": "cp",
"requester": {
"user_id": "10001",
"display_user_id": "123456",
"username": "Alice",
"avatar": "https://cdn.example/a.png"
},
"target": {
"user_id": "10002",
"display_user_id": "654321",
"username": "Bob",
"avatar": "https://cdn.example/b.png"
},
"gift": {
"gift_id": "cp_ring",
"gift_name": "CP Ring",
"gift_icon_url": "https://cdn.example/cp-ring.png",
"gift_animation_url": "",
"gift_count": 1,
"gift_value": 520,
"billing_receipt_id": "gift_tx_1"
},
"room_id": "room_1001",
"room_region_id": 86,
"group_id": "hy_lalu_bc_r_86",
"source_created_at_ms": 1779259100000
}
处理:
- 只在区域播报位展示,不当作私聊消息。
- 点击可进入
room_id对应房间;房间不存在或进房失败时只关闭播报。
8. 客户端错误提示
| code | 建议提示 |
|---|---|
CONFLICT |
你或对方已经有关系;如果申请已过期则提示 申请已过期 |
NOT_FOUND |
申请不存在或已失效 |
PERMISSION_DENIED |
只能处理发给你的申请 |
9. 本地状态规则
- 不要靠送礼成功直接创建关系。
- 不要靠 C2C 或房间按钮本地改关系,必须调用同意或拒绝接口。
- 同一个
event_id只处理一次。 - 同一个
application_id在房间和 C2C 里可能都出现,按钮状态要共享。 - 申请过期时间以
expires_at_ms为准。 - 亲密值和等级以后端关系接口返回为准,不在 Flutter 里按礼物价格推算。
- 进度条和距离下一级只使用关系接口返回的阈值字段;字段缺失时不展示,不要伪造
next_level_threshold、needed_for_next_level或进度百分比。