hyapp-server/docs/flutter对接/房间锁房接口Flutter对接.md
2026-05-14 00:25:02 +08:00

11 KiB
Raw Permalink Blame History

房间锁房接口 Flutter 对接

本文描述 Flutter App 对接房间锁房能力的 HTTP 接口。房间密码由 room-service 只保存 bcrypt 哈希;客户端只提交明文用于本次设置或进房校验,任何列表、搜索、详情、房间消息和响应体都不会返回明文或哈希。

接口地址

POST /api/v1/rooms/password

POST /api/v1/rooms/join

GET /api/v1/rooms

GET /api/v1/rooms/feeds

本地开发地址:

http://127.0.0.1:13000

请求方法

方法 接口 说明
POST /api/v1/rooms/password 房主设置或清空房间入房密码。
POST /api/v1/rooms/join 进入房间;带锁房间需要传 password
GET /api/v1/rooms 公共房间列表和搜索;房间卡片返回 locked
GET /api/v1/rooms/feeds Mine 页房间流;房间卡片返回 locked

请求头

Header 必填 模拟值 说明
Authorization Bearer <access_token> 登录 access token服务端从 token 读取当前用户 ID。
X-App-Code lalu App 编码;登录态接口最终以 token 内的 app_code 为准。

设置或清空房间密码

只有房主可以调用。房管、主持人和普通观众调用会返回 PERMISSION_DENIED

请求体:

字段 必填 类型 模拟值 说明
room_id string room_10001 房间 ID。
command_id string cmd_room_password_room_10001_1710000000 房间命令幂等键;同一次点击重试必须复用。
locked bool true true 表示设置密码,false 表示清空密码。
password locked=true 时必填 string 1234 入房密码;服务端 trim 后最长 64 个字符。locked=false 时忽略。

设置密码请求示例:

POST /api/v1/rooms/password
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json

{
  "room_id": "room_10001",
  "command_id": "cmd_room_password_room_10001_1710000000",
  "locked": true,
  "password": "1234"
}

清空密码请求示例:

POST /api/v1/rooms/password
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json

{
  "room_id": "room_10001",
  "command_id": "cmd_room_password_room_10001_1710000100",
  "locked": false
}

成功响应:

{
  "code": "OK",
  "message": "ok",
  "request_id": "req_abc",
  "data": {
    "result": {
      "applied": true,
      "room_version": 12,
      "server_time_ms": 1710000000456
    },
    "room": {
      "room_id": "room_10001",
      "im_group_id": "room_10001",
      "room_short_id": "100001",
      "title": "Live Room",
      "cover_url": "https://cdn.example/room.png",
      "owner_user_id": "10001",
      "mode": "voice",
      "status": "active",
      "chat_enabled": true,
      "locked": true,
      "heat": 1000,
      "online_count": 12,
      "seat_count": 15,
      "occupied_seat_count": 2,
      "version": 12
    },
    "seats": [],
    "server_time_ms": 1710000000456
  }
}

字段说明:

字段 类型 说明
data.result.applied bool 本次命令是否产生状态变更;重复提交相同命令可能为 false
data.result.room_version int64 命令完成后房间版本。
data.room.locked bool 最新锁房状态;true 表示后续新用户进房需要密码。
data.room.version int64 最新房间快照版本;客户端本地房间状态以更大版本覆盖旧状态。
data.seats array 当前座位列表;锁房接口成功后可直接复用该房间快照刷新页面。

带密码进入房间

客户端进入房间前先看房间卡片或详情里的 locked

locked 客户端行为
false 直接调用 JoinRoompassword 可不传或传空字符串。
true 先弹出密码输入,再调用 JoinRoom 并传 password

请求体:

字段 必填 类型 模拟值 说明
room_id string room_10001 房间 ID。
command_id string cmd_join_room_10001_20001_1710000000 进房命令幂等键;同一次进房重试必须复用。
role string audience 当前固定传 audience
password 锁房时必填 string 1234 只用于本次进房校验,服务端不保存明文。

请求示例:

POST /api/v1/rooms/join
Authorization: Bearer <access_token>
X-App-Code: lalu
Content-Type: application/json

{
  "room_id": "room_10001",
  "command_id": "cmd_join_room_10001_20001_1710000000",
  "role": "audience",
  "password": "1234"
}

成功响应只展示锁房相关字段:

{
  "code": "OK",
  "message": "ok",
  "request_id": "req_abc",
  "data": {
    "result": {
      "applied": true,
      "room_version": 13,
      "server_time_ms": 1710000001456
    },
    "room": {
      "room_id": "room_10001",
      "im_group_id": "room_10001",
      "locked": true,
      "version": 13
    },
    "viewer": {
      "user_id": "20001",
      "role": "audience",
      "joined_at_ms": 1710000001456,
      "last_seen_at_ms": 1710000001456
    },
    "im": {
      "group_id": "room_10001",
      "need_join_group": true
    },
    "rtc": {
      "need_token": true,
      "available": true
    },
    "server_time_ms": 1710000001456
  }
}

进房规则:

场景 结果
房间未锁 不校验 password
房间已锁且密码正确 JoinRoom 成功,客户端再调用腾讯 IM joinGroup 和 RTC enterRoom
房间已锁且密码错误或缺失 返回 403 PERMISSION_DENIED,不会创建 presence不会返回 RTC token。
房主进入自己的房间 不需要密码。
用户已经在房间内 再次 JoinRoom 按幂等恢复当前房间态,不会因为后续锁房被踢出。

房间列表和搜索 locked 字段

公共列表和搜索使用同一个接口:

GET /api/v1/rooms?tab=hot&query=100001
Authorization: Bearer <access_token>
X-App-Code: lalu

Mine 页房间流:

GET /api/v1/rooms/feeds?tab=followed
Authorization: Bearer <access_token>
X-App-Code: lalu

列表响应示例:

{
  "code": "OK",
  "message": "ok",
  "request_id": "req_abc",
  "data": {
    "rooms": [
      {
        "room_id": "room_10001",
        "im_group_id": "room_10001",
        "owner_user_id": "10001",
        "title": "Live Room",
        "cover_url": "https://cdn.example/room.png",
        "mode": "voice",
        "status": "active",
        "locked": true,
        "heat": 1000,
        "online_count": 12,
        "seat_count": 15,
        "occupied_seat_count": 2,
        "visible_region_id": 1,
        "app_code": "lalu",
        "room_short_id": "100001"
      }
    ],
    "next_cursor": ""
  }
}

字段说明:

字段 类型 说明
rooms[].locked bool 房间是否需要入房密码;搜索结果和普通列表一致。
rooms[].im_group_id string 腾讯 IM 群 ID只用于 JoinRoom 成功后的 joinGroup,不能绕过 JoinRoom。
next_cursor string 下一页游标;为空表示没有下一页。

错误处理

失败返回仍使用统一 envelope

{
  "code": "PERMISSION_DENIED",
  "message": "permission denied",
  "request_id": "req_abc"
}
HTTP 状态码 code 典型场景 处理方式
400 INVALID_ARGUMENT room_idcommand_id 缺失;设置锁房时 password 为空或超过 64 字符。 客户端修正参数,不自动重试。
401 UNAUTHORIZED token 缺失、无效、过期或会话失效。 重新登录。
403 PROFILE_REQUIRED 用户资料未完成。 跳转资料补全流程。
403 PERMISSION_DENIED 非房主设置密码;进锁房密码错误;用户被 ban。 设置密码时隐藏入口或提示无权限;进房时提示密码错误。
404 NOT_FOUND 房间不存在。 刷新列表或退出房间页。
409 ROOM_CLOSED 房间已关闭。 刷新列表或退出房间页。
409 CONFLICT 同一个 command_id 被不同请求体复用。 为新的用户动作生成新的 command_id
502 UPSTREAM_ERROR 内部服务暂不可用。 可提示稍后重试。

幂等规则:

场景 返回
同一个 command_id 重试同一次设置密码 返回同一命令结果,不重复变更房间。
同一个 command_id 换另一个密码 409 CONFLICT
重复清空已解锁房间 200 OKdata.room.locked=false
同一个用户重复 JoinRoom 返回当前房间首屏数据,不重复创建 presence。

Flutter 解析示例

class RoomCommandResult {
  RoomCommandResult({
    required this.applied,
    required this.roomVersion,
    required this.serverTimeMs,
  });

  final bool applied;
  final int roomVersion;
  final int serverTimeMs;

  factory RoomCommandResult.fromJson(Map<String, dynamic> json) {
    return RoomCommandResult(
      applied: json['applied'] as bool? ?? false,
      roomVersion: (json['room_version'] as num?)?.toInt() ?? 0,
      serverTimeMs: (json['server_time_ms'] as num?)?.toInt() ?? 0,
    );
  }
}

class RoomCard {
  RoomCard({
    required this.roomId,
    required this.imGroupId,
    required this.title,
    required this.locked,
    required this.onlineCount,
  });

  final String roomId;
  final String imGroupId;
  final String title;
  final bool locked;
  final int onlineCount;

  factory RoomCard.fromJson(Map<String, dynamic> json) {
    return RoomCard(
      roomId: json['room_id'] as String? ?? '',
      imGroupId: json['im_group_id'] as String? ?? '',
      title: json['title'] as String? ?? '',
      locked: json['locked'] as bool? ?? false,
      onlineCount: (json['online_count'] as num?)?.toInt() ?? 0,
    );
  }
}

class RoomLockResult {
  RoomLockResult({
    required this.result,
    required this.room,
    required this.serverTimeMs,
  });

  final RoomCommandResult result;
  final RoomCard room;
  final int serverTimeMs;

  factory RoomLockResult.fromJson(Map<String, dynamic> json) {
    return RoomLockResult(
      result: RoomCommandResult.fromJson(
        json['result'] as Map<String, dynamic>? ?? const {},
      ),
      room: RoomCard.fromJson(
        json['room'] as Map<String, dynamic>? ?? const {},
      ),
      serverTimeMs: (json['server_time_ms'] as num?)?.toInt() ?? 0,
    );
  }
}

调用时先解析外层 envelope只有 code == "OK" 时读取 data;其他 code 按登录态、参数错误、权限错误或服务错误处理,并把 request_id 写入客户端日志。

客户端生成 command_id 时按一次用户动作生成一次,例如:

String newRoomCommandId(String action, String roomId, String userId) {
  return 'cmd_${action}_${roomId}_${userId}_${DateTime.now().millisecondsSinceEpoch}';
}

同一次 HTTP 重试复用原 command_id;用户重新输入密码或再次点击设置按钮时生成新的 command_id