# 面试管理接口文档 ## 概述 面试管理基础模块,支持企业创建面试、学生响应面试、企业取消/完成面试,以及面试状态时间线查看。 所有接口路径前缀: `/api/v1` ## 请求头 所有接口需要携带: | 请求头 | 必填 | 说明 | |--------|------|------| | Authorization | 是 | `Bearer ` | | 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 | 否 | 备注 | ```json { "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):** ```json { "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):** ```json { "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):** ```json { "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):** ```json { "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 | 否 | 备注 | ```json { "startAt": "2026-08-16T10:00:00", "endAt": "2026-08-16T11:00:00", "meetingUrl": "https://meeting.example.com/456" } ``` **成功响应 (200):** ```json { "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 | 否 | 原因说明 | ```json { "action": "CONFIRM", "reason": "时间可以接受" } ``` **成功响应 (200):** ```json { "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 | 否 | 取消原因 | ```json { "reason": "岗位已招满" } ``` **成功响应 (200):** ```json { "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 | 否 | 完成备注 | ```json { "notes": "面试表现优秀" } ``` **成功响应 (200):** ```json { "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):** ```json { "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 | 企业完成 | 学生 |