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 使用同一份服务端排序结果:
heat从高到低;heat为null的技能排在所有非空热度之后。- 热度相同时按
nameZh升序。 - 其余情况按
skillId升序,保证重复请求顺序稳定。 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。