# CP关系Flutter对接 本文只写 App 需要对接的地址、参数、返回值和相关 IM。CP 关系包含 `cp`、`brother`、`sister` 三种类型;每个用户同时只能有一个 active 关系,不需要佩戴接口,也没有后台数量配置。 ## 1. 礼物面板CP Tab 地址: ```http GET /api/v1/rooms/{room_id}/gift-panel Authorization: Bearer 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礼物 地址: ```http POST /api/v1/rooms/gift/send Authorization: Bearer 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 | 示例: ```json { "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. 查询我的申请 地址: ```http GET /api/v1/cp/applications?direction=incoming&status=pending&page=1&page_size=20 Authorization: Bearer X-App-Code: lalu ``` 参数: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `direction` | string | 否 | `incoming` 我收到的;`outgoing` 我发出的 | | `status` | string | 否 | `pending`、`accepted`、`rejected`、`expired`、`blocked` | | `page` | int32 | 否 | 默认 1 | | `page_size` | int32 | 否 | 默认 20 | 返回值: ```json { "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. 同意申请 地址: ```http POST /api/v1/cp/applications/{application_id}/accept Authorization: Bearer X-App-Code: lalu ``` 参数: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `application_id` | string | 是 | 申请 ID | 返回值: ```json { "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. 拒绝申请 地址: ```http POST /api/v1/cp/applications/{application_id}/reject Authorization: Bearer X-App-Code: lalu Content-Type: application/json ``` 参数: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `application_id` | string | 是 | 申请 ID | | `reason` | string | 否 | 拒绝原因 | 返回值: ```json { "application": { "application_id": "cp_app_10001_10002_cp", "relation_type": "cp", "status": "rejected", "decided_at_ms": 1779259100000 } } ``` ## 6. 查询我的关系 地址: ```http GET /api/v1/cp/relationships?relation_type=cp&page=1&page_size=20 Authorization: Bearer X-App-Code: lalu ``` 参数: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `relation_type` | string | 否 | `cp`、`brother`、`sister`;不传返回全部 | | `page` | int32 | 否 | 默认 1 | | `page_size` | int32 | 否 | 默认 20 | 返回值: ```json { "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, "formed_at_ms": 1779259100000, "updated_at_ms": 1779259200000 } ], "total": 1 } ``` 说明: - 每个用户同时只能有一个 active 关系。 - 不需要佩戴接口,资料卡直接展示当前 active 关系。 ## 7. 相关IM 所有 IM 都是 Tencent `TIMCustomElem`,`MsgContent.Desc` 等于下表事件名,`MsgContent.Data` 是 JSON 字符串。客户端按 `event_id` 去重。 ### 7.1 收到关系申请 房间 IM 和 C2C IM 都会收到: | 字段 | 值 | | --- | --- | | `MsgContent.Desc` | `cp_application_created` | | `Data.event_type` | `cp_application_created` | Data: ```json { "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` 时按钮置灰并刷新申请列表。 ### 7.2 申请被同意 C2C 和房间 IM 都会收到: | 字段 | 值 | | --- | --- | | `MsgContent.Desc` | `cp_application_accepted` | | `Data.event_type` | `cp_application_accepted` | Data 字段和申请消息一致,差异是: | 字段 | 值 | | --- | --- | | `status` | `accepted` | | `decided_at_ms` | 处理时间 | | `recipient_user_id` | 房间 IM 中为 A 的 user_id | 处理: - A 收到后刷新 `/api/v1/cp/relationships`。 - B 操作成功后也刷新关系列表。 - 同一对用户其他 pending 申请需要置灰。 ### 7.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 | ### 7.4 区域关系成立播报 用户组成任意关系后,会发送区域 IM: | 字段 | 值 | | --- | --- | | `MsgContent.Desc` | `cp_relationship_created` | | `Data.event_type` | `cp_relationship_created` | Data: ```json { "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 里按礼物价格推算。