# 行业技能词云、个人匹配度与学习路径 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 `,身份只从 JWT/SecurityContext 获取,不接受 `userId`、`studentId` 等身份注入参数。 - 使用统一 `ApiResponse` 包装结构。 - 只读,不写入学习进度、学生资料或词库数据。 查询参数: | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `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` 来自 O*NET Importance。无可靠 O*NET 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. 查询词云 ```http GET /api/v1/skills/word-cloud?industryId=1&occupationId=2&categoryId=101 Authorization: Bearer ``` `categoryId` 可省略: ```http GET /api/v1/skills/word-cloud?industryId=1&occupationId=2 ``` 成功响应为 `ApiResponse`。`SkillCloudResponse` 返回筛选节点、总数和当前范围内的完整技能列表: ```json { "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. 查询个人技能匹配度 ```http GET /api/v1/skills/match?industryId=1&occupationId=2&categoryId=101 Authorization: Bearer ``` 匹配集合严格取当前筛选结果的 TOP10。系统从当前 JWT 学生的 `student_profiles.skills_json` 读取技能,使用标准技能名、英文名、技能代码和别名归一化匹配。大小为 `total` 的筛选范围使用 `min(10, total)` 作为分母: ```text matchScore = round(matchedCount * 100 / min(10, total)) ``` - `total=0` 时 `matchScore=0`,命中列表和缺失列表为空。 - 范围少于 10 项时按实际技能数计算。 - 重复技能或指向同一标准技能的多个别名只计一次。 - 示例:范围 15 条、命中 5 条,`matchScore=33`。 - 原始 `skills_json`、手机号、邮箱和简历内容不会出现在响应中。 成功响应为 `ApiResponse`: ```json { "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. 生成学习路径 ```http GET /api/v1/skills/learning-path?industryId=1&occupationId=2&categoryId=101 Authorization: Bearer ``` 学习路径只消费个人匹配结果中的 `missingSkills`: - 只包含当前 TOP10 中未命中的技能,不包含已命中技能或 TOP10 之外的技能。 - 顺序与 `missingSkills` 完全一致,`priority` 从 1 连续递增。 - 每项只返回技能 ID、名称、分类、优先级和简化学习建议。 - 后端不调用 AI、课程平台或外部知识库;不会返回课程链接、讲师、价格、学习时长或完成度。 - 系统不持久化学习进度或完成状态。 - 全部命中或合法范围没有技能时返回 `total=0`、`items=[]`。 建议模板按技能分类确定性生成: | 模板类别 | 建议方向 | |---|---| | 编程语言 | 基础语法、常见数据结构、至少一个编码练习。 | | 框架 | 核心概念与生命周期、小型项目集成实践。 | | 工具 | 安装与基础配置、结合工程完成实际操作。 | | 通用 | 核心概念和常见应用场景、简短实践巩固。 | ```json { "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. 错误响应示例 所有错误也使用统一 `ApiResponse`: ```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 或令牌无效/过期 | `2001` | | `403` | 已登录但不是 STUDENT | `2002` | 示例 401: ```json { "code": 2001, "message": "unauthorized", "data": null, "requestId": "req-401", "timestamp": "2026-09-15T10:04:00Z" } ``` 示例 403: ```json { "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`。