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

59 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. 本地获取测试验证码(不通过日志)

应用日志不会输出验证码,也不得从控制台 / stdout / 日志文件中读取验证码。

本地 dev/test 联调请使用受控方式取得验证码:

  1. 启动应用时显式开启 Mock 短信:SMS_MOCK_ENABLED=true(即 app.sms.mock.enabled=true)
  2. 显式授权本次执行读取验证码表:ALLOW_TEST_OTP_DB_READ=true
  3. 通过受控测试脚本或数据库内部只读查询取得验证码;脚本内部取码,不向终端展示

docs/smoke-test.sh 已按上述双重保护实现;两项条件任一不满足即输出 test OTP retrieval is disabled 并安全失败。

约束:

  • 生产环境禁止查询验证码表辅助登录
  • 当前尚未接入真实短信供应商;app.sms.mock.enabled 为 false、缺失、空值或非法值时,发码返回“服务不可用”,绝不返回虚假成功
  • 验证码当前仍以明文存储于 sms_verification_codes,属已知遗留风险(未加密、未哈希)

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. 验证码不再输出到日志;本地按 §3 的受控方式取得(生产禁止查表)
  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,参与计算维度重新归一化。

V38 联调(功能开关 scoring.ability-assessment.enabled,默认 false):开关开启后规则版本为 "candidate-scoring-v0.2",ABILITY 可由最新有效四维能力测评(WORKPLACE_ABILITY、COMPLETED、答题 24/24、valid_until > capturedNow)映射为 OK 并参与综合分。学生 ABILITY 单维明细的 subScores[] 为四条白名单聚合分(逻辑能力/35、执行能力/30、学习能力/20、沟通能力/15),不含任何答案/答卷/来源结果 ID;企业评分卡对 ABILITY 仍仅暴露聚合 score/status。后端本地验证已通过,真实 MySQL/Docker 黑盒待负责人验收。

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 做请求地址。
  • 上传仍仅接受 PDF/DOC/DOCX 且不超过 10MB;服务端会同时校验扩展名、Content-Type 和实际文件结构。伪装扩展名、损坏文件、危险 DOCX 压缩包或含路径/控制字符的文件名返回 400,前端不要仅依据浏览器 MIME 判断文件有效。
  • 上传替换失败时原简历保持可用;成功提交后旧文件才会清理。该流程不改变现有请求字段或响应结构。

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。

21. RIASEC 职业兴趣测评(Assessment)联调

基于 RIASEC 理论的自研题库(72 道),仅供职业兴趣探索参考,不构成心理或职业诊断。结果不写入求职者评分引擎。

接口(身份一律取 JWT,不接受 userId 参数)

GET  /assessments/types                                # 测评类型列表(任意登录用户)
GET  /assessments/HOLLAND_RIASEC/questions             # 题目列表,仅 ACTIVE(隐藏 dimension/reverseScored/source/版本/questionCode)
POST /assessments/attempts                             # 创建答卷(六维各抽 5 题共 30 道)【仅 STUDENT,须 ACTIVE】
GET  /assessments/attempts/{attemptId}                 # 答卷详情(固定题目顺序+已答)
PATCH /assessments/attempts/{attemptId}/answers        # 保存答案(value 1-5,同请求内同题不可重复,upsert)
POST /assessments/attempts/{attemptId}/submit          # 提交(30 题全答)返回结果
GET  /assessments/attempts/{attemptId}/result          # 结果(六维聚合分+排名+探索方向)
GET  /assessments/me?page=0&size=20                    # 我的测评历史

要点

  • 类型 ACTIVE:题目列表与创建答卷均要求类型为 ACTIVE;INACTIVE/不存在统一 404/9701。
  • 题目响应:不返回任何可推导维度或内部编码的字段,dimension/reverseScored/source/questionVersion/questionCode 一律不下发;前端只应使用 id/stem/scaleType。
  • 创建答卷:返回 30 道题及 version;每次创建随机抽题并持久化,刷新页面 / 再次 GET 不重新抽题。六维各恰好 5 题、总数严格 30;题库任一维度不足 5 道时拒绝创建。
  • 保存答案:请求带 version(乐观锁),并发/版本过期 → 409/9707;题目不属于本答卷 → 400/9705;同一请求内同题重复出现 → 400/9705;同一题跨请求重复保存为 upsert。首次保存后状态变为 IN_PROGRESS。
  • 提交:answeredCount<30 → 400/9706;30 题提交生成结果并写 ASSESSMENT_ATTEMPT_SUBMITTED 审计;重复提交幂等返回原结果、不重复生成/审计(原子流转 + attempt_id 唯一约束,并发提交只生成一条结果/一条审计);不写入 candidate_dimension_scores。
  • 越权:答卷题目、已保存答案与结果均仅本人 STUDENT 可读;COMPANY/ADMIN 越权读取一律 403/2002。
  • 状态机:DRAFT → IN_PROGRESS → COMPLETED;COMPLETED 不可改;同一学生同一类型最多一份活跃答卷(409/9708,由 active_key 唯一约束并发原子保证);并发创建竞争失败返回 409/9708(不返回 500);同一 idempotencyKey 并发重放返回原 attempt(幂等 200);完成后允许重新测评。
  • 计分:overallScore 六维按 percentage = raw/25×100(HALF_UP scale=2)降序,同分按维度字母升序;前三组成 primaryCode。
  • 结果脱敏:不返回 studentUserId/phone/email/password/原始答案/企业信息/他人信息;探索方向使用"可能感兴趣的职业探索方向"文案,不使用"适合/精准/最适合/诊断"表述。
  • 探索方向:career-direction-v0.1,精确三字母优先,未配置按前三个维度的兜底方向。
  • 错误码:9701(404)/9702(404)/9703(409)/9705(400)/9706(400)/9707(409)/9708(409)/9709(409)。
  • 数据表:新增 assessment_definitions / assessment_questions / assessment_attempts / assessment_attempt_questions / assessment_answers / assessment_results 6 张表(V34 迁移)。
  • API 详见 docs/riasec-assessment-api.md。

22. 四维能力测评(Ability Assessment)联调

功能版本 V36。学生四维能力自评(逻辑 / 沟通 / 执行 / 学习),每次按每维抽 6 题共 24 题,限时 30 分钟。企业端不可查看学生测评结果;本版不写入候选评分引擎。API 详见 docs/ability-assessment-api.md。

22.1 接口(7 个)

GET   /abilities/types                                     类型列表(任意登录用户,仅 ACTIVE)
POST  /abilities/attempts                                  创建答卷(仅 STUDENT)
GET   /abilities/attempts/{attemptId}                      答卷详情/恢复(仅本人)
PATCH /abilities/attempts/{attemptId}/answers              保存/修改答案 upsert(仅本人)
POST  /abilities/attempts/{attemptId}/submit               提交(24/24 且四维各 6,仅本人)
GET   /abilities/attempts/{attemptId}/result               结果与每题解析(仅本人)
GET   /abilities/me?page=0&size=20                         我的历史分页
  • 不提供 GET /abilities/{type}/questions;题目只在人类创建答卷后随 GET /abilities/attempts/{attemptId} 返回。

22.2 创建与恢复

  • POST /abilities/attempts 传 {typeCode:"WORKPLACE_ABILITY", idempotencyKey?};同幂等键重放返回原答卷,不重抽题。
  • 创建仅进入 DRAFT,startedAt/expiresAt 为空、remainingSeconds=null,不开始计时。
  • 同学生同类型同时至多存在一份 DRAFT/IN_PROGRESS 活跃答卷;再创建返回 9808。
  • 刷新或重进页面,用 GET /abilities/attempts/{attemptId} 恢复:题目顺序固定、返回已选 selectedOptionId,不重新抽题。
  • 返回字段:attemptId/status/answeredCount/totalQuestions(24)/completionRate/startedAt/expiresAt/remainingSeconds/questions[{attemptQuestionId,position,itemType,stem,options[{optionId,text}],selectedOptionId}]。

22.3 首次保存启动 30 分钟

  • 第 1 个有效答案保存时一次性设置 startedAt 与 expiresAt=startedAt+30分钟,status 转 IN_PROGRESS 并返回 remainingSeconds。
  • 后续保存不延长倒计时。同一 PATCH 内重复 attemptQuestionId 或非法 optionId → 整体拒绝 9805,不启动计时。
  • 超时后保存/提交一律按 9803 拒绝;读取时自动把超时答卷标为 EXPIRED 并释放活跃占位(active_key 置空)。

22.4 提交条件

  • 必须 24/24 且四维每维各 6 答满才能提交,否则 9806(产品「完成率 ≥70%」仅示意,不做提交门槛)。
  • 提交原子推进 DRAFT/IN_PROGRESS→COMPLETED;重复/并发提交幂等返回原结果,只生成 1 条结果与 1 条提交审计。

22.5 7 天有效期 / 9810 冷却 / 9811 90 天限制

  • 提交生成结果:validUntil=completedAt+7天;validityStatus 取 VALID/EXPIRED(动态判断)。
  • 有效结果存续期内禁止重测 → POST /abilities/attempts 返回 9810(409)。
  • 滚动 90 天内最多完成 3 次;达上限 → 9811(429)。EXPIRED 超时答卷不计入完成次数。
  • 历史答卷与结果永久保留,可随时分页查询 GET /abilities/me。

22.6 结果字段(白名单,前端可直接展示)

  • dimensionScores[{code,score,answeredCount}](LOGIC/COMMUNICATION/EXECUTION/LEARNING 百分制)、overallScore、weaknesses[{code,score}](并列最低维度全返回)、radar[{code,value}]、improvementAreas[{dimension,currentScore,summary,actions}]、questionFeedback[{attemptQuestionId,position,stem,selectedOptionId,awardedScore,maxScore,explanation}]、validityStatus/validUntil/answeredCount/completionRate/questionSetVersion/rulesVersion/generatedAt/disclaimer。
  • improvementAreas 只含 dimension/currentScore/summary/actions,不返回 competencyTag。

22.7 脱敏字段(禁止向学生/前端返回)

  • 题目与结果均不得返回:questionCode、competencyTag、answerKey、scoringRule、source、difficulty、discrimination,以及 studentUserId/phone/email/password 等身份字段。
  • questionFeedback 允许题干、用户所选项、得分与解析文本,但绝不返回正确选项字母或完整评分规则。

22.8 错误码(V36,98xx)

码 含义 HTTP
9801 类型不存在/未启用 404
9802 答卷不存在 404
9803 答卷状态非法(含超时/已提交) 409
9805 答案非法(跨答卷/同题重复/非法选项) 400
9806 未答满 24/24 或某维不足 400
9807 并发保存冲突 409
9808 已有活跃答卷 409
9809 幂等冲突 409
9810 有效结果冷却期内不可重测 409
9811 90 天完成次数达上限 429
复用 1001 参数/业务非法、2001 未登录、2002 无权限 400/401/403

22.9 前端注意

  • 学生每次答卷题目固定顺序且数量恒为 24;恢复页只渲染 questions 即可,勿依赖外部题库接口。
  • 计时用后端 remainingSeconds 展示(创建阶段为 null);到期后结果不生成,提示重测需先创建新答卷。
  • 结果页必须展示 disclaimer 文案(自我了解与提升参考,不构成职业资格/招聘结论/永久标签)。

23. 防刷风险概览与运营规则配置(Risk Governance)联调

功能版本 V37。仅 ADMIN 使用,把既有拒绝型安全审计(REPORT_RATE_LIMITED/BLOCKED_SEND_PROHIBITED)转为可聚合的风险事件并支持规则配置与人工标记。不自动封禁/限流,不改动用户、投递、消息、评分状态。API 详见 docs/risk-governance-api.md。

23.1 接口(7 个,仅 ADMIN)

GET   /admin/scoring/risk/overview                 风险概览(OPEN 总数/等级分布/规则分布/最近检测)
GET   /admin/scoring/risk/trend?bucket=HOUR|DAY    风险趋势(SQL 聚合)
GET   /admin/scoring/risk/distribution             按规则×等级分布
GET   /admin/scoring/risk/events                   事件分页(status/severity/ruleCode/from/to)
GET   /admin/scoring/risk/rules                    规则列表
PUT   /admin/scoring/risk/rules/{ruleCode}         更新规则(expectedVersion 乐观锁)
POST  /admin/scoring/risk/events/{eventId}/actions 人工标记(ACKNOWLEDGE/DISMISS)
  • 身份一律 JWT/SecurityContext;不接受 adminUserId/operatorUserId/userId 参数。
  • 未登录 401;STUDENT/COMPANY 403/2002;仅 ADMIN 可访问。

23.2 时间与分页

  • 默认时间范围近 7 天,最大 90 天;from>to 或超范围 → 9906。
  • page 从 0 起、size 1–50;trend.bucket 仅 HOUR/DAY。

23.3 规则配置

  • 更新请求:{"enabled":true,"thresholdCount":3,"windowMinutes":15,"severity":"MEDIUM","expectedVersion":2}。
  • 不允许修改 ruleCode/sourceAction;expectedVersion 不匹配 → 9903(409)。
  • 规则只影响后续扫描;禁用规则对审计照常越过 checkpoint,重新启用不回放历史。

23.4 人工标记与状态机

  • 事件状态机:OPEN→ACKNOWLEDGED、OPEN→DISMISSED、ACKNOWLEDGED→DISMISSED;DISMISSED 终态。
  • 操作请求:{"action":"ACKNOWLEDGE","reasonCode":"MONITORING","expectedVersion":0}。
  • reasonCode 仅 MONITORING/CONFIRMED_FALSE_POSITIVE/OUT_OF_BAND_ESCALATED,无自由文本备注。
  • 冲突/非法流转 → 9904/9905(409)。

23.5 响应脱敏(ADMIN 可见字段白名单)

  • 事件响应仅:eventId/ruleCode/sourceAction/severity/status/occurrenceCount/windowStartedAt/windowEndedAt/detectedAt/version。
  • 严禁返回:subjectUserId、手机号、邮箱、ipAddress、userAgent、details、requestId、token/password/secret 等。运营仅用 eventId 定位,不做自动处置。

23.6 错误码(V37,99xx)

码 含义 HTTP
9901 规则不存在 404
9902 事件不存在 404
9903 规则并发更新 409
9904 事件并发操作 409
9905 非法 action/reasonCode/流转 409
9906 非法筛选(时间/bucket/severity/status) 400
复用 2001 未登录、2002 无权限、1001 分页/参数非法 401/403/400

23.7 前端注意

  • 风险入口仅在管理后台;学生/企业端无任何风险接口。
  • 规则/阈值的命中可能不会立刻出现:扫描默认每 60 秒跑一次(app.risk.scan.fixed-delay-ms)。
  • 概览/趋势/分布由后端 SQL 聚合返回,前端无需额外拉取明细。

24. V40 企业评价管理与官方回复

验收状态:真实 MySQL/Docker 黑盒已通过(PASS=62 FAIL=0 SKIP=0);OpenAPI 159/196,待 Git 检查点。

V41 企业人才发现

新增接口详见 enterprise-talent-discovery-api.md。学生先通过 /profile/me/talent-discovery 显式开启授权;企业仅能查询已认证企业自己的已发布岗位,岗位需具备 V39 审核通过且启用的画像。候选卡只返回不透明 candidateKey、匿名标签、城市、学历、毕业年份、白名单技能、匹配档位、固定概述和可用信号类别,不返回学生身份、联系方式、简历或任何测评分数。

V41 服务端先对全部合格候选计算并全局排序,再切分页;fitScore 不下发。无可用岗位画像返回 UNAVAILABLE,无候选返回 NO_ELIGIBLE_CANDIDATE。OpenAPI 实际为 161 paths / 199 operations(学生 GET/PUT 共用一个 path)。

V42 企业人才邀约与授权沟通

V42 新增 6 个 path / 6 个 operation,静态 OpenAPI 计数为 167 paths / 205 operations。已认证企业只能对本公司 PUBLISHED 且画像 enabled=true && APPROVED 的岗位使用 V41 candidateKey 创建邀约;请求只提交 candidateKey、站内 message(1–300 字)和 UUID idempotencyKey,不得提交学生 ID。服务端会重新校验授权、账号状态、当前企业全部历史投递、有效推荐信号和每日限额。

验收状态:自动化测试 1475/1475 通过;真实 MySQL/Docker 黑盒通过(PASS=76 / FAIL=0),运行时与静态 OpenAPI 均为 167 paths / 205 operations。

学生通过 /profile/me/talent-invitations 查看邀约,详情中的 version 必须原样带入决策请求的 expectedVersion。只有 PENDING 可 ACCEPT 或 REJECT;接受成功才返回 conversationId 并建立受限 TALENT_INVITATION 会话,拒绝或过期不建会话。邀约有效期 7 天,终态后同企业同学生冷却 30 天;重复幂等请求返回原邀约,不重复占用额度。

企业响应不包含 candidateKey、studentUserId、测评/评分详情、联系方式或内部原因;邀约会话的会话、消息列表和发送响应只返回 CURRENT_USER/OTHER_PARTICIPANT 相对身份,不暴露 createdBy/senderUserId。V42 不写 candidate_dimension_scores,不发送通知。

验收状态:自动化测试 1461/1461 通过;真实 MySQL/Docker 黑盒已通过(PASS=37 / FAIL=0)。黑盒确认最新低分测评优先于历史高分、同企业任一历史投递均排除、其他企业投递不误排,并验证全局排序在分页之前完成。

24.1 接口

GET  /companies/me/job-reviews
GET  /companies/me/job-reviews/{reviewId}
POST /companies/me/job-reviews/{reviewId}/reply
PUT  /companies/me/job-reviews/{reviewId}/reply
GET  /admin/review-replies/pending
POST /admin/review-replies/{replyId}/decision

企业接口只返回当前登录企业自有岗位评价;身份从 JWT 获取,不传 companyId、studentUserId。STUDENT/ADMIN 为 403/2002,未登录为 401,跨企业访问不泄露评价是否存在。

24.2 回复状态与操作

  • 创建回复正文 1–500 字符,idempotencyKey 必须为 UUID(最长 64);安全文本为 AUTO_APPROVED,风险文本为 PENDING。
  • 同评价最多一个当前回复;同幂等键同评价同正文重放返回原回复,内容不一致为 409/10106,已有回复为 409/10102。
  • 只有 REJECTED 回复可以 PUT 修订,必须携带 expectedVersion;AUTO_APPROVED、APPROVED、PENDING 不可覆盖或删除。版本冲突为 409/10104。
  • ADMIN 仅可对 PENDING 执行 APPROVE 或 REJECT;驳回时 reason 必填(1–500 字符)。重复决策为 409/10103,非法决策为 400/10107。

企业列表/详情的 reply 只包含 id/content/status/version/updatedAt/rejectionReason。禁止依赖 studentUserId、学生姓名、联系方式、IP/UA、moderationScore、内部 reasonCodes 或管理员 ID。

24.3 公开评价与官方回复

既有 /jobs/{jobId}/reviews 与 /jobs/{jobId}/reviews/{reviewId} 的评价载荷不再包含 studentUserId,可显示固定 reviewerLabel="匿名求职者"。仅公开评价且回复状态为 AUTO_APPROVED/APPROVED 时显示:

{"officialReply":{"content":"感谢反馈。","repliedAt":"2026-09-04T10:05:00"}}

officialReply 不含回复 ID、公司 ID、审核状态、版本、原因码或审核人信息;PENDING/REJECTED 不渲染该区域。前端应兼容字段缺省或 null。

24.4 管理员页面

待审列表可按公司、岗位、风险等级和时间筛选;管理员响应中的风险等级/原因码只用于后台审核,不得透传到公开评价。V40 不提供回复通知、统计、申诉或公开历史接口。

24.5 错误码

10101 回复不存在(404)、10102 已有回复(409)、10103 状态非法(409)、10104 并发冲突(409)、10105 正文/幂等键非法(400)、10106 幂等冲突(409)、10107 决策非法(400)。

24. V39 测评驱动岗位推荐(编码中)

  • 开关 RECOMMENDATION_ASSESSMENT_ENABLED 默认 false:关闭时 GET /profile/me/job-recommendations 仅返回 mode=FALLBACK 的最新公开岗位,不展示伪造的测评匹配分。
  • 开启后按学生偏好 40%、RIASEC 聚合分 35%、四维能力聚合分 25% 做实时规则计算;缺少任一数据源时对可用项重归一化。推荐不读取题目、答案或候选评分快照。
  • 新增公开 GET /job-recommendation-taxonomies;企业通过 GET/PUT /companies/me/jobs/{jobId}/recommendation-profile 选择平台受控行业/岗位族并填写画像。更新启用画像后状态为 PENDING_REVIEW,仅 ADMIN 审核通过的启用画像参与个性化推荐。
  • 学生接口:GET /profile/me/job-recommendations?page&size;POST /profile/me/job-recommendations/{jobId}/feedback;DELETE .../feedback?expectedVersion=。NOT_INTERESTED 会持久化排除该岗位;解释反馈不影响首版分数。
  • ADMIN:GET /admin/job-recommendation-profiles、POST /admin/job-recommendation-profiles/{jobId}/decision。企业端不得取得任何学生分数或推荐理由;学生端不得取得画像权重、测评结果 ID 或题库字段。

25. V43 候选评分公平性与稳定性监测

V43 仅供管理员后台使用,前端不得向学生或企业展示。当前状态为版本内补集定向修复完成、待独立复验;总览和申诉使用最终 SQL 聚合,并统一执行独立候选人数与补集抑制。drift 和 ranking-stability 在当前没有不可变评分历史与排名快照时返回 availability=UNAVAILABLE,页面应显示“暂无可比历史数据”。

27. N07 消息撤回与会话一致性

消息发送者可在发送后两分钟内调用 POST /conversations/{conversationId}/messages/{messageId}/recall 撤回本人消息。请求不接收用户 ID;服务端从登录身份、会话成员关系和消息发送者校验归属。窗口边界采用 capturedNow <= createdAt + 2 minutes,恰好两分钟仍允许,超过窗口返回 409。非成员、非原发送者、管理员和跨会话访问均按既有 403/2002 语义处理。

成功撤回后,消息状态为 RECALLED,正文统一显示“消息已撤回”,并保留原时间线位置;响应、消息列表、发送响应和审计均不再返回原正文。重复撤回幂等返回当前撤回状态,不重复写审计。撤回消息不计入未读数,markRead 不为撤回消息新增已读记录。DIRECT 会话保持既有 senderUserId 语义,TALENT_INVITATION 会话继续只返回 CURRENT_USER/OTHER_PARTICIPANT 相对身份。完整接口契约见 messaging-api.md。

26. N02 候选评分规则解释与合理性

学生评分卡和单维详情在保持原有路径不变的前提下增加固定解释字段。综合卡新增 explanationVersion=candidate-scoring-explanation-v1、coverage(实际参与 overallScore 的维度及排除原因)和 comparabilityNotice;单维详情新增中性 displayName、whatItMeasures、interpretation、limitations、evidenceLevel、ruleContributions、improvementActions、explanationVersion、validity(仅 ABILITY)和 calculatedAt。

ruleContributions 与真实评分分支同源,贡献之和可对账最终维度分数;固定模板不使用 AI/LLM/SHAP。dataSources 只显示“个人教育资料”“求职偏好”“平台求职活动”“简历与技能资料”“Offer 履约与候选反馈”“四维能力测评”等产品语义名称,不展示表名、字段路径、题目、答案、答案键或评分规则。INTEGRITY、GROWTH、STABILITY、COMPLIANCE、ACTIVITY 等信号均明确为平台资料/行为代理,不代表人格、信用、成长潜力、服从性、工作积极性或录用结论。

企业评分接口 /company/candidates/{applicationId}/scoring 的字段和 JSON 结构保持不变,企业不返回任何解释字段、行为细分、规则贡献、改进建议或 ABILITY 四维详情。N02 不新增 Flyway、表、ErrorCode、公网路径、消息或审计动作,也不改变分数、权重、排名、推荐、申诉和快照写入语义。

可用接口、参数和小样本规则见 candidate-scoring-fairness-api.md。前端不得根据城市、学校、姓名等字段创建敏感人群筛选,也不得尝试通过多次小样本查询反推出候选人。

总览、维度和申诉响应都显式返回 availability/reason。状态/版本/回滚原因及分布桶均按独立候选人数计算;appealCount 仅表示事件数。SUPPRESSED 时不得用总体计数、补集或其它接口字段反推出被隐藏比例,NO_DATA、INSUFFICIENT_SAMPLE、SUPPRESSED 均不得渲染为 NORMAL。