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

17 KiB

V41 企业人才发现推荐——详细设计(编码前评审)

状态:已实现并通过自动化与真实 MySQL/Docker 黑盒验收。自动化测试 1461/1461 通过,真实黑盒 PASS=37 / FAIL=0。本文是 V41 的唯一设计基线。

1. 目标与边界

1.1 目标

让已认证企业针对自己已发布的岗位,发现主动开启人才发现的学生候选人。系统用已批准的岗位画像和学生自愿提供的聚合信号做规则排序,返回数据最小化的候选人卡片。

这是一项“人才发现”能力,不替代投递、面试、Offer 或现有企业候选人看板:

  • 现有 /company/candidates/** 只展示已经投递的候选人;
  • V41 只展示尚未向当前企业投递、且显式同意被发现的候选人;
  • V41 不创建投递、不发送消息、不创建邀约,也不让企业获得联系方式或简历文件。

1.2 确认范围

  1. 学生人才发现默认关闭,学生可随时自行开启或关闭;
  2. 只有 verified=true 的 COMPANY,才可对其自有、PUBLISHED 的岗位查询;
  3. 仅已启用且 ADMIN 审核通过的 V39 岗位画像可以计算推荐;
  4. 复用 V39 的规则信号权重:学生偏好 40%、RIASEC 聚合兴趣 35%、四维能力聚合 25%;
  5. 已向当前企业任一岗位投递过的学生均从候选池排除,包含已撤回或已拒绝的历史投递,避免重复触达;
  6. 公司只得到匹配档位与安全概述,不得到 RIASEC、四维能力、候选评分、答卷、答案、题目或原始权重;
  7. 本版没有企业私信、邀约、收藏/淘汰反馈、企业端联系方式、学生被动通知、AI 解释或推荐历史快照。

1.3 明确不做

  • 不改变 V35/V36 的测评题库、提交、重测、有效期或审计语义;
  • 不改变 V38 的候选评分写入、排名和企业评分卡;
  • 不改变 V39 的“学生找岗位”推荐及其岗位画像审核流;
  • 不改变现有 resumeVisibility / contactVisible 的含义;
  • 不按性别、年龄、籍贯、手机号、邮箱、学校排名、姓名或头像作为排序信号;
  • 不生成“录用概率”“胜任结论”“适合/不适合”之类确定性结论。

2. 现状核查与设计前提

项目 现状 V41 处理
最新迁移 V38 为企业评价官方回复 V41 建议下一编号 V39__enterprise_talent_discovery.sql
V39 岗位画像 job_recommendation_profiles,仅 enabled=true 且 APPROVED 可用 作为岗位侧唯一的结构化推荐输入
学生偏好 student_preferences 含城市、行业、岗位类型、用工类型、远程与薪资期望 只用于内部偏好匹配,不原样返回企业
兴趣测评 V35 RIASEC 聚合结果 仅可作为内部聚合信号,绝不下发维度分
能力测评 V36 完整且未过期的聚合结果 仅可作为内部聚合信号,绝不下发维度分
简历隐私 resumeVisibility 只控制简历可见性,默认 PRIVATE 不复用为人才发现授权;V41 另设明确授权
企业候选人看板 仅已投递的 application V41 只发现未向本企业投递过的候选人

resumeVisibility 与“是否允许被人才发现”是不同目的的授权:前者控制简历,后者控制是否进入企业发现候选池。两者不可混用或隐式推导。

3. 角色、权限与隐私

3.1 权限矩阵

能力 STUDENT COMPANY(未认证) COMPANY(认证且岗位所属) ADMIN 未登录
查询/更新本人人才发现开关 ✓ 403/2002 403/2002 403/2002 401
查询岗位人才推荐 403/2002 403/2002 ✓ 403/2002 401

所有身份从 SecurityContext.getAuthentication().getName() 的 JWT subject 推导。请求中不得接受 companyId、studentUserId、candidateId 或任何越权身份参数。

3.2 候选人可见性规则

一个学生进入某企业某岗位的候选池,必须同时满足:

  1. 用户角色为 STUDENT,账号未进入注销终态;
  2. 存在 student_talent_discovery_preferences 且 enabled=true;无记录一律视为关闭;
  3. 存在可用于候选卡的基础学生档案;
  4. 与当前公司不存在任何历史 applications 记录;
  5. 当前 COMPANY 已认证,目标岗位归属该 COMPANY 且为 PUBLISHED;
  6. 目标岗位有 APPROVED + enabled=true 的 V39 岗位画像。

关闭开关后,后续查询立即不再返回该学生;不删除其档案、投递、测评、评分或审计历史。V41 首版不保存企业查询历史与候选人曝光历史,避免把“被浏览”固化为可追踪个人行为数据。

3.3 响应白名单

企业候选卡仅允许:

{
  "candidateKey": "tdc_opaque_token",
  "candidateLabel": "开放人才候选人",
  "city": "合肥",
  "education": "本科",
  "graduationYear": 2027,
  "skills": ["Java", "Spring Boot"],
  "fitBand": "HIGH",
  "summary": "岗位画像与候选人公开资料存在较高匹配度",
  "signalsAvailable": ["PREFERENCE", "RIASEC", "ABILITY"]
}

candidateKey 是服务端签名的、与公司和岗位绑定的短期不透明标识,只用于前端列表稳定 key;它不等于 studentUserId,也不能在本版换取详情、联系方式或简历。

企业响应禁止出现:

  • studentUserId、用户 ID、简历 ID、手机号、邮箱、住址、简历 URL;
  • 姓名、头像、出生日期、性别、身份证明、学校排名;
  • 测评 attempt/result ID、维度分、原始分、题目、答案、答案键、解析、评分规则;
  • candidate_dimension_scores 明细、候选综合分、排名、百分位;
  • 画像原始权重、能力期望、内部规则版本、SQL/审计数据。

学生端开关响应只返回 enabled、version、createdAt、updatedAt;不返回企业查询过该学生的记录。

4. 接口契约(提案)

4.1 学生人才发现偏好

GET /profile/me/talent-discovery
PUT /profile/me/talent-discovery

GET 无记录时返回默认值,不写库:

{ "enabled": false, "version": 0 }

PUT 请求:

{ "enabled": true, "expectedVersion": 0 }
  • enabled 必填;
  • expectedVersion 可选,传入时必须与当前版本一致;
  • 首次创建仅接受 expectedVersion 缺失或 0;
  • 并发冲突复用现有乐观锁错误 9201,不新增无必要错误码;
  • 成功只写 TALENT_DISCOVERY_PREFERENCE_UPDATED 审计,details 仅有开关状态、版本、requestId 与时间。

4.2 企业岗位人才推荐

GET /companies/me/jobs/{jobId}/talent-recommendations?page=0&size=20

参数规范:page 从 0 起;size 为 1–50;jobId > 0。无候选人返回 HTTP 200:

{
  "mode": "NO_ELIGIBLE_CANDIDATE",
  "items": [],
  "total": 0,
  "page": 0,
  "size": 20,
  "totalPages": 0,
  "message": "暂未发现已授权且满足基础条件的候选人"
}

目标岗位缺少可用画像时也返回 HTTP 200,但明确:

{
  "mode": "UNAVAILABLE",
  "items": [],
  "message": "该岗位尚无审核通过的推荐画像"
}

不得把空候选池或不可用画像伪装成评分为 0 的推荐列表。

4.3 预计 OpenAPI

新增 2 个唯一 path / 3 个 operation(学生 GET/PUT 共用同一 path):

方法 路径 角色
GET /profile/me/talent-discovery STUDENT
PUT /profile/me/talent-discovery STUDENT
GET /companies/me/jobs/{jobId}/talent-recommendations 已认证 COMPANY,且岗位归属本人

以当前 159 paths / 196 operations 为基线,预计更新到 161 paths / 199 operations。三个 operation 只形成两个唯一 URL path;实际以运行时 /v3/api-docs 和静态 openapi-rc.json 的对账结果为准。

5. 数据模型与迁移

5.1 新迁移

建议新增、且只新增:

V39__enterprise_talent_discovery.sql

不修改 V1–V38 已执行迁移;不向 candidate_dimension_scores 写入任何数据。

5.2 student_talent_discovery_preferences

字段 类型/约束 含义
id BIGINT PK 主键
student_user_id BIGINT NOT NULL UNIQUE,FK users(id) 仅本人一条授权记录
enabled TINYINT NOT NULL DEFAULT 0 人才发现显式授权
version BIGINT NOT NULL DEFAULT 0 乐观锁
created_at DATETIME NOT NULL 创建时间
updated_at DATETIME NOT NULL 最后更新

索引:(enabled, student_user_id)。关闭的记录不参与候选查询;不存在记录等效关闭。

不存:公司 ID、岗位 ID、企业浏览记录、联系方式、简历路径、测评细节、候选评分或算法分。

6. 规则计算与数据一致性

6.1 capturedNow

企业一次查询在服务起点生成唯一 capturedNow。同一次计算的能力有效期、候选过滤与输出必须复用该值,避免边界时刻同页结果不一致。

6.2 信号选择

对每位候选人仅读取以下最小聚合数据:

信号 有效条件 不可用处理
偏好 P 可解析的城市、行业、岗位类型、用工类型、远程或薪资偏好 该信号不计入分母
兴趣 I 最新 HOLLAND_RIASEC、COMPLETED,按 completed_at DESC, attempt_id DESC 取一条,六维完整且 0–100 不计入分母
能力 A 最新 WORKPLACE_ABILITY、COMPLETED、valid_until > capturedNow,四维完整、每维 6 题、总计 24 题且 0–100 不计入分母

禁止取历史最高分。JSON 损坏、缺维、越界、空值、重复维或无效时间都只能令该信号不可用,不能造成 HTTP 500。

6.3 分数与排序

仅当至少一项可用信号存在时,计算:

fitScore =
  (P × 40 + I × 35 + A × 25) / 可用信号权重之和

中间与最终结果使用 BigDecimal,最终保留两位、HALF_UP。fitScore 仅用于后端排序与映射档位,不对企业返回数值。

匹配档位 内部区间 企业展示
HIGH ≥ 75 较高匹配度
MEDIUM 60–74.99 中等匹配度
BASIC < 60 基础匹配度

排序:fitScore DESC → 候选档案最后更新时间 DESC → student_user_id DESC。最后一项只用于稳定排序,不对外暴露。

若没有任何有效信号,候选人不进入 V41 首版列表;不使用学校、姓名、性别等字段做“兜底排序”。

6.4 安全概述

summary 固定来自模板,只允许说明总体匹配等级。signalsAvailable 仅披露存在的类别 PREFERENCE / RIASEC / ABILITY,不披露任何单项高低、维度或权重。详细理由与可解释反馈明确留给后续“推荐理由和解释”模块评审。

7. 查询、性能与并发

7.1 查询策略

V41 第一版采用实时计算、全局排序后分页:不能先在数据库分页、只对当前页计算分数再排序,否则第 2 页候选人可能比第 1 页分数更高,分页结果会错误。

  1. 先用一次批量资格查询读取全部候选 ID 与必要的基础档案字段:授权开启、STUDENT、档案存在、账号未处于注销限制状态、与当前公司不存在任何历史投递;历史投递通过 NOT EXISTS 或批量集合排除,不能只排除当前岗位,也不能按候选人逐条查询。
  2. 按固定批次(建议每批 500 个 ID)批量读取学生偏好、最新可用 RIASEC、最新有效能力结果和展示档案;候选 ID 必须使用参数绑定,禁止拼接 SQL。
  3. 服务层以内存 Map 汇总每个候选人的信号,计算所有候选人的 fitScore,再按 fitScore DESC、档案 updatedAt DESC、studentUserId DESC 做全局排序,最后执行 page/size 切片。

这是第一版实时方案;当候选池达到规模阈值,应升级为异步推荐索引/快照,但不能以页内排序牺牲当前版本的全局排序正确性。查询需预留真实 MySQL EXPLAIN 验收:student_user_id、definition_id、completed_at、valid_until 与授权开关索引均应参与过滤。

7.2 并发

人才推荐列表是只读、实时计算,无持久化推荐记录,故无创建推荐的并发写入问题。学生开关通过 @Version + expectedVersion 控制并发,两个竞争更新只能有一个成功;另一个返回 409/9201。

查询过程中学生关闭开关的竞态允许下一次请求生效;同一已开始的只读事务可在当前页继续返回当时快照,不延迟或阻塞用户操作。

8. 错误语义与审计

8.1 错误

场景 HTTP / 业务码
非认证 401
角色不符 403 / 2002
企业不存在 404 / 5001
岗位不存在/非发布 复用既有岗位错误码
岗位不属于当前企业 403 / 2002(不泄露额外信息)
企业未认证 403 / 2002
page/size/jobId 非法 400 / 1001
学生开关版本冲突 409 / 9201

本版不新增 V41 专属错误码。UNAVAILABLE 和 NO_ELIGIBLE_CANDIDATE 是成功响应模式,不是错误。

8.2 审计

仅学生开关写审计:

{
  "enabled": true,
  "version": 2,
  "requestId": "..."
}

GET 推荐列表不写逐候选人或逐企业浏览审计,避免形成候选人追踪链路。不得审计候选人清单、matchScore、信号细节、学生 ID、联系方式、简历地址、答案、IP、User-Agent、token 或密码。

9. 安全与公平性门禁

  1. 学生显式同意是进入企业发现池的前置条件;默认关闭;
  2. 企业必须认证、岗位必须属于自己且公开发布;
  3. 推荐不是自动淘汰或录用依据,企业仍需在后续合法招聘流程中自行评估;
  4. 不使用受保护特征或联系方式排序;
  5. 任何测评聚合仅在学生自愿进入发现池后作为内部排序信号;企业侧不见测评详情;
  6. 若后续加入解释、邀约、反馈或模型训练,必须单列模块并重新做隐私、风控和产品评审。

10. 建议代码结构

com.mydreamjob.talentdiscovery
  StudentTalentDiscoveryPreference
  StudentTalentDiscoveryPreferenceRepository
  TalentDiscoveryService
  TalentDiscoveryCandidateSource       # 批量只读数据来源
  TalentDiscoveryScoreCalculator      # 纯函数,无数据库访问
  TalentDiscoveryController
  dto/

允许复用 V39 的安全 JSON 解析与岗位画像校验,但不得复制 Controller 中的权限逻辑或把 V41 放入 V39 Controller 而扩大其职责。

11. 测试与验收方案

11.1 自动化测试

  1. TalentDiscoveryScoreCalculatorTest
    • 三信号权重与缺项重归一化;
    • 最新低分优先于历史高分;
    • 能力恰在 capturedNow 过期;
    • 缺维、非法 JSON、越界均不 500;
    • 不使用姓名、性别、学校或联系方式作为输入。
  2. TalentDiscoveryServiceTest
    • 默认关闭不入池;开启后入池;关闭后立即排除;
    • 当前企业任一历史投递均排除;其他企业投递不排除;
    • 未认证企业、跨企业岗位、无审核画像、未发布岗位;
    • 候选卡白名单;无数据模式正确;N+1 查询次数固定。
  3. TalentDiscoveryControllerTest
    • STUDENT/COMPANY/ADMIN/未登录矩阵;
    • 分页校验;不泄露 studentUserId、联系方式、测评字段或分数;
    • 学生开关 PUT 乐观锁冲突。
  4. TalentDiscoveryPersistenceIntegrationTest
    • V39 迁移、唯一约束、FK、默认关闭、@Version 冲突;
    • 批量查询与已投递排除在 H2 MySQL 模式工作。

11.2 真实 MySQL/Docker 黑盒

  1. V39 迁移成功且无影响 V1–V38;
  2. 默认关闭、开启、关闭后的立即可见性变化;
  3. 已认证/未认证企业、岗位所有权、PUBLISHED 与已审核画像门禁;
  4. 同企业历史投递排除、其他企业投递不误排;
  5. 最新低分优先、能力过期、无有效信号和无候选人模式;
  6. 响应脱敏、审计不写候选清单;
  7. EXPLAIN 与常数批量查询验证;
  8. RabbitMQ 队列终态与 V39/V40 关键接口回归。

12. 文档、OpenAPI 与发布门禁

编码后更新:

  • docs/enterprise-talent-discovery-api.md;
  • docs/frontend-integration.md;
  • docs/backend-development-roadmap.md;
  • docs/openapi-rc.json(V41 实际 161 paths / 199 operations,两个唯一 path 承载三个 operation);
  • docs/verify_openapi.py。

发布门禁已完成:专项与全量自动化测试通过(1461/1461),OpenAPI 静态/运行时均为 161 paths / 199 operations,真实 MySQL/Docker 黑盒 PASS=37 / FAIL=0。黑盒覆盖 V39 迁移、授权状态机、最新版测评选择、同企业历史投递排除、全局排序后分页、权限与脱敏、队列终态及 V39 回归。

13. 编码前确认结论

负责人已确认本设计的业务范围。进入编码前还必须按本设计完成只读核查:最新 Flyway 为 V38、V39 岗位画像审核字段存在、认证企业判断可用、学生档案字段可满足候选卡白名单;如任一项与当前代码冲突,停止并报告,不自行改变接口、迁移号、隐私边界或排序口径。