diff --git a/docs/app-interview-rtc-api.md b/docs/app-interview-rtc-api.md new file mode 100644 index 0000000..9a954a5 --- /dev/null +++ b/docs/app-interview-rtc-api.md @@ -0,0 +1,218 @@ +# 面试视频房间接口 + +## 1. 接口说明 + +本组接口用于创建一场面试视频通话,并分别获取面试官和候选人的 MyRTC 连接凭据。 + +后端会在一次请求中完成以下操作: + +1. 自动生成一个唯一的 `roomId`; +2. 生成面试官专用的 `peerId`; +3. 生成候选人专用的 `peerId`; +4. 使用与 MyRTC 服务一致的 JWT 密钥,分别签发两枚一次性 token; +5. 保证两枚 token 绑定同一个 `roomId`。 + +前端不需要调用房间详情查询和房间销毁接口。房间在 MyRTC 客户端成功执行 `join` 后创建;双方离开后由 MyRTC 自动回收空房间。 + +## 2. 创建房间并获取双方凭据 + +### 请求 + +```http +POST /app/interview/rtc/token +Authorization: Bearer <业务系统登录Token> +Content-Type: application/json +``` + +请求体为空即可,不需要传入 `roomId`、`peerId` 或 token。 + +```json +{} +``` + +### 成功响应 + +接口遵循后端统一的 `AjaxResult` 返回格式: + +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "roomId": "room-0a4c3fbd8f9e4b70a0e2de99f6b5f2a8", + "expiresIn": "5m", + "interviewer": { + "role": "interviewer", + "peerId": "interviewer-4a78d8f0c4a5468fb0d8d05a8199b3bb", + "token": "eyJhbGciOiJIUzI1NiJ9..." + }, + "candidate": { + "role": "candidate", + "peerId": "candidate-1d2f7e2e2ae8462da3bb514d2f39b8e3", + "token": "eyJhbGciOiJIUzI1NiJ9..." + } + } +} +``` + +字段说明: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `code` | number | `200` 表示成功 | +| `msg` | string | 接口提示信息 | +| `data.roomId` | string | 本次面试房间 ID,双方必须使用同一个值 | +| `data.expiresIn` | string | token 有效期,当前通常为 `5m` | +| `data.interviewer.role` | string | 固定为 `interviewer` | +| `data.interviewer.peerId` | string | 面试官在该房间内的唯一 ID | +| `data.interviewer.token` | string | 面试官的一次性 MyRTC JWT | +| `data.candidate.role` | string | 固定为 `candidate` | +| `data.candidate.peerId` | string | 候选人在该房间内的唯一 ID | +| `data.candidate.token` | string | 候选人的一次性 MyRTC JWT | + +## 3. 前端使用流程 + +### 3.1 调用后端接口 + +```javascript +const response = await request.post('/app/interview/rtc/token', {}) + +if (response.code !== 200) { + throw new Error(response.msg || '创建面试视频房间失败') +} + +const session = response.data + +const roomId = session.roomId +const interviewer = session.interviewer +const candidate = session.candidate +``` + +注意:`roomId` 在响应的 `data.roomId` 中,不是在响应最外层。 + +### 3.2 面试官加入房间 + +面试官客户端使用: + +```javascript +{ + roomId: session.roomId, + peerId: session.interviewer.peerId, + token: session.interviewer.token +} +``` + +### 3.3 候选人加入房间 + +候选人客户端使用: + +```javascript +{ + roomId: session.roomId, + peerId: session.candidate.peerId, + token: session.candidate.token +} +``` + +两端的 `roomId` 必须完全相同,但 `peerId` 和 `token` 必须使用各自对应角色的数据,不能交叉使用。 + +## 4. MyRTC WebSocket 连接参数 + +MyRTC 的 WebSocket 地址由 MyRTC 服务部署地址决定。当前服务的信令路径是 `/signaling`,JWT 通过 WebSocket 握手阶段的 `token` 查询参数传入,例如: + +```text +wss://rtc.zhaopinzao8dian.com/signaling?token= +``` + +如果使用 `WebSocket` 原生 API,建议使用 `URL` 对 token 做编码,不要直接拼接未编码的 token: + +```javascript +const wsUrl = new URL('wss://rtc.zhaopinzao8dian.com/signaling') +wsUrl.searchParams.set('token', token) +const ws = new WebSocket(wsUrl.toString()) +``` + +加入房间时,发送的业务消息需要包含 `roomId`、`peerId` 和 token。协议形式如下: + +```json +{ + "type": "request", + "action": "join", + "requestId": "join-001", + "payload": { + "roomId": "room-0a4c3fbd8f9e4b70a0e2de99f6b5f2a8", + "peerId": "interviewer-4a78d8f0c4a5468fb0d8d05a8199b3bb" + } +} +``` + +token 通常在建立 WebSocket 连接时通过 SDK 配置或连接参数传入,不能把面试官 token 和候选人 token 互换。 + +如果使用项目已有 MyRTC SDK,前端只需要将以下三个字段传给 SDK: + +```javascript +await rtcClient.connect({ + roomId, + peerId, + token +}) +``` + +## 5. token 注意事项 + +- token 是一次性的,成功 `join` 后即会被 MyRTC 消费; +- token 默认有效期为 `5m`,过期后不能继续使用; +- 页面刷新或连接断开后重新连接,不能继续复用已经成功 `join` 的 token,需要重新调用后端接口签发; +- 面试官和候选人的 token 绑定不同的 `peerId`,不能让两个客户端使用同一组凭据; +- `roomId`、`peerId` 和 token 应保存在当前面试页面的内存状态中,不建议写入长期本地存储; +- 前端不需要保存或传递 MyRTC 的 `ADMIN_TOKEN`,该令牌只由业务后端访问 MyRTC 控制面时使用。 + +## 6. 错误响应 + +### 未登录 + +```json +{ + "code": 500, + "msg": "未登录" +} +``` + +### MyRTC JWT 密钥未配置或配置不一致 + +后端可能返回类似以下错误: + +```json +{ + "code": 500, + "msg": "MyRTC JWT_SECRET 未配置" +} +``` + +如果后端签发 token 使用的密钥和 MyRTC 服务配置不一致,前端会在连接或 `join` 阶段收到 token 签名错误。此时需要检查业务后端和 MyRTC 的 `JWT_SECRET` 是否完全一致。 + +### MyRTC 服务不可用 + +```json +{ + "code": 500, + "msg": "无法连接 MyRTC" +} +``` + +## 7. 房间查询和销毁接口 + +这两个接口不是前端视频通话流程的一部分,前端无需调用: + +```text +GET /app/interview/rtc/room/{roomId} +DELETE /app/interview/rtc/room/{roomId} +``` + +它们仅供后端管理或运维场景使用。正常情况下,前端只需要: + +```text +POST /app/interview/rtc/token +``` + +然后使用响应中的双方凭据加入同一个 `roomId` 即可。