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.
7.6 KiB
7.6 KiB
结构化面试评分 API(V31)
本文档描述「结构化面试评分」模块。这与候选人反馈(candidate_feedback)是两套完全独立的机制,请勿混淆:
- candidate_feedback / Candidate Scoring Engine:既有的候选人反馈与评分治理模块,语义保持不变。
- 结构化面试评分:企业针对某一场**已完成(
COMPLETED)**的面试,按固定五个维度填写并提交评分。独立落库到interview_scores/interview_score_history两张新表,不写 candidate_feedback。
只支持单面试官评分模型(一场面试一条评分);不新增多面试官模型。
OpenAPI 路径总数 113 → 116(3 个 path、4 个 operation)。
一、接口总览
| 操作 | 路径 | 说明 |
|---|---|---|
| PUT | /interviews/{interviewId}/score |
企业 owner 保存评分草稿(幂等更新) |
| GET | /interviews/{interviewId}/score |
查看评分(企业见 DRAFT/SUBMITTED;学生仅 SUBMITTED) |
| POST | /interviews/{interviewId}/score/submit |
提交草稿 → SUBMITTED(不可逆) |
| GET | /interviews/{interviewId}/score/history |
评分快照历史(无记录返回 200 空数组) |
身份与权限
- 所有接口都需要登录(JWT);身份只从 JWT/SecurityContext 获取,不接受
userId/studentUserId/companyId参数。 - 归属校验:
companyRepository.findByOwnerUserId(userId)解析企业;interview.companyUserId == company.ownerUserId且经由 application→job 的job.companyId == company.id且application.studentUserId == interview.studentUserId,否则403/8402。 - 企业 owner(COMPANY):可写可读草稿与已提交。
- 目标学生(STUDENT):只能查看**已提交(SUBMITTED)**的评分;查看 DRAFT 或他人评分返回
403/8402。 - ADMIN:不允许访问(
403/8402)。 - 面试不存在
404/8401。
二、保存评分草稿
PUT /interviews/{interviewId}/score
请求体:
{
"technicalScore": null,
"communicationScore": 4,
"problemSolvingScore": 3,
"roleFitScore": null,
"professionalismScore": 5,
"recommendation": "HIRE",
"comment": "综合素质较好"
}
字段:
- 五个维度
technicalScore/communicationScore/problemSolvingScore/roleFitScore/professionalismScore,取值 1-5;草稿阶段可留空(null)。 recommendation(可选):STRONG_HIRE/HIRE/NEUTRAL/NO_HIRE,企业手动选择,不由 overallScore 推导;非法值400/1001。comment(可选):≤2000 字符。overallScore:DTO 中不提供该字段,由服务端计算并忽略客户端传入值。
约束:
- 无评分时创建
DRAFT;已有 DRAFT 时更新同一行(@Version递增)。 SUBMITTED后再次 PUT →409/9503。- 保存草稿不写历史节点(历史仅写
NULL→DRAFT、DRAFT→SUBMITTED两次),但写审计日志INTERVIEW_SCORE_SAVED。 - 并发冲突 →
409/9501。
成功响应(脱敏):
{
"code": 200,
"message": "ok",
"data": {
"id": 1,
"interviewId": 10,
"applicationId": 20,
"status": "DRAFT",
"technicalScore": null,
"communicationScore": 4,
"problemSolvingScore": 3,
"roleFitScore": null,
"professionalismScore": 5,
"overallScore": null,
"recommendation": "HIRE",
"comment": "综合素质较好",
"submittedAt": null,
"version": 1,
"createdAt": "2026-08-26T10:00:00",
"updatedAt": "2026-08-26T10:00:00"
}
}
脱敏:响应不返回
companyUserId/studentUserId/evaluatorUserId/ 手机号 / 邮箱。
三、查看评分
GET /interviews/{interviewId}/score
- 企业 owner:可看
DRAFT与SUBMITTED。 - 目标学生:仅可看
SUBMITTED;查看 DRAFT →403/8402。 - 无评分 →
404/9504。 - 面试未完成 →
409/9502。
四、提交评分
POST /interviews/{interviewId}/score/submit
无请求体。将 DRAFT 提交为 SUBMITTED:
- 五个维度必须全部非空,否则
400/1001。 overallScore由服务端计算:(t + c + ps + rf + p) / 5,BigDecimal,保留两位小数,RoundingMode.HALF_UP。- 提交成功后
submittedAt落库;不可逆。 - 写历史节点
DRAFT→SUBMITTED;写审计日志INTERVIEW_SCORE_SUBMITTED。 - 幂等:重复 submit 已 SUBMITTED →
200返回原评分;不新增历史、不重复通知。 - 通知:仅通知目标学生一次,
businessType=INTERVIEW、businessId=interviewId、幂等键interview-score-submitted-{interviewId};草稿不发通知;不通知企业自身;无 Push。 - 并发冲突 →
409/9501。
五、查看评分历史
GET /interviews/{interviewId}/score/history
- 企业 owner:可查看全部历史。
- 目标学生:仅在评分
SUBMITTED后可查看。 - 无历史记录 →
200空数组items=[]。 - 历史快照
snapshot为解析后的维度分 +overallScore(JSON 字符串落库于snapshot_textTEXT 字段,不返回内部用户标识)。
响应示例:
{
"code": 200,
"message": "ok",
"data": [
{
"id": 1,
"scoreId": 1,
"interviewId": 10,
"fromStatus": null,
"toStatus": "DRAFT",
"actorRole": "COMPANY",
"snapshot": { "technicalScore": null, "overallScore": null },
"createdAt": "2026-08-26T10:00:00"
},
{
"id": 2,
"scoreId": 1,
"interviewId": 10,
"fromStatus": "DRAFT",
"toStatus": "SUBMITTED",
"actorRole": "COMPANY",
"snapshot": { "technicalScore": 4, "overallScore": 4.20 },
"createdAt": "2026-08-26T10:05:00"
}
]
}
脱敏:历史条目不返回
actorUserId、手机号、邮箱。
六、状态机与历史
无评分 → DRAFT → SUBMITTED
- PUT 无评分 → 创建
DRAFT(写历史NULL→DRAFT)。 - PUT 已有 DRAFT → 更新同一行,不写历史,写审计
INTERVIEW_SCORE_SAVED。 - POST submit:DRAFT → SUBMITTED(写历史
DRAFT→SUBMITTED,写审计INTERVIEW_SCORE_SUBMITTED)。 SUBMITTED不可逆;重复 submit 幂等。- 草稿每次 PUT
version递增;@Version乐观锁防并发。
七、错误码
| 错误码 | 含义 | HTTP |
|---|---|---|
| 9501 | 并发冲突(更新或首次并发创建撞唯一约束) | 409 |
| 9502 | 面试未完成(非 COMPLETED),不可评分 | 409 |
| 9503 | 评分已提交,不可再修改 | 409 |
| 9504 | 评分不存在 | 404 |
| 8401 | 面试不存在 | 404 |
| 8402 | 归属校验失败 / 越权访问 | 403 |
| 2002 | 非 COMPANY(对写入),未授权访问 | 403 |
| 1001 | 参数校验失败(非 1-5 维度、非法推荐值、提交缺维度) | 400 |
八、数据库(V31)
新增两张表,均 ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci:
interview_scores:interview_id唯一;五个评分字段允许 NULL 且在 1-5;overall_score1.00-5.00;statusCHECK(DRAFT/SUBMITTED);recommendationCHECK;SUBMITTED时五维、overallScore、submittedAt均非空;@Version;外键ON DELETE CASCADE。interview_score_history:snapshot_text TEXT(不使用 JSON 类型,避免 MySQL/H2 兼容问题);外键ON DELETE CASCADE。
不修改 V1-V30、不修改 CandidateFeedback、不修改 Interview 状态机、不修改 Candidate Scoring Engine、不修改个人数据导出/账号注销/隐私设置。
九、文档与验证
- OpenAPI 路径数 113 → 116;
docs/openapi-rc.json已同步。 - 验证:
python docs/verify_openapi.py;项目mvn test、mvn clean verify。