视频面试
This commit is contained in:
@@ -1,38 +1,57 @@
|
||||
# 面试视频房间接口
|
||||
# 面试视频 Token 接口
|
||||
|
||||
## 1. 接口说明
|
||||
## 1. 接口用途
|
||||
|
||||
本组接口用于创建一场面试视频通话,并分别获取面试官和候选人的 MyRTC 连接凭据。
|
||||
面试官和候选人分别调用同一个 Token 接口,但每次请求只返回当前调用者自己的连接凭据:
|
||||
|
||||
后端会在一次请求中完成以下操作:
|
||||
```text
|
||||
企业端调用一次 → 面试官 peerId + token
|
||||
候选人端调用一次 → 候选人 peerId + token
|
||||
```
|
||||
|
||||
1. 自动生成一个唯一的 `roomId`;
|
||||
2. 生成面试官专用的 `peerId`;
|
||||
3. 生成候选人专用的 `peerId`;
|
||||
4. 使用与 MyRTC 服务一致的 JWT 密钥,分别签发两枚一次性 token;
|
||||
5. 保证两枚 token 绑定同一个 `roomId`。
|
||||
企业端不会拿到候选人的 token,候选人端也不会拿到面试官的 token。
|
||||
|
||||
前端不需要调用房间详情查询和房间销毁接口。房间在 MyRTC 客户端成功执行 `join` 后创建;双方离开后由 MyRTC 自动回收空房间。
|
||||
两次请求必须关联到同一条面试邀约记录,后端会为同一个 `interviewId` 复用同一个 `roomId`,并为每次调用生成新的 `peerId` 和一次性 token。
|
||||
|
||||
## 2. 创建房间并获取双方凭据
|
||||
## 2. 面试官获取 Token
|
||||
|
||||
### 请求
|
||||
企业端使用后台用户登录态调用:
|
||||
|
||||
```http
|
||||
POST /app/interview/rtc/token
|
||||
Authorization: Bearer <业务系统登录Token>
|
||||
POST /cms/interview/rtc/token?interviewId=123
|
||||
Authorization: Bearer <企业端登录Token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求体为空即可,不需要传入 `roomId`、`peerId` 或 token。
|
||||
请求体为空:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
### 成功响应
|
||||
企业端接口只返回当前面试官的一套凭据。
|
||||
|
||||
接口遵循后端统一的 `AjaxResult` 返回格式:
|
||||
## 3. 候选人获取 Token
|
||||
|
||||
候选人端使用移动端登录态调用:
|
||||
|
||||
```http
|
||||
POST /app/interview/rtc/token?interviewId=123
|
||||
Authorization: Bearer <候选人登录Token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求体为空:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
候选人端接口只返回当前候选人的一套凭据。
|
||||
|
||||
## 4. 成功响应
|
||||
|
||||
两个接口的返回格式相同,但每次只对应当前调用者:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -40,17 +59,9 @@ Content-Type: application/json
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"roomId": "room-0a4c3fbd8f9e4b70a0e2de99f6b5f2a8",
|
||||
"expiresIn": "5m",
|
||||
"interviewer": {
|
||||
"role": "interviewer",
|
||||
"peerId": "interviewer-4a78d8f0c4a5468fb0d8d05a8199b3bb",
|
||||
"token": "eyJhbGciOiJIUzI1NiJ9..."
|
||||
},
|
||||
"candidate": {
|
||||
"role": "candidate",
|
||||
"peerId": "candidate-1d2f7e2e2ae8462da3bb514d2f39b8e3",
|
||||
"token": "eyJhbGciOiJIUzI1NiJ9..."
|
||||
}
|
||||
"peerId": "peer-4a78d8f0c4a5468fb0d8d05a8199b3bb",
|
||||
"token": "eyJhbGciOiJIUzI1NiJ9...",
|
||||
"expiresIn": "5m"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -61,113 +72,126 @@ Content-Type: application/json
|
||||
| --- | --- | --- |
|
||||
| `code` | number | `200` 表示成功 |
|
||||
| `msg` | string | 接口提示信息 |
|
||||
| `data.roomId` | string | 本次面试房间 ID,双方必须使用同一个值 |
|
||||
| `data.roomId` | string | 当前面试房间 ID;同一个 `interviewId` 的双方得到相同值 |
|
||||
| `data.peerId` | string | 当前调用者在房间内的唯一 ID |
|
||||
| `data.token` | string | 当前调用者的一次性 MyRTC JWT |
|
||||
| `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. 前端使用流程
|
||||
## 5. 调用流程
|
||||
|
||||
### 3.1 调用后端接口
|
||||
假设面试邀约 ID 为 `123`。
|
||||
|
||||
### 5.1 企业端
|
||||
|
||||
```javascript
|
||||
const response = await request.post('/app/interview/rtc/token', {})
|
||||
const response = await request.post(
|
||||
'/cms/interview/rtc/token?interviewId=123',
|
||||
{}
|
||||
)
|
||||
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.msg || '创建面试视频房间失败')
|
||||
throw new Error(response.msg || '获取面试官视频凭据失败')
|
||||
}
|
||||
|
||||
const session = response.data
|
||||
const rtc = response.data
|
||||
|
||||
const roomId = session.roomId
|
||||
const interviewer = session.interviewer
|
||||
const candidate = session.candidate
|
||||
await rtcClient.connect({
|
||||
roomId: rtc.roomId,
|
||||
peerId: rtc.peerId,
|
||||
token: rtc.token
|
||||
})
|
||||
```
|
||||
|
||||
注意:`roomId` 在响应的 `data.roomId` 中,不是在响应最外层。
|
||||
|
||||
### 3.2 面试官加入房间
|
||||
|
||||
面试官客户端使用:
|
||||
### 5.2 候选人端
|
||||
|
||||
```javascript
|
||||
{
|
||||
roomId: session.roomId,
|
||||
peerId: session.interviewer.peerId,
|
||||
token: session.interviewer.token
|
||||
const response = await request.post(
|
||||
'/app/interview/rtc/token?interviewId=123',
|
||||
{}
|
||||
)
|
||||
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.msg || '获取候选人视频凭据失败')
|
||||
}
|
||||
|
||||
const rtc = response.data
|
||||
|
||||
await rtcClient.connect({
|
||||
roomId: rtc.roomId,
|
||||
peerId: rtc.peerId,
|
||||
token: rtc.token
|
||||
})
|
||||
```
|
||||
|
||||
### 3.3 候选人加入房间
|
||||
两端的 `roomId` 必须相同,但两端的 `peerId` 和 `token` 必须使用各自接口返回的值,不能互换。
|
||||
|
||||
候选人客户端使用:
|
||||
## 6. MyRTC WebSocket 连接
|
||||
|
||||
```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` 查询参数传入,例如:
|
||||
当前 MyRTC 服务的信令地址是:
|
||||
|
||||
```text
|
||||
wss://rtc.zhaopinzao8dian.com/signaling?token=<url-encoded-token>
|
||||
```
|
||||
|
||||
如果使用 `WebSocket` 原生 API,建议使用 `URL` 对 token 做编码,不要直接拼接未编码的 token:
|
||||
原生 WebSocket 建议使用 `URL` 自动编码 token:
|
||||
|
||||
```javascript
|
||||
const wsUrl = new URL('wss://rtc.zhaopinzao8dian.com/signaling')
|
||||
wsUrl.searchParams.set('token', token)
|
||||
wsUrl.searchParams.set('token', rtc.token)
|
||||
|
||||
const ws = new WebSocket(wsUrl.toString())
|
||||
```
|
||||
|
||||
加入房间时,发送的业务消息需要包含 `roomId`、`peerId` 和 token。协议形式如下:
|
||||
连接成功后发送 `join` 请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "request",
|
||||
"id": 1,
|
||||
"action": "join",
|
||||
"requestId": "join-001",
|
||||
"payload": {
|
||||
"roomId": "room-0a4c3fbd8f9e4b70a0e2de99f6b5f2a8",
|
||||
"peerId": "interviewer-4a78d8f0c4a5468fb0d8d05a8199b3bb"
|
||||
"roomId": "room-0a4c3fbd8f9e4b70a0e2de99f6b3bb",
|
||||
"peerId": "peer-4a78d8f0c4a5468fb0d8d05a8199b3bb"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
token 通常在建立 WebSocket 连接时通过 SDK 配置或连接参数传入,不能把面试官 token 和候选人 token 互换。
|
||||
`id` 必须是正整数。当前 MyRTC 服务要求使用 `id`,不是 `requestId`。
|
||||
|
||||
如果使用项目已有 MyRTC SDK,前端只需要将以下三个字段传给 SDK:
|
||||
## 7. interviewId 的来源
|
||||
|
||||
```javascript
|
||||
await rtcClient.connect({
|
||||
roomId,
|
||||
peerId,
|
||||
token
|
||||
})
|
||||
`interviewId` 就是业务表 `interview_invitation` 的主键 ID,不是 MyRTC 临时生成的 ID。
|
||||
|
||||
候选人端可以从以下接口获得面试邀约 ID:
|
||||
|
||||
```http
|
||||
GET /app/interview/list
|
||||
GET /app/interview/{id}
|
||||
```
|
||||
|
||||
## 5. token 注意事项
|
||||
返回数据中的:
|
||||
|
||||
- token 是一次性的,成功 `join` 后即会被 MyRTC 消费;
|
||||
- token 默认有效期为 `5m`,过期后不能继续使用;
|
||||
- 页面刷新或连接断开后重新连接,不能继续复用已经成功 `join` 的 token,需要重新调用后端接口签发;
|
||||
- 面试官和候选人的 token 绑定不同的 `peerId`,不能让两个客户端使用同一组凭据;
|
||||
- `roomId`、`peerId` 和 token 应保存在当前面试页面的内存状态中,不建议写入长期本地存储;
|
||||
- 前端不需要保存或传递 MyRTC 的 `ADMIN_TOKEN`,该令牌只由业务后端访问 MyRTC 控制面时使用。
|
||||
```json
|
||||
{
|
||||
"id": 123
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 错误响应
|
||||
其中 `id` 就是获取 RTC Token 时使用的 `interviewId`。
|
||||
|
||||
企业端也应使用企业端面试列表中的同一条面试邀约记录 ID。
|
||||
|
||||
## 8. Token 注意事项
|
||||
|
||||
- token 是一次性的,成功 `join` 后即被 MyRTC 消费;
|
||||
- token 默认有效期为 `5m`;
|
||||
- 页面刷新或连接断开后重新连接,需要重新获取 token;
|
||||
- 每次获取 token 都会生成新的 `peerId`;
|
||||
- 同一个 `interviewId` 在 Redis 中对应同一个 `roomId`,有效期默认 6 小时;
|
||||
- 前端不需要调用房间详情查询和房间销毁接口;
|
||||
- 前端不需要保存或传递 MyRTC 的 `ADMIN_TOKEN`。
|
||||
|
||||
## 9. 错误响应
|
||||
|
||||
### 未登录
|
||||
|
||||
@@ -178,9 +202,16 @@ await rtcClient.connect({
|
||||
}
|
||||
```
|
||||
|
||||
### MyRTC JWT 密钥未配置或配置不一致
|
||||
### 缺少 interviewId
|
||||
|
||||
后端可能返回类似以下错误:
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "Required request parameter 'interviewId' for method parameter type Long is not present"
|
||||
}
|
||||
```
|
||||
|
||||
### MyRTC JWT 密钥未配置
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -189,30 +220,4 @@ await rtcClient.connect({
|
||||
}
|
||||
```
|
||||
|
||||
如果后端签发 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` 即可。
|
||||
如果后端签发 token 使用的密钥和 MyRTC 服务配置不一致,WebSocket 握手会失败,需要检查业务后端和 MyRTC 的 `JWT_SECRET` 是否完全一致。
|
||||
|
||||
Reference in New Issue
Block a user