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

9.0 KiB

行业技能词云、个人匹配度与学习路径 API

版本:N03 设计锁定版 状态:已编码实现 适用范围:学生端只读接口。本文描述 GET /api/v1/skills/word-cloud、GET /api/v1/skills/match、GET /api/v1/skills/learning-path。 Base URL:http://127.0.0.1:8080/api/v1(生产环境替换域名,路径不变)。

1. 权限与共同参数

三个接口均:

  • 仅允许 STUDENT 访问。
  • 必须携带 Authorization: Bearer <accessToken>,身份只从 JWT/SecurityContext 获取,不接受 userId、studentId 等身份注入参数。
  • 成功、400 和 403 响应使用统一 ApiResponse;匿名或无效 JWT 的 401 由全局 Security 入口直接返回裸 JSON,不包含 data 和 requestId。
  • 只读,不写入学习进度、学生资料或词库数据。

查询参数:

参数 类型 必填 说明
industryId long 是 词库行业 ID,必须为正整数,且必须是真实存在的行业。
occupationId long 是 词库职位 ID,必须为正整数,且必须属于该行业。
categoryId long 否 技能分类 ID,必须为正整数;省略时使用该职位下全部技能分类。传入时必须属于目标职位。

任一必填参数缺失、非正整数,或行业—职位—分类关系非法时返回 400。如果未登录返回 401,非 STUDENT 用户返回 403。

2. 通用响应与排序

三个接口的 data 都使用 camelCase 字段。词云和 TOP10 使用同一份服务端排序结果:

  1. heat 从高到低;heat 为 null 的技能排在所有非空热度之后。
  2. 热度相同时按 nameZh 升序。
  3. 其余情况按 skillId 升序,保证重复请求顺序稳定。
  4. rank 从 1 连续递增。

heat 来自 ONET Importance。无可靠 ONET Importance 映射的 ESCO 技能仍返回,但 heat 为 null。TOP10 不在后端单独存储或单独排序;前端直接取词云 data.items 的前 min(10, data.total) 项。

技能身份来源:

  • sourceType:ESCO 或 O*NET,表示技能条目的主来源。
  • sourceCode:可追溯来源标识。ESCO 为技能 URI;O*NET 补充技能为 SOC|Workplace Example|Element ID。
  • heatSourceCode:热度来源 SOC;没有 O*NET 热度时为 null。
  • heatElementId:O*NET Importance 的来源 Element ID;没有时为 null。
  • heatProxy:该条热度是否为代理映射。代理值不改变排序规则,但前端可据此展示来源说明。

3. 查询词云

GET /api/v1/skills/word-cloud?industryId=1&occupationId=2&categoryId=101
Authorization: Bearer <accessToken>

categoryId 可省略:

GET /api/v1/skills/word-cloud?industryId=1&occupationId=2

成功响应为 ApiResponse<SkillCloudResponse>。SkillCloudResponse 返回筛选节点、总数和当前范围内的完整技能列表:

{
  "code": 200,
  "message": "ok",
  "data": {
    "industry": { "id": 1, "code": "INTERNET", "name": "互联网" },
    "occupation": { "id": 1, "code": "FRONTEND_DEVELOPER", "name": "前端开发工程师" },
    "category": { "id": 103, "code": "FRONTEND_FRAMEWORK", "name": "前端框架" },
    "total": 1,
    "items": [
      {
        "rank": 1,
        "skillId": 26,
        "skillCode": "REACT",
        "nameZh": "React",
        "nameEn": "React",
        "sourceType": "O*NET",
        "sourceCode": "15-1254.00|React|2.E.7.h",
        "categoryId": 103,
        "categoryName": "前端框架",
        "heat": 4.38,
        "heatSourceCode": "15-1254.00",
        "heatElementId": "2.E.7.h",
        "heatProxy": true
      }
    ]
  },
  "requestId": "req-123",
  "timestamp": "2026-09-15T10:00:00Z"
}

category 在未传 categoryId 时为 null。合法范围但暂时没有技能时返回成功、total=0、items=[]。前端根据 items 渲染词云,并截取前十条作为 TOP10。

4. 查询个人技能匹配度

GET /api/v1/skills/match?industryId=1&occupationId=2&categoryId=101
Authorization: Bearer <accessToken>

匹配集合严格取当前筛选结果的 TOP10。系统从当前 JWT 学生的 student_profiles.skills_json 读取技能,使用标准技能名、英文名、技能代码和别名归一化匹配。大小为 total 的筛选范围使用 min(10, total) 作为分母:

matchScore = round(matchedCount * 100 / min(10, total))
  • total=0 时 matchScore=0,命中列表和缺失列表为空。
  • 范围少于 10 项时按实际技能数计算。
  • 重复技能或指向同一标准技能的多个别名只计一次。
  • 示例:范围 15 条、命中 5 条,matchScore=33。
  • 原始 skills_json、手机号、邮箱和简历内容不会出现在响应中。

成功响应为 ApiResponse<SkillMatchResponse>:

{
  "code": 200,
  "message": "ok",
  "data": {
    "matchScore": 50,
    "matchedCount": 1,
    "totalCount": 2,
    "matchedSkills": [
      {
        "rank": 1,
        "skillId": 26,
        "skillCode": "REACT",
        "nameZh": "React",
        "nameEn": "React",
        "sourceType": "O*NET",
        "sourceCode": "15-1254.00|React|2.E.7.h",
        "categoryId": 103,
        "categoryName": "前端框架",
        "heat": 4.38,
        "heatSourceCode": "15-1254.00",
        "heatElementId": "2.E.7.h",
        "heatProxy": true
      }
    ],
    "missingSkills": [
      {
        "rank": 2,
        "skillId": 31,
        "skillCode": "DOCKER",
        "nameZh": "Docker",
        "nameEn": "Docker",
        "sourceType": "O*NET",
        "sourceCode": "15-1299.08|Docker|2.E.7.h",
        "categoryId": 105,
        "categoryName": "工程化工具",
        "heat": 4.12,
        "heatSourceCode": "15-1299.08",
        "heatElementId": "2.E.7.h",
        "heatProxy": true
      }
    ]
  },
  "requestId": "req-456",
  "timestamp": "2026-09-15T10:01:00Z"
}

matchedSkills 与 missingSkills 的并集等于当前 TOP10,两个列表各自保持词云排序;totalCount 是实际匹配集合大小。

5. 生成学习路径

GET /api/v1/skills/learning-path?industryId=1&occupationId=2&categoryId=101
Authorization: Bearer <accessToken>

学习路径只消费个人匹配结果中的 missingSkills:

  • 只包含当前 TOP10 中未命中的技能,不包含已命中技能或 TOP10 之外的技能。
  • 顺序与 missingSkills 完全一致,priority 从 1 连续递增。
  • 每项只返回技能 ID、名称、分类、优先级和简化学习建议。
  • 后端不调用 AI、课程平台或外部知识库;不会返回课程链接、讲师、价格、学习时长或完成度。
  • 系统不持久化学习进度或完成状态。
  • 全部命中或合法范围没有技能时返回 total=0、items=[]。

建议模板按技能分类确定性生成:

模板类别 建议方向
编程语言 基础语法、常见数据结构、至少一个编码练习。
框架 核心概念与生命周期、小型项目集成实践。
工具 安装与基础配置、结合工程完成实际操作。
通用 核心概念和常见应用场景、简短实践巩固。
{
  "code": 200,
  "message": "ok",
  "data": {
    "total": 1,
    "items": [
      {
        "priority": 1,
        "skillId": 26,
        "skillName": "React",
        "categoryId": 103,
        "categoryName": "前端框架",
        "suggestion": "先理解 React 的核心概念与生命周期,再通过一个小型项目练习集成。"
      }
    ]
  },
  "requestId": "req-789",
  "timestamp": "2026-09-15T10:02:00Z"
}

6. 错误响应示例

400 和 403 使用统一 ApiResponse;401 由全局 Security 入口返回裸 JSON。以下示例与运行时契约一致。

{
  "code": 1001,
  "message": "bad request",
  "data": null,
  "requestId": "req-400",
  "timestamp": "2026-09-15T10:03:00Z"
}
HTTP 场景 典型 code
400 industryId/occupationId 缺失、非正整数,或节点关系非法 1001
401 未携带有效 JWT 或令牌无效/过期 401
403 已登录但不是 STUDENT 2002

示例 401:

{
  "code": 401,
  "message": "not authenticated",
  "timestamp": ""
}

401 响应不包含 data 和 requestId。

示例 403:

{
  "code": 2002,
  "message": "forbidden",
  "data": null,
  "requestId": "req-403",
  "timestamp": "2026-09-15T10:05:00Z"
}

7. 字段参考

SkillCloudNode

id、code、name。category 节点在未指定分类时为 null。

SkillCloudItem

rank、skillId、skillCode、nameZh、nameEn、sourceType、sourceCode、categoryId、categoryName、heat、heatSourceCode、heatElementId、heatProxy。

SkillMatchResponse

matchScore、matchedCount、totalCount、matchedSkills、missingSkills。

SkillLearningPathItem / SkillLearningPathResponse

SkillLearningPathItem:priority、skillId、skillName、categoryId、categoryName、suggestion。

SkillLearningPathResponse:total、items。