forked from baskbuddy/zhangshun
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
13 KiB
13 KiB
面试管理接口文档
概述
面试管理基础模块,支持企业创建面试、学生响应面试、企业取消/完成面试,以及面试状态时间线查看。
所有接口路径前缀: /api/v1
请求头
所有接口需要携带:
| 请求头 | 必填 | 说明 |
|---|---|---|
| Authorization | 是 | Bearer <JWT> |
| Content-Type | 是 | application/json |
| X-Request-Id | 否 | 请求追踪 ID |
时间字段和时区
- 所有时间字段使用 ISO 8601 格式 (如
2026-08-10T14:00:00) - 存储时区: UTC (由数据库层处理)
- API 层时区: 通过
timezone字段指定,默认Asia/Shanghai startAt必须在未来时间endAt必须晚于startAt- 面试时长不能超过 8 小时
状态枚举
SCHEDULED - 已安排 (企业创建后初始状态)
CONFIRMED - 已确认 (学生确认参加)
DECLINED - 已拒绝 (学生拒绝,终态)
RESCHEDULE_REQUESTED - 申请改期 (学生请求改期)
CANCELLED - 已取消 (企业取消,终态)
COMPLETED - 已完成 (企业标记完成,终态)
状态流转规则
SCHEDULED ──────────> CONFIRMED ──────────> COMPLETED (终态)
│ │
├────────────────────┼──> CANCELLED (终态)
│ │
├──> DECLINED (终态) │
│ │
└──> RESCHEDULE_REQUESTED ──> SCHEDULED (企业重新安排)
- SCHEDULED → CONFIRMED / DECLINED / RESCHEDULE_REQUESTED / CANCELLED
- RESCHEDULE_REQUESTED → SCHEDULED (企业重新安排)
- CONFIRMED → CANCELLED / COMPLETED
- DECLINED / CANCELLED / COMPLETED 为终态,不可再流转
- 非法状态流转返回 409
权限规则
| 操作 | 企业 (COMPANY) | 学生 (STUDENT) |
|---|---|---|
| 创建面试 | 仅本企业岗位投递 | 禁止 |
| 查看面试列表(投递维度) | 仅本企业 | 仅本人 |
| 查看面试详情 | 仅本企业 | 仅本人 |
| 修改面试 | 仅本企业 (SCHEDULED/CONFIRMED) | 禁止 |
| 取消面试 | 仅本企业 | 禁止 |
| 完成面试 | 仅本企业 (CONFIRMED 且已过 endAt) | 禁止 |
| 响应面试 | 禁止 | 仅本人 (SCHEDULED 状态) |
| 查看时间线 | 仅本企业 | 仅本人 |
- 非参与者访问任何接口返回 403
- 企业只能操作自己创建的面试
- 学生只能操作自己的面试
幂等规则
- 重复 CONFIRM (已 CONFIRMED): 返回 200,不产生状态变更
- 重复 DECLINE (已 DECLINED): 返回 200,不产生状态变更
- 重复 RESCHEDULE_REQUESTED (已 RESCHEDULE_REQUESTED): 返回 200,不产生状态变更
- 重复 CANCELLED (已 CANCELLED): 返回 200,不产生状态变更
- 重复 COMPLETED (已 COMPLETED): 返回 200,不产生状态变更
- 已处于终态的面试再次操作: 返回 409
- 并发更新: 乐观锁(version 字段)保护,不会覆盖状态历史
接口列表
1. 创建面试 (企业)
POST /api/v1/applications/{applicationId}/interviews
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| roundNo | Integer | 否 | 面试轮次,默认 1 |
| title | String | 是 | 面试标题,最大 200 字符 |
| interviewType | String | 是 | 面试类型: ONLINE / OFFLINE |
| startAt | DateTime | 是 | 面试开始时间 (ISO 8601) |
| endAt | DateTime | 是 | 面试结束时间 (ISO 8601) |
| timezone | String | 否 | 时区,默认 "Asia/Shanghai" |
| meetingUrl | String | 条件必填 | ONLINE 时必填,最大 500 字符 |
| address | String | 条件必填 | OFFLINE 时必填,最大 500 字符 |
| interviewerName | String | 否 | 面试官姓名,最大 100 字符 |
| notes | String | 否 | 备注 |
{
"roundNo": 1,
"title": "技术面试",
"interviewType": "ONLINE",
"startAt": "2026-08-15T14:00:00",
"endAt": "2026-08-15T15:00:00",
"timezone": "Asia/Shanghai",
"meetingUrl": "https://meeting.example.com/123",
"interviewerName": "张经理",
"notes": "请准备项目经验介绍"
}
成功响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"id": 1,
"applicationId": 1,
"companyUserId": 2,
"studentUserId": 3,
"roundNo": 1,
"title": "技术面试",
"interviewType": "ONLINE",
"startAt": "2026-08-15T14:00:00",
"endAt": "2026-08-15T15:00:00",
"timezone": "Asia/Shanghai",
"meetingUrl": "https://meeting.example.com/123",
"address": null,
"interviewerName": "张经理",
"notes": "请准备项目经验介绍",
"status": "SCHEDULED",
"version": 0,
"createdAt": "2026-08-13T10:00:00",
"updatedAt": "2026-08-13T10:00:00"
},
"requestId": "..."
}
错误:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 400 | BAD_REQUEST | 缺少必填字段、参数校验失败 |
| 400 | INTERVIEW_TIME_INVALID | 结束时间早于开始时间 |
| 400 | INTERVIEW_START_TIME_PAST | 面试开始时间在过去 |
| 400 | INTERVIEW_DURATION_EXCEEDED | 面试时长超过 8 小时 |
| 400 | INTERVIEW_ONLINE_URL_REQUIRED | ONLINE 类型需要 meetingUrl |
| 400 | INTERVIEW_OFFLINE_ADDRESS_REQUIRED | OFFLINE 类型需要 address |
| 401 | UNAUTHORIZED | 未提供有效 JWT |
| 403 | FORBIDDEN | 非企业用户 |
| 404 | APPLICATION_NOT_FOUND | 投递记录不存在 |
| 404 | COMPANY_NOT_FOUND | 企业信息不存在 |
2. 查询投递的面试列表
GET /api/v1/applications/{applicationId}/interviews
成功响应 (200):
{
"code": 0,
"message": "ok",
"data": [ { ...interview... } ],
"requestId": "..."
}
错误:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 401 | UNAUTHORIZED | 未提供有效 JWT |
| 403 | INTERVIEW_ACCESS_DENIED | 非投递学生或岗位所属企业 |
| 404 | APPLICATION_NOT_FOUND | 投递记录不存在 |
3. 我的面试列表
GET /api/v1/interviews/me?status=SCHEDULED&from=2026-08-01T00:00:00&to=2026-08-31T23:59:59&page=0&size=20
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| status | String | 否 | 状态筛选 (SCHEDULED/CONFIRMED/DECLINED 等) |
| from | DateTime | 否 | 开始时间 >= from (ISO 8601) |
| to | DateTime | 否 | 开始时间 <= to (ISO 8601) |
| page | int | 否 | 页码,默认 0 |
| size | int | 否 | 每页大小,默认 20,最大 100 |
成功响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"items": [ { ...interview... } ],
"total": 50,
"page": 0,
"size": 20,
"totalPages": 3
},
"requestId": "..."
}
错误:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 401 | UNAUTHORIZED | 未提供有效 JWT |
| 403 | FORBIDDEN | 非企业或学生角色 |
4. 面试详情
GET /api/v1/interviews/{interviewId}
成功响应 (200):
{
"code": 0,
"message": "ok",
"data": { ...interview... },
"requestId": "..."
}
错误:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 401 | UNAUTHORIZED | 未提供有效 JWT |
| 403 | INTERVIEW_ACCESS_DENIED | 非参与者 |
| 404 | INTERVIEW_NOT_FOUND | 面试不存在 |
5. 修改面试 (企业)
PUT /api/v1/interviews/{interviewId}
请求体: 所有字段均为可选,传入的字段才会更新
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| title | String | 否 | 面试标题 |
| startAt | DateTime | 否 | 面试开始时间,需与 endAt 同时提供 |
| endAt | DateTime | 否 | 面试结束时间,需与 startAt 同时提供 |
| timezone | String | 否 | 时区 |
| meetingUrl | String | 否 | 在线会议链接 |
| address | String | 否 | 面试地址 |
| interviewerName | String | 否 | 面试官姓名 |
| notes | String | 否 | 备注 |
{
"startAt": "2026-08-16T10:00:00",
"endAt": "2026-08-16T11:00:00",
"meetingUrl": "https://meeting.example.com/456"
}
成功响应 (200):
{
"code": 0,
"message": "ok",
"data": { ...interview... },
"requestId": "..."
}
错误:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 400 | INTERVIEW_TIME_INVALID | 结束时间早于开始时间 |
| 400 | INTERVIEW_START_TIME_PAST | 面试开始时间在过去 |
| 400 | INTERVIEW_DURATION_EXCEEDED | 面试时长超过 8 小时 |
| 401 | UNAUTHORIZED | 未提供有效 JWT |
| 403 | INTERVIEW_ACCESS_DENIED | 非本企业面试 |
| 403 | FORBIDDEN | 非企业用户 |
| 404 | INTERVIEW_NOT_FOUND | 面试不存在 |
| 409 | INVALID_INTERVIEW_STATUS_TRANSITION | 面试状态不允许修改 (非 SCHEDULED/CONFIRMED) |
6. 学生响应面试
POST /api/v1/interviews/{interviewId}/respond
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| action | String | 是 | CONFIRM / DECLINE / REQUEST_RESCHEDULE |
| reason | String | 否 | 原因说明 |
{
"action": "CONFIRM",
"reason": "时间可以接受"
}
成功响应 (200):
{
"code": 0,
"message": "ok",
"data": { ...interview... },
"requestId": "..."
}
错误:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 400 | BAD_REQUEST | action 缺失或值无效 |
| 401 | UNAUTHORIZED | 未提供有效 JWT |
| 403 | INTERVIEW_ACCESS_DENIED | 非本人面试 |
| 404 | INTERVIEW_NOT_FOUND | 面试不存在 |
| 409 | INVALID_INTERVIEW_STATUS_TRANSITION | 面试状态不可响应 (非 SCHEDULED) |
7. 企业取消面试
POST /api/v1/interviews/{interviewId}/cancel
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| reason | String | 否 | 取消原因 |
{
"reason": "岗位已招满"
}
成功响应 (200):
{
"code": 0,
"message": "ok",
"data": { ...interview... },
"requestId": "..."
}
错误:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 401 | UNAUTHORIZED | 未提供有效 JWT |
| 403 | INTERVIEW_ACCESS_DENIED | 非本企业面试 |
| 403 | FORBIDDEN | 非企业用户 |
| 404 | INTERVIEW_NOT_FOUND | 面试不存在 |
| 409 | INVALID_INTERVIEW_STATUS_TRANSITION | 面试已是终态 |
8. 企业完成面试
POST /api/v1/interviews/{interviewId}/complete
请求体 (可选):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| notes | String | 否 | 完成备注 |
{
"notes": "面试表现优秀"
}
成功响应 (200):
{
"code": 0,
"message": "ok",
"data": { ...interview... },
"requestId": "..."
}
错误:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 401 | UNAUTHORIZED | 未提供有效 JWT |
| 403 | INTERVIEW_ACCESS_DENIED | 非本企业面试 |
| 403 | FORBIDDEN | 非企业用户 |
| 404 | INTERVIEW_NOT_FOUND | 面试不存在 |
| 409 | INTERVIEW_COMPLETE_NOT_READY | 面试尚未到结束时间 |
| 409 | INVALID_INTERVIEW_STATUS_TRANSITION | 面试未确认 (非 CONFIRMED) |
9. 状态时间线
GET /api/v1/interviews/{interviewId}/timeline
成功响应 (200):
{
"code": 0,
"message": "ok",
"data": [
{
"id": 1,
"fromStatus": null,
"toStatus": "SCHEDULED",
"actorUserId": 2,
"reason": "Interview created",
"createdAt": "2026-08-13T10:00:00"
},
{
"id": 2,
"fromStatus": "SCHEDULED",
"toStatus": "CONFIRMED",
"actorUserId": 3,
"reason": "时间可以接受",
"createdAt": "2026-08-13T10:30:00"
}
],
"requestId": "..."
}
错误:
| 状态码 | 错误码 | 说明 |
|---|---|---|
| 401 | UNAUTHORIZED | 未提供有效 JWT |
| 403 | INTERVIEW_ACCESS_DENIED | 非参与者 |
| 404 | INTERVIEW_NOT_FOUND | 面试不存在 |
错误码汇总
| 错误码 | HTTP | 说明 |
|---|---|---|
| 8401 | 404 | INTERVIEW_NOT_FOUND - 面试不存在 |
| 8402 | 403 | INTERVIEW_ACCESS_DENIED - 无权访问面试 |
| 8403 | 409 | INVALID_INTERVIEW_STATUS_TRANSITION - 非法状态流转 |
| 8404 | 400 | INTERVIEW_TIME_INVALID - 时间区间无效 |
| 8405 | 400 | INTERVIEW_DURATION_EXCEEDED - 面试时长超过 8 小时 |
| 8406 | 400 | INTERVIEW_ONLINE_URL_REQUIRED - 在线面试需要会议链接 |
| 8407 | 400 | INTERVIEW_OFFLINE_ADDRESS_REQUIRED - 线下面试需要地址 |
| 8408 | 400 | INTERVIEW_START_TIME_PAST - 面试开始时间必须在未来 |
| 8409 | 409 | INTERVIEW_COMPLETE_NOT_READY - 面试未结束不能完成 |
| 8410 | 409 | 已响应,不可重复操作 |
通知事件
| 事件类型 | 触发方 | 接收方 |
|---|---|---|
| INTERVIEW_INVITED | 企业创建面试 | 学生 |
| INTERVIEW_CONFIRMED | 学生确认 | 企业 |
| INTERVIEW_DECLINED | 学生拒绝 | 企业 |
| INTERVIEW_RESCHEDULE_REQUESTED | 学生申请改期 | 企业 |
| INTERVIEW_RESCHEDULED | 企业修改时间 | 学生 |
| INTERVIEW_CANCELLED | 企业取消 | 学生 |
| INTERVIEW_COMPLETED | 企业完成 | 学生 |