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.
9.6 KiB
9.6 KiB
双向匹配基础模块接口说明
职来 (mydreamjob) · 双向匹配基础流程(规则阈值版)与匹配后沟通解锁 基础路径:
/api/v1· 全部接口需Authorization: Bearer <accessToken>
1. 概述
企业与求职者围绕同一条投递 application 形成双向匹配,不依赖 AI 或综合算法:
candidateInterestScore(0-100):求职者对岗位的意愿分(人工录入,来源MANUAL_INTEREST), 不是教育/能力/诚信/算法综合分;未来算法服务可替换该分数来源。hrSuitabilityScore(0-100):企业对候选人的 HR 适配分(人工录入,来源HR_MANUAL)。companyRequiredCandidateScore(0-100,默认 60):企业设置的"最低意愿分"阈值。studentRequiredHrScore(0-100,默认 60):求职者设置的"最低 HR 适配分"阈值。
匹配成功条件:candidateInterestScore >= companyRequiredCandidateScore 且 hrSuitabilityScore >= studentRequiredHrScore。
匹配成功(状态 MATCHED)后:写入 matchedAt;双方各收到一次 MATCH_SUCCEEDED 通知;
双方可基于该 application 创建/获取直聊会话;本期不再允许修改双方评分。
未达标时状态为 NOT_MATCHED,双方可重提/改分,每次修改后重新计算。不做 7 天冷却期等额外限制。
2. 状态机
| 状态 | 含义 |
|---|---|
PENDING_BOTH |
双方都未评分 |
PENDING_HR |
求职者已评分,等待企业 HR 分 |
PENDING_CANDIDATE |
企业已评分,等待求职者意愿分 |
NOT_MATCHED |
双方已有评分但未同时达到阈值 |
MATCHED |
双方达到阈值(终态之一,评分锁定) |
CLOSED |
投递已撤回/拒绝、岗位关闭等,不再允许匹配(终态) |
重算规则(每次评分提交后执行):
- 两侧评分缺失 →
PENDING_BOTH;仅缺候选人分 →PENDING_CANDIDATE;仅缺 HR 分 →PENDING_HR; - 两侧都有且同时达标 →
MATCHED;否则 →NOT_MATCHED。 - 仅有效投递状态(
SUBMITTED/VIEWED/INTERVIEW)可评分;REJECTED/HIRED/WITHDRAWN或岗位CLOSED时评分为CLOSED并拒绝写入。 - 重复提交相同分数不产生重复事件/通知;并发评分经乐观锁 +
MATCH_SUCCEEDED事件唯一约束, 保证仅一次从非 MATCHED 进入 MATCHED。
3. 接口清单
3.1 企业匹配阈值
GET /companies/me/match-rule
PUT /companies/me/match-rule
- 角色:企业本人(COMPANY 且绑定企业);未配置读取返回默认
60;首次更新创建配置。 - PUT 请求:
{"requiredCandidateScore": 70}(0-100,非法值 400)。 - 响应:
{"companyId": 1, "requiredCandidateScore": 70}。 - 阈值更新只影响后续重新计算;匹配记录保留本次计算实际使用的阈值快照。
3.2 学生匹配阈值
GET /profile/me/match-rule
PUT /profile/me/match-rule
- 角色:学生本人;同上,PUT 字段为
requiredHrScore。
3.3 提交求职者岗位意愿分
PUT /applications/{id}/candidate-interest-score
- 角色:该投递的求职者本人(STUDENT);请求
{"score": 80}(0-100)。 - 前置:投递存在(404)、投递归属本人(403)、投递状态有效(否则 409
MATCH_CLOSED)、 匹配记录非 MATCHED/CLOSED(409MATCH_SCORES_LOCKED/MATCH_CLOSED)。 - 成功 200,返回匹配记录视图(状态、双分、阈值快照、matchedAt)。
3.4 提交企业 HR 适配分
PUT /applications/{id}/hr-suitability-score
- 角色:岗位所属企业(COMPANY,经
company_id归属校验);请求{"score": 80}。 - 权限/校验同上;他人企业 403。
3.5 查询投递匹配状态
GET /applications/{id}/match
- 角色:该投递求职者或岗位所属企业;其他 403,不存在 404。
- 响应(脱敏,不含内部路径/事件 ID):
applicationId, jobId, companyId, candidateId, status, candidateInterestScore, hrSuitabilityScore, companyRequiredCandidateScore, studentRequiredHrScore, candidateScoreSource, hrScoreSource, matchedAt, matchVersion。 - 无记录返回
PENDING_BOTH+ 当前阈值;投递终态/岗位关闭返回CLOSED。
3.6 匹配后创建/获取沟通会话
POST /applications/{id}/conversation
- 角色:该投递求职者或岗位所属企业;
application必须为MATCHED,否则 409APPLICATION_NOT_MATCHED。 - 幂等:已有对应会话则返回原会话;否则创建 DIRECT 会话并记录
application_match_conversations; 并发冲突返回既有会话。 - 响应:会话视图
{id, title, type, createdBy, createdAt, updatedAt}。
4. 权限与越权矩阵
| 接口 | 该投递求职者 | 岗位所属企业 | 其他企业 | 其他求职者 | 未登录 |
|---|---|---|---|---|---|
| 企业阈值 GET/PUT | 403 | ✅ | 403 | 403 | 401 |
| 学生阈值 GET/PUT | ✅ | 403 | 403 | 403 | 401 |
| candidate-interest-score | ✅(本人) | 403 | 403 | 403 | 401 |
| hr-suitability-score | 403 | ✅(本人岗位) | 403 | 403 | 401 |
| 匹配状态查询 | ✅(本人) | ✅(本人岗位) | 403 | 403 | 401 |
| 匹配会话创建 | ✅(且 MATCHED) | ✅(且 MATCHED) | 403 | 403 | 401 |
原则:身份一律来自 JWT,不信任请求体/查询参数中的 userId/companyId/studentId; 401 未登录;403 存在但越权;404 资源不存在;分数/阈值非法值 400。
5. 防绕过:旧 /conversations 收紧
- 现有
POST /conversations在新建会话时,若双方为"企业账号 + 求职者账号"组合, 必须存在一条同时归属双方且状态为MATCHED的投递,否则 403MATCH_REQUIRED_FOR_CONVERSATION。 - 已有 DIRECT 会话:仅允许原成员继续访问,不删除、不失效(命中既有会话直接返回,不校验)。
- 学生-学生、企业-企业及其他组合:保持原语义。
- 无法判定身份时(上游已校验存在性)防御性放行。
6. 通知与 RabbitMQ(Outbox)
- 首次进入
MATCHED时,求职者与企业各收到一次MATCH_SUCCEEDED通知。 - 通知内容含
applicationId、jobId、对方展示信息(企业名/岗位名、候选人姓名/昵称), 不暴露手机号、JWT、内部存储路径。 - 防重复三层保障:①
application_match_events对同一 application 的MATCH_SUCCEEDED事件唯一(dedup_key); ② 事件notification_status仅PENDING → SENT,发送后不再重发; ③ 通知幂等键match-succeeded-{applicationId}-{candidate|company}。 - 可靠发送:评分事务提交后
afterCommit立即发送;失败保持PENDING,由MatchNotificationScheduler(app.match.notification.enabled/retry-ms,默认 60s)重试;sendMatchSucceeded为REQUIRES_NEW独立事务,重试/并发不产生重复通知。 - 复用现有
NotificationService(幂等键落库),不新增队列、不清空任何 RabbitMQ 队列;不影响 Offer 通知语义。
7. 数据库迁移(V16)
src/main/resources/db/migration/V16__bidirectional_match.sql(兼容 MySQL 8 / H2 MODE=MySQL)。
| 表 | 关键字段/约束 |
|---|---|
company_match_rules |
company_id 唯一 FK→companies;required_candidate_score 0-100 默认 60 |
student_match_rules |
student_user_id 唯一 FK→users;required_hr_score 0-100 默认 60 |
application_match_records |
application_id 唯一 FK→applications;双分可空(0-100 CHECK)、阈值快照、评分来源、status、matched_at、version 乐观锁;索引(company_id,status)/(candidate_id,status)/(status,matched_at)/(job_id) |
application_match_events |
application_id+dedup_key 唯一(保证 MATCH_SUCCEEDED 仅一次);event_type/old_status/new_status/old_score/new_score/score_field/notification_status/request_id;索引(application_id,created_at)/(event_type,notification_status) |
application_match_conversations |
application_id 唯一;conversation_id FK→conversations;解锁会话幂等 |
不改任何既有表;/jobs/{jobId}/match 岗位匹配诊断、candidate_feedback、POST /conversations 语义保持不变。
8. Apifox 测试步骤
- 登录学生与企业账号(Mock 验证码)。
- 企业
PUT /companies/me/match-rule设阈值 70;学生PUT /profile/me/match-rule设 70。 - 学生投递岗位,记录 applicationId。
- 学生
PUT /applications/{id}/candidate-interest-score{"score":80}→ 200,状态PENDING_HR。 - 企业
PUT /applications/{id}/hr-suitability-score{"score":80}→ 200,状态MATCHED,matchedAt 非空。 GET /applications/{id}/match查看双方评分与快照。- 双方各收到
MATCH_SUCCEEDED通知(通知列表可查)。 POST /applications/{id}/conversation→ 创建会话;重复调用返回同一会话。- 再提分 → 409
MATCH_SCORES_LOCKED。 - 未匹配投递
POST /applications/{id}/conversation→ 409APPLICATION_NOT_MATCHED。 - 未匹配的企业+学生调旧
POST /conversations→ 403MATCH_REQUIRED_FOR_CONVERSATION。
9. 已知限制
- 评分与阈值均为人工录入(来源 MANUAL_INTEREST/HR_MANUAL),非算法评分;算法服务接入时替换写入方并升级
matchVersion/评分来源。 MATCHED后评分锁定;如需协商调整需先 CLOSED 或产品确认撤销规则。- 阈值更新只影响后续重算,匹配记录保留计算时点快照(可审计)。
- 通知在评分事务提交后发送,极端情况下依赖调度任务补发(默认 60s 周期)。
- OpenAPI 快照:
docs/openapi-rc.json由 H2 集成测试上下文导出(55 路径),现网可用GET /api/v1/v3/api-docs重新导出。
10. 错误码(新增 83xx)
| 错误码 | 枚举 | HTTP | 含义 |
|---|---|---|---|
| 8301 | MATCH_CLOSED |
409 | 投递终态/岗位关闭,匹配已关闭 |
| 8302 | MATCH_SCORES_LOCKED |
409 | 已 MATCHED,评分锁定 |
| 8303 | MATCH_REQUIRED_FOR_CONVERSATION |
403 | 企业-求职者建会话需匹配成功 |
| 8304 | APPLICATION_NOT_MATCHED |
409 | 投递未匹配,不可建会话 |