轻松聘AI开发
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.
 
 
 

36 KiB

职来后端前端联调说明(RC)

1. API Base URL

环境 Base URL
本地开发 http://127.0.0.1:8080/api/v1
生产(示例) https://api.mydreamjob.example.com/api/v1

所有接口统一前缀 /api/v1,OpenAPI 文档:GET /api/v1/v3/api-docs(Swagger UI:/api/v1/swagger-ui/index.html)。

2. 后端启动(Windows)

先启动基础设施容器:

cd backend-infra
docker compose up -d

然后设置环境变量并启动:

cd mydreamjob-server
$env:DB_URL = "jdbc:mysql://127.0.0.1:3307/mydreamjob?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai"
$env:DB_USERNAME = "mydreamjob"
$env:DB_PASSWORD = "<你的数据库密码>"
$env:RABBITMQ_USERNAME = "mydreamjob_dev"
$env:RABBITMQ_PASSWORD = "<你的 RabbitMQ 密码>"
# 注意:本机 Windows 环境变量 RABBITMQ_VHOST 可能被设置为无关路径,必须显式覆盖为默认 vhost
$env:RABBITMQ_VHOST = "/"
$env:JWT_SECRET = "<至少256位的随机密钥>"
$env:SMS_MOCK_ENABLED = "true"
mvn spring-boot:run -Dspring-boot.run.profiles=dev

健康检查:http://127.0.0.1:8080/api/v1/health 返回 status: UP。

3. Mock 验证码日志位置

开发环境短信为 Mock,验证码打印在服务控制台/stdout 日志中:

MOCK SMS CODE: phone=13800000002, purpose=login, code=483920
  • 运行 mvn spring-boot:run → 看终端输出
  • 后台运行 → 看重定向的日志文件(如 logs/mydreamjob.log)
  • 每次发码后取最新一条 code=<6位数字>

4. Apifox 导入 OpenAPI

  1. 打开 Apifox → 新建项目
  2. 左侧「导入数据」→ 选择「OpenAPI/Swagger」
  3. 上传 docs/openapi-rc.json(或填 URL http://127.0.0.1:8080/api/v1/v3/api-docs)
  4. 点击「确认导入」,Apifox 自动生成全部接口、参数、响应模型
  5. 在 Apifox 中可直接调试:登录接口获取 token 后,设置「环境变量 token」,在接口「Authorization」填 Bearer {{token}}

5. Token 使用方式

登录成功返回 accessToken(有效期 24h)与 refreshToken。

{
  "code": 200,
  "data": {
    "userId": 2,
    "accessToken": "eyJhbGciOi...",
    "refreshToken": "eyJhbGciOi...",
    "expiresIn": 86400,
    "role": "STUDENT"
  }
}

后续请求:所有需要认证的接口在请求头携带

Authorization: Bearer <accessToken>

3. 请求头

Header 必需 说明
Authorization 认证接口 Bearer <token>
Content-Type POST/PUT/PATCH application/json
X-Request-Id 可选 全链路追踪 ID,审计日志会记录
User-Agent 可选 客户端标识,审计日志会记录

注意:X-Request-Id 和 User-Agent 会被写入 audit_logs,联调时建议带自定义值便于排查。

4. 分页格式

列表接口统一使用 page(从 0 起)和 size 参数,返回 PageResponse 结构:

{
  "code": 200,
  "data": {
    "items": [ ... ],
    "total": 42,
    "page": 0,
    "size": 20,
    "totalPages": 3
  }
}
接口 默认 size 最大 size
GET /jobs 20 50
GET /applications/me 20 100
GET /company/applications 20 100
GET /jobs/{id}/reviews 20 50
GET /conversations/{id}/messages 20 50
GET /notifications 20 50

5. 统一响应与错误码

所有接口返回统一 ApiResponse:

{ "code": 200, "message": "ok", "data": {...}, "requestId": "...", "timestamp": "..." }
HTTP 业务 code 含义
401 401(安全过滤器) 未登录 / 无 token
403 2002, 8004, 8303, 8402 无角色权限 / 越权(含企业归属不匹配)
400 1001, 3002-3006, 8404-8408, 8505, 9105 参数错误 / 非法请求体 / 资源未命中默认映射
404 4001, 5001, 6001, 7001, 8002, 9102, 9104, 8305, 8501 资源不存在
409 5003, 7003, 8001, 8003, 8301-8302, 8304, 8502-8504, 9101, 9103 重复 / 非法状态流转
500 1002, 9001 服务器内部错误

前端策略:优先判断 HTTP 状态码,再读取 body.code 显示业务信息。

6. 测试账号

账号 角色 说明
13800000002 STUDENT 学生,可投递/匹配/查看通知
13800000003 COMPANY 企业,可创建岗位/查看投递/更新状态
13800000001 ADMIN 管理员(预留)

登录方式(开发环境 Mock):

  1. POST /auth/sms/send?phone=<手机号> 发送验证码
  2. 验证码打印在服务日志:MOCK SMS CODE: ... code=xxxxxx
  3. POST /auth/login 携带 {phone, code, purpose:"login"} 获取 token

7. 典型联调流程

学生端:
  发码 → 登录 → GET /jobs → GET /jobs/{id}/match → POST /applications
        → GET /notifications → POST /notifications/{id}/read

企业端:
  发码 → 登录 → POST /companies/me → POST /jobs → PATCH /jobs/{id}/status
        → GET /company/applications?jobId= → PATCH /applications/{id}/status

8. 投递接口与评价/对比补充

8.1 投递接口(正确写法)

创建投递为 POST /applications,请求体 {"jobId": 123}(不要写成 POST /jobs/{jobId}/applications):

{ "jobId": 123 }

8.2 岗位八维评价

POST /jobs/{jobId}/reviews 可附加可选 dimensionScores(八个固定维度,值 1-5,可稀疏提交):

{ "rating": 4, "content": "岗位信息真实", "tags": ["团队氛围"],
  "dimensionScores": { "AUTHENTICITY": 5, "TEAM_ATMOSPHERE": 4 } }
  • 不传 dimensionScores 时旧接口完全兼容;
  • 评价列表/详情返回 dimensionScores;
  • 汇总 GET /jobs/{jobId}/reviews/summary 返回 dimensionSummary(INSUFFICIENT sampleCount<3 / SUFFICIENT >=3);
  • 详情见 docs/job-review-api.md。

8.3 岗位对比(公开)

GET /jobs/compare?ids=1,2,3(无需登录,2-3 个公开岗位),返回公开字段与公开评分。

9. Candidate Scoring(求职者评分体系)联调

规则版本固定 rulesVersion = "candidate-scoring-v0.1";八维 EDUCATION / INTERNSHIP / ABILITY / COMPLIANCE / STABILITY / GROWTH / INTEGRITY / ACTIVITY,默认权重各 12.50,参与计算维度重新归一化。

9.1 接口

接口 方法 角色 说明
/profile/me/candidate-scoring GET STUDENT 本人 综合评分卡(8 维 + overallScore + weights + status)
/profile/me/candidate-scoring/dimensions/{dimension} GET STUDENT 本人 单维度明细(subScores/reasons/dataSources);非法 dimension → 400
/profile/me/candidate-scoring/trend?months=6 GET STUDENT 本人 月度趋势(months 1–24,越界 400)
/company/candidates/{applicationId}/scoring GET COMPANY(岗位归属) 企业只读评分卡

9.2 权限矩阵

接口 未登录 STUDENT COMPANY ADMIN
学生 3 接口 401 ✅ 本人 403 403
企业评分 401 403 ✅(岗位归属) 403
  • 企业归属:companyRepository.findByOwnerUserId(jwtUserId) 无企业→403/2002;company.id != job.companyId→403/8004;application 不存在→404/8002。

9.3 企业响应脱敏字段(禁止展示)

企业评分响应仅含 applicationId / overallScore / status / dimensions[]{dimension,score,status} / generatedAt / rulesVersion;不得出现 studentUserId / candidateId / phone / email / resumeUrl / subScores / 内部日志。

9.4 状态与错误码

  • 维度状态:OK / ESTIMATED / INSUFFICIENT_DATA / PENDING;PENDING/INSUFFICIENT_DATA 的 score 为 null。
  • 综合卡状态:OK / PARTIAL / INSUFFICIENT_DATA / PENDING;无可计算维度时 overallScore=null。
  • 错误码复用:BAD_REQUEST(1001)、UNAUTHORIZED(2001)(未登录实际 401)、FORBIDDEN(2002)、APPLICATION_NOT_FOUND(8002)、APPLICATION_OWNER_MISMATCH(8004)。

9.5 前端注意

  • 学生端先调综合卡,按 status 决定是否渲染 overallScore;PENDING/INSUFFICIENT 维度显示"数据不足/待补充"。
  • 企业端评分卡仅用于候选人详情展示,勿透传任何候选人联系方式或简历地址。

10. 企业评分运营看板联调

全部只读聚合;候选人按学生去重,综合分复用评分引擎公式;岗位评价口径为「审核通过 + PUBLISHED」。详见 docs/company-scoring-dashboard-api.md。

10.1 接口

接口 方法 角色 说明
/company/scoring/dashboard/overview GET COMPANY 企业评分运营概览(岗位/评价/候选人统计)
/company/scoring/dashboard/job-rankings?page&size GET COMPANY 岗位评分排名(avgRating DESC, jobId DESC)
/company/scoring/dashboard/job-rankings/{jobId} GET COMPANY 单岗位评分排名明细(跨企业 400/7002)
/company/scoring/dashboard/candidate-rankings?page&size GET COMPANY 候选人评分排名(按学生去重,overallScore DESC)
/company/scoring/dashboard/candidate-rankings/{applicationId} GET COMPANY 单候选人评分排名明细(跨企业 403/8004)
/company/scoring/dashboard/trend?months=6 GET COMPANY 企业评分趋势(岗位评价 + 候选人综合分,按月)
/admin/scoring/dashboard/overview GET ADMIN 全局运营概览(全平台统计 + 企业 Top10)

10.2 权限矩阵

接口 未登录 STUDENT COMPANY ADMIN
企业看板 6 接口 401 403 ✅(本企业) 403
全局概览 401 403 403 ✅

10.3 关键口径

  • 候选人去重:同一学生多次投递视为 1 名候选人,代表投递取企业下 applicationId 最大一条;WITHDRAWN/REJECTED 排除。
  • 综合分:复用 CandidateScoringCalculator,读取候选人 <= 当前月 最新期快照计算;无可计算维度 → overallScore=null。
  • 岗位评价:status=PUBLISHED 且 moderation_status ∈ {AUTO_APPROVED, APPROVED}(与评价汇总口径一致)。
  • percentile:在有分候选人中计算(升序),无分 → null;sampleCount 为有分候选人数。
  • 分页参数:page(0 起)、size(1–100),months(1–24,默认 6)。

10.4 前端注意

  • 看板为只读统计,勿依赖实时刷新频次;趋势冷启动期通常只有当前月数据,随评分调用/评价累积逐渐补齐。
  • 候选人排名项不返回联系方式与简历地址,仅返回姓名/学校/综合分/百分位/维度分(维度分仅在详情接口)。
  • avgJobScore/avgCandidateScore/overallScore 无数据时返回 null,前端按「暂无数据」处理。

11. Offer 进阶(还价 / 对比 / 统计)联调

在基础 Offer 之上新增薪资洽谈、Offer 对比、企业统计,并扩展 WITHDRAWN 撤销状态。 完整字段/错误码见 docs/offer-advance-api.md。OpenAPI 总路径数 102 → 107。

11.1 接口

接口 方法 角色 说明
/offers/{id}/counter-offers POST STUDENT 本人 发起还价(SENT 时自动进入 NEGOTIATING)
/offers/{id}/counter-offers GET STUDENT 本人 / COMPANY(本企业) 还价列表
/offers/{id}/counter-offers/{coId}/accept PATCH COMPANY(本企业) 接受还价 → Offer 成交(ACCEPTED)
/offers/{id}/counter-offers/{coId}/reject PATCH COMPANY(本企业) 拒绝还价 → Offer 保持 NEGOTIATING
/offers/compare?ids=1,2,3 GET STUDENT 本人 对比本人 2-5 个非 DRAFT Offer
/offers/statistics?months=6 GET COMPANY 企业 Offer 统计(状态分布/接受率/响应时长)

11.2 还价要点

  • 仅 SENT/NEGOTIATING 可发起;候选人发起后 Offer 自动进入 NEGOTIATING,前端应刷新 Offer 状态。
  • 同一 Offer 同时最多一个 PENDING 还价;二次发起返回 409 OFFER_COUNTER_OFFER_EXISTS。
  • 企业 accept 后会更新 Offer 薪资/入职日期并置为 ACCEPTED;reject 后 Offer 仍为 NEGOTIATING,候选人可再次还价。

11.3 对比要点

  • ids 为逗号分隔且 2-5 个、全为正数;非法参数一律 400,前端需先本地校验避免请求失败。
  • 返回项附带 companyName;被比较 Offer 含 DRAFT 视为 403 OFFER_COMPARE_NOT_OWNER,故只选已发送/洽谈中 Offer。

11.4 统计要点

  • 仅企业可访问;months 1-24,越界按默认 6 处理。
  • acceptanceRate / avgResponseHours 无数据时为 null,前端按「暂无数据」处理;byStatus 恒含全部 7 个状态键。

11.5 撤销(WITHDRAWN)

  • 企业 PATCH /offers/{id}/status {"status":"WITHDRAWN"},仅 SENT/NEGOTIATING 有效;其他状态 409。
  • 撤销/过期会使该 Offer 的 PENDING 还价变为 EXPIRED,Offer 自身释放"唯一有效 Offer"槽位;企业端应刷新 Offer + 还价列表。

11.6 新增错误码(已并入统一映射)

错误码 HTTP 含义
8109 409 已存在 PENDING 还价
8110 404 还价不存在/不属于该 Offer
8111 409 还价非 PENDING
8113 409 Offer 已撤销/过期,还价失效
8114 400 Offer 状态不允许还价
8115 400 对比数量需 2-5 个
8116 403 对比含非本人/非有效 Offer

12. 学生个人资料 GET /profile/me 联调

12.1 基础信息

项 值
Base URL http://127.0.0.1:8080/api/v1
状态 已实现、已验收(运行时黑盒 18/18 PASS)
接口 GET /api/v1/profile/me
认证 必须携带 Authorization: Bearer <accessToken>
Token 获取 通过登录接口(POST /auth/login,开发环境为 Mock 验证码)获取 accessToken;文档不提供任何真实凭据
限流 当前未设置接口级限流

accessToken 有效期 24h。Token 只通过登录流程获得,切勿在文档、代码或日志中硬编码或打印真实 Token。

12.2 请求参数

  • 无 Path 参数:路径不含任何 {id} 等占位符。
  • 无 Query 参数:不读取任何查询串参数。
  • 无 Body:不解析请求体。
  • 不接受身份参数:身份仅从 JWT / SecurityContext 获取,userId、studentUserId 等入参一律忽略。
    • 例如请求 GET /api/v1/profile/me?userId=999,后端仍返回 当前 JWT 身份 对应的资料,userId=999 不会改变 JWT 身份。

12.3 角色权限说明

调用方 HTTP body.code body.message
STUDENT 200 200 ok
COMPANY 403 2002 forbidden
ADMIN 403 2002 forbidden
未登录 / Token 缺失 / 非法 / 过期 401 401 not authenticated

12.4 成功响应示例(有资料)

{
  "code": 200,
  "message": "ok",
  "data": {
    "id": 1,
    "userId": 2,
    "realName": "张三",
    "education": "本科",
    "school": "某某大学",
    "graduationYear": 2026,
    "resumeUrl": "/uploads/resumes/2/resume.pdf",
    "skills": "[Java, Spring Boot]",
    "createdAt": "2026-08-24T10:00:00"
  },
  "timestamp": "2026-08-24T10:00:00Z"
}

成功 code=200(不是 0)。null 字段不序列化。

12.5 空资料响应示例

{
  "code": 200,
  "message": "ok",
  "data": {},
  "timestamp": "2026-08-24T10:00:00Z"
}
  • data={} 是空对象,不是 null。
  • 有资料查询也不会返回 HTTP 404。
  • 不会自动创建 student_profiles 记录,不修改数据库。
  • 前端必须对 data 进行空对象判定(Object.keys(data).length === 0),而非仅判非 null。

12.6 401 响应示例

{
  "code": 401,
  "message": "not authenticated",
  "timestamp": "2026-08-24T10:00:00Z"
}

12.7 403 响应示例

{
  "code": 2002,
  "message": "forbidden",
  "data": null,
  "timestamp": "2026-08-24T10:00:00Z"
}

12.8 业务码说明表

场景 HTTP code message
成功 200 200 ok
未登录 / Token 无效 401 401 not authenticated
非 STUDENT 角色 403 2002 forbidden
  • 成功码是 200,不要写成 0。
  • 本接口未登录错误为 401,不要写成 2001。运行时认证入口实际返回 code=401,以运行时契约为准。

12.9 响应字段表(StudentProfileResponse,null 不输出)

字段 类型 是否返回 说明 示例
id Long 有资料时 学生资料 ID 1
userId Long 有资料时 当前登录用户 ID 2
realName String 可空 真实姓名 张三
bio String 可空 个人简介 —
location String 可空 所在地 —
education String 可空 学历 本科
school String 可空 学校 某某大学
graduationYear Short/Integer 可空 毕业年份(四位) 2026
resumeUrl String 可空 简历地址 /uploads/resumes/2/resume.pdf
skills String 可空 技能(字符串) [Java, Spring Boot]
createdAt date-time 有资料时 创建时间 2026-08-24T10:00:00

不返回 passwordHash、手机号、验证码、JWT、内部认证字段。

12.10 skills 特别说明

  • 当前类型为 String,不保证是合法的 JSON。
  • 前端不要无条件 JSON.parse(skills),解析失败会抛异常。
  • 示例 "[Java, Spring Boot]" 只是普通字符串内容,不代表数组表示法。
  • 后续若改为数组,必须作为独立版本或兼容字段变更处理,不能静默破坏现有消费方。
  • 本轮不修改 skills 类型,也不修改 PUT 语义。

12.11 resumeUrl 特别说明

  • 值可能是相对存储路径或文件存储地址,不保证是完整的 http/https URL。
  • 前端下载简历应调用 GET /api/v1/profile/me/resume,不要直接拼接 resumeUrl 做请求地址。

12.12 graduationYear 特别说明

  • 当前后端没有实现年份范围校验。
  • 文档不得虚构未实现的范围限制(如 1900–2100 之类)。
  • 前端若展示年份,可自行决定是否校验,但接口本身不做约束。

13. 候选人反馈响应脱敏

候选人反馈接口保持原有路径、请求、权限和错误码不变:

POST   /applications/{applicationId}/feedback
GET    /applications/{applicationId}/feedback
PUT    /applications/{applicationId}/feedback
DELETE /applications/{applicationId}/feedback

成功响应中的 data 仅包含以下业务字段:id、applicationId、rating、content、tags、createdAt、updatedAt。

学生只能查看自己的投递反馈;岗位所属企业可以创建、查看、修改和删除反馈;其他学生、其他企业和管理员不因用户 ID 巧合而获得访问权限。

响应示例:

{
  "code": 200,
  "message": "ok",
  "data": {
    "id": 1,
    "applicationId": 31,
    "rating": 5,
    "content": "沟通良好",
    "tags": ["沟通良好"],
    "createdAt": "2026-08-25T10:00:00",
    "updatedAt": "2026-08-25T10:00:00"
  }
}

前端不得依赖或透传以下字段;这些字段不会出现在反馈响应中:

companyUserId、studentUserId、phone、email、passwordHash、resumeUrl

candidate_feedback 数据表仍保留企业用户和学生用户关联字段供权限校验和评分聚合使用,但这些内部字段不属于 API 响应。CandidateFeedback 也不等同于结构化面试评分;面试评分将在独立模块中提供。

14. 隐私设置(Privacy Settings)联调

学生隐私设置用于控制简历可见性与联系方式可见性,仅 STUDENT 角色可用:

GET  /profile/me/privacy
PUT  /profile/me/privacy
  • 身份仅从 JWT 获取,不接受 userId / studentUserId / candidateId 等参数。
  • GET 无记录时返回默认对象(resumeVisibility=PRIVATE、contactVisible=false、version=0),不写库。
  • PUT 执行 upsert,同一学生始终一行;version 冲突返回 409 / 9201。
  • COMPANY / ADMIN → 403 / 2002;未登录 → 401。
  • 企业候选人详情/列表接口保持原脱敏白名单,不返回 resumeUrl 与联系方式,本轮不新增联系方式字段。

详见 docs/privacy-settings-api.md。

15. 账号注销(Account Deletion)联调

当前登录用户可对自己的账号发起注销申请、查询状态、在冷静期内取消,适用于 STUDENT / COMPANY / ADMIN:

POST /profile/me/deletion          # 申请注销(NONE → PENDING;PENDING 幂等 200;COMPLETED → 409/9301)
GET  /profile/me/deletion          # 查询状态(无记录返回默认 NONE,不写库)
POST /profile/me/deletion/cancel   # 取消注销(仅 PENDING 且未过期;PENDING → NONE)
  • 身份仅从 JWT 获取,不接受 userId / candidateId 等参数;未登录 → 401。
  • 状态机:NONE → PENDING → COMPLETED(终态),可在冷静期内取消回 NONE。
  • 冷静期:默认 30 天(app.deletion.cooling-days / DELETION_COOLING_DAYS 可配置)。
  • PENDING 写限制:冷静期内 POST/PUT/PATCH/DELETE 业务写操作返回 409 / 9302;GET、OPTIONS 及三个注销接口放行。
  • COMPLETED 旧 JWT:即使 JWT 未过期也拒绝普通业务请求(409 / 9303);登录时亦被阻止。
  • 脱敏:仅脱敏 users 表 PII(phone→deleted_{id}、email/passwordHash/avatarUrl 置空、nickname→deleted、is_active=false),不删除任何业务数据。
  • 原手机号禁止重新注册:注销完成后仅保存原手机号的 HMAC-SHA256 摘要(deleted_phone_hash,密钥 app.deletion.phone-hash-secret),不存明文;原手机号再次登录/注册返回 409 / 9303,且不会自动创建新账号。
  • 懒转换:到期 PENDING 首次 GET /profile/me/deletion 即返回 COMPLETED(独立事务重读最新记录),不会出现先 PENDING 后 COMPLETED 的不一致。
  • ADMIN:最后一个有效 ADMIN 不允许注销 → 409 / 9306。
  • 错误码 9301-9306(详见 docs/account-deletion-api.md)。

前端建议:发起注销与取消前先 GET /profile/me/deletion 确认当前状态;处于 PENDING 时应禁用业务提交类按钮并提示冷静期到期时间。

16. 个人数据导出(Personal Data Export)联调

这不是简历导出(简历导出走 /resume-export-tasks,两者独立)。个人数据导出把当前登录用户的全量业务数据异步打包成 ZIP 供本人下载,适用于 STUDENT / COMPANY / ADMIN:

POST /profile/me/data-export-tasks                     # 创建导出任务(异步,返回 PENDING)
GET  /profile/me/data-export-tasks?page=0&size=20      # 本人任务列表(倒序分页)
GET  /profile/me/data-export-tasks/{taskId}            # 本人任务详情(状态/大小等)
GET  /profile/me/data-export-tasks/{taskId}/download   # 下载已完成 ZIP(需 JWT)
  • 异步:创建后立即返回 PENDING;后台 Worker 打包完成后变为 COMPLETED 才可下载。
  • 下载需 JWT:下载地址不能脱离 JWT 单独访问;不提供公开 URL。
  • 文件有效期:默认 24 小时(app.data-export.file-ttl-hours),过期状态为 EXPIRED 且不可再下载。
  • 导出范围/脱敏:仅包含本人数据;不含他人手机号/邮箱/简历地址、不含 passwordHash/JWT/短信验证码/deletedPhoneHash、管理员内部审核字段、系统异常堆栈、服务器绝对路径与连接串。会话对方仅保留 participantId。详见 docs/personal-data-export-api.md。
  • 错误:未登录 401;访问他人任务 403/2002;任务不存在 404/9401;存在未完成任务 409/9402;文件未就绪 409/9403;已过期 409/9404;失败 409/9405;文件缺失 409/9406。
  • 注销限制:注销 PENDING 禁止创建(409/9302);注销 COMPLETED 所有导出接口返回 409/9303。

前端建议:创建后轮询任务详情直到 status=COMPLETED 且 downloadAvailable=true 再展示下载按钮;处于注销 PENDING/COMPLETED 时应隐藏「导出我的数据」入口。

17. 结构化面试评分(Structured Interview Score)联调

这不是 candidate_feedback(候选人反馈是另一模块,两者独立;本模块是结构化维度评分)。企业为已完成(COMPLETED)的面试填写并提交评分,学生仅能在提交后查看自己的评分与历史:

PUT  /interviews/{interviewId}/score              # 企业 owner 保存草稿(幂等更新,DRAFT)
GET  /interviews/{interviewId}/score              # 查看评分(企业 see DRAFT/SUBMITTED;学生仅 SUBMITTED)
POST /interviews/{interviewId}/score/submit       # 提交草稿 → SUBMITTED(不可逆)
GET  /interviews/{interviewId}/score/history      # 评分快照历史(无记录返回 200 空数组)
  • 五个维度:technicalScore、communicationScore、problemSolvingScore、roleFitScore、professionalismScore,每项 1-5。
  • 草稿:PUT 时五维可留空;重复 PUT 更新同一行(version 递增)。
  • 提交:五维全部非空,否则 400/1001;overallScore 由服务端按 (t+c+ps+rf+p)/5 计算(两位小数 HALF_UP),客户端不要传,传了也会被忽略。
  • recommendation(可选):STRONG_HIRE / HIRE / NEUTRAL / NO_HIRE,企业手动选择,不由 overallScore 推导。
  • 权限:写入仅岗位所属企业 owner(COMPANY);ADMIN 不可访问;STUDENT 不可写。学生只能看自己的 SUBMITTED,查看他人或 DRAFT 返回 403/8402。
  • 状态机:无评分 → DRAFT → SUBMITTED。SUBMITTED 后再次 PUT 409/9503;重复 submit 幂等返回原评分(不新增历史、不重复通知)。
  • 通知:提交成功仅通知目标学生,businessType=INTERVIEW,businessId=interviewId;草稿与重复提交不发通知;企业自身不通知;无 Push。
  • 错误码:9501 并发冲突(409)、9502 面试未完成(409)、9503 已提交(409)、9504 无评分(404);归属非法 8402、未被授权 2002、面试不存在 8401。
  • 脱敏:响应不返回 companyUserId/studentUserId/evaluatorUserId/手机号/邮箱。
  • 历史脱敏:GET /score/history 不返回 actorUserId,仅 actorRole。
  • 面试本身必须为 COMPLETED 才能评分(否则 409/9502)。

前端建议:企业端保存草稿用 PUT;「提交」用 POST submit 并在按钮上二次确认(不可逆);学生端仅在评分状态为 SUBMITTED 时展示。API 详见 docs/interview-score-api.md。

18. 同岗位竞争力排名(Competition Ranking)联调

学生查看自己在某个 已投递且为 PUBLISHED 岗位候选人池中的竞争力排名(匿名):

GET /jobs/{jobId}/competition-ranking   # 仅 STUDENT 且已投递该岗位
  • 权限矩阵:
    • 未登录 → 401(安全过滤器)
    • COMPANY / ADMIN → 403/2002
    • STUDENT 但未投递该岗位 → 403/2002
    • STUDENT 唯一投递为 WITHDRAWN 或 REJECTED → 403/2002:投递已退出候选人池,与 cohort 排除口径一致,不能再查看排名。
    • jobId <= 0 → 400/1001
    • 岗位不存在或状态非 PUBLISHED(如 DRAFT/CLOSED)→ 404/7001
  • cohort:该岗位非 WITHDRAWN、非 REJECTED 的所有投递;SUBMITTED/VIEWED/INTERVIEW/HIRED 计入;同一学生同一岗位由数据库唯一约束保证仅一条。无有效评分的候选人仍计入 cohortSize,但不参与百分位,排在末尾。
  • 综合分:复用 CandidateScoringCalculator 真实公式(仅统计 OK/ESTIMATED 维度,PENDING/INSUFFICIENT_DATA 不折算为 0),取每人 period <= 当前月 的最新快照。
  • 百分位:严格复用企业评分看板公式 lower / (scored - 1) * 100;有分候选人数 < 2 时 percentile = null;未取整为 2 位小数 HALF_UP。
  • 本人无有效评分:rank、overallScore、percentile 均为 null(仍返回 cohortSize)。
  • 响应结构(示例):
{
  "jobId": 9, "rank": 2, "cohortSize": 8, "overallScore": 77.50,
  "percentile": 66.67, "status": "PARTIAL",
  "asOfPeriod": "2026-08", "mySnapshotPeriod": "2026-08",
  "dimensions": [ { "dimension": "EDUCATION", "score": 80, "status": "OK", "weight": 12.50 } ],
  "gaps": [],
  "topCandidates": [ { "rank": 1, "overallScore": 88.50 } ],
  "rulesVersion": "candidate-scoring-v0.1", "generatedAt": "2026-08-26T10:00:00Z"
}
  • 脱敏:topCandidates 固定返回 Top 5、每项仅含 rank 与 overallScore,不含 studentUserId/candidateId/name/school/phone/email/resumeUrl/applicationId 等任何身份字段;响应整体也不含 candidateId。不提供 top 查询参数。
  • 只读:该接口为 GET,不写任何业务表;身份仅取自 JWT,不接收 userId/studentUserId/candidateId 参数。
  • 规则版本固定 rulesVersion = "candidate-scoring-v0.1";asOfPeriod 为计算截止月(当前月),mySnapshotPeriod 为当前学生实际使用的快照月(若与他人周期不同,二者可不同)。
  • API 详见 docs/competition-ranking-api.md。

19. 举报与屏蔽(Report & Block)联调

提供举报、屏蔽与 ADMIN 举报处理能力。身份一律取 JWT,不接受 userId/reporterId/reportedUserId 参数。

举报(用户端)

POST /reports                 # 提交举报
GET  /reports/me              # 我的举报列表(status/page/size)
GET  /reports/{id}            # 举报详情(本人或 ADMIN)
  • targetType=USER 必填 targetUserId;targetType=MESSAGE 必填 messageId。reportedUserId 由服务端推导,不传。
  • category ≤40 字符;reason 1–1000 字符;evidenceUrls 可选(≤5 个,仅 http/https,每个 ≤500 字符);idempotencyKey 可选(≤64 字符)。
  • 幂等:同 idempotencyKey + 相同请求体重放返回原举报 200;同 key 不同请求体 → 409/9606;同目标同分类已有 PENDING → 409/9603。
  • 限流:每用户每小时最多 10 次 POST /reports,超限 → 429/9609。
  • MESSAGE 举报:无权访问该消息 → 403/2002(不泄露消息是否存在);发送者由服务端推导,禁止客户端注入。
  • 举报自己 → 400/9602;不存在目标 → 复用用户不存在错误码。

屏蔽(用户端)

PUT    /blocks/{blockedUserId}   # 屏蔽
DELETE /blocks/{blockedUserId}   # 解除(幂等)
GET    /blocks/me                # 我的屏蔽列表(page/size)
  • 屏蔽自己 → 400/9608;PUT 已存在返回 200 不新增;DELETE 不存在返回 200 不报错。
  • 屏蔽仅影响站内信:禁止与对方新建 DIRECT 会话、禁止向对方发送新消息(403/2002,模糊提示“message cannot be sent”);历史消息仍可读,且不影响系统通知、投递、Offer、面试、评分、评价。

ADMIN 举报管理

GET  /admin/reports                  # 举报列表(status/targetType/category/page/size)
GET  /admin/reports/{id}             # 举报详情
POST /admin/reports/{id}/decision    # 处理决定
  • 仅 ADMIN;COMPANY/STUDENT/未登录分别 403/403/401。
  • decision 仅 RESOLVED/REJECTED;REJECTED 必填 note;重复决定 → 409/9604;非法流转 → 409/9605。
  • 处理不自动封禁、不删除消息、不修改投递/面试/评分状态;处理后向举报人发送一条 REPORT_DECISION 通知。

脱敏:所有举报/屏蔽响应均不返回 phone、email、passwordHash、resumeUrl、JWT、消息全文;MESSAGE 举报仅返回脱敏 messagePreview;举报列表不返回举报人内部 ID。

20. 统一求职进度聚合(Career Progress)联调

学生端「求职进度」页的只读聚合接口,一次返回投递分页、岗位摘要、下一场面试、最近已提交结构化面试评分、可展示 Offer、同岗位竞争力摘要,以及待办/通知计数:

GET /profile/me/career-progress?page=0&size=20&status=INTERVIEW   # 仅 STUDENT;page/size/status 均可选
  • 权限矩阵:
    • 未登录 → 401(安全过滤器)
    • COMPANY / ADMIN → 403/2002
    • page<0、size<1、size>50、非法 status → 400/1001
    • 身份只取 JWT,不接受 userId/studentUserId/candidateId 参数
  • 查询参数:page(默认 0,≥0)、size(默认 20,1–50)、status(可选,仅 SUBMITTED/VIEWED/INTERVIEW/HIRED/REJECTED/WITHDRAWN)。
  • 响应结构(示例,字段为 camelCase):
{
  "todo": {
    "pendingInterviewResponseCount": 1,
    "upcomingConfirmedInterviewCount": 2,
    "pendingOfferResponseCount": 1,
    "unreadNotificationCount": 3,
    "unreadReportDecisionCount": 1
  },
  "applications": {
    "items": [
      {
        "applicationId": 31, "applicationStatus": "INTERVIEW",
        "appliedAt": "2026-08-20T10:00:00", "updatedAt": "2026-08-25T12:00:00",
        "job": { "jobId": 9, "title": "Java 后端工程师", "companyName": "示例科技", "city": "杭州", "employmentType": "FULL_TIME", "jobStatus": "PUBLISHED" },
        "nextInterview": { "interviewId": 23, "roundNo": 2, "title": "技术二面", "interviewType": "ONLINE", "startAt": "2026-08-30T14:00:00", "endAt": "2026-08-30T15:00:00", "status": "CONFIRMED" },
        "latestSubmittedInterviewScore": { "interviewId": 22, "overallScore": 3.60, "recommendation": "HIRE", "submittedAt": "2026-08-25T16:00:00" },
        "offer": { "offerId": 14, "status": "SENT", "title": "Java 后端工程师", "expireAt": "2026-09-05T23:59:59", "onboardingDate": "2026-09-15" },
        "competition": { "eligible": true, "rank": 3, "cohortSize": 18, "overallScore": 77.50, "percentile": 82.35, "status": "PARTIAL", "mySnapshotPeriod": "2026-08", "rulesVersion": "candidate-scoring-v0.1" }
      }
    ],
    "total": 8, "page": 0, "size": 20, "totalPages": 1
  },
  "generatedAt": "2026-08-27T10:00:00Z"
}
  • 空数据:无投递 → items=[]、total=0、todo 全 0;无下一场面试/无已提交评分/无可展示 Offer 时,相应对象为 null(JSON 省略)。
  • competition:
    • 有效投递(SUBMITTED/VIEWED/INTERVIEW/HIRED)eligible=true;有评分快照才返回 rank/overallScore/percentile/mySnapshotPeriod,无快照则这些为 null 但仍返回 cohortSize。
    • WITHDRAWN/REJECTED 投递保留在历史,但 eligible=false 且不返回 rank/cohortSize/overallScore/percentile/status/mySnapshotPeriod/rulesVersion。
    • 复用 V32 同岗位排名的同一套 cohort/排序/百分位口径,但不返回 dimensions/gaps/topCandidates。
  • Todo 口径:pendingInterviewResponseCount=状态 SCHEDULED 面试数;upcomingConfirmedInterviewCount=状态 CONFIRMED 且 startAt>now;pendingOfferResponseCount=状态 SENT/NEGOTIATING 且 expireAt>now;unreadNotificationCount=未读通知总数;unreadReportDecisionCount=未读 REPORT_DECISION 通知数。
  • 脱敏:不返回 studentUserId/candidateId/companyUserId/evaluatorUserId/phone/email/resumeUrl/passwordHash/JWT、通知标题/正文、评分 comment、Offer 薪资/条款、他人任何身份字段。本接口只返回通知计数,通知详情仍用 /notifications。
  • 只读:GET,不写任何业务表;size 上限 50,聚合查询次数与单页条数无关(无 N+1)。
  • API 详见 docs/career-progress-api.md。