4 changed files with 1030 additions and 1 deletions
@ -0,0 +1,275 @@ |
|||||
|
# 行业技能词云、个人匹配度与学习路径 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` 等身份注入参数。 |
||||
|
- 使用统一 `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 <accessToken> |
||||
|
``` |
||||
|
|
||||
|
`categoryId` 可省略: |
||||
|
|
||||
|
```http |
||||
|
GET /api/v1/skills/word-cloud?industryId=1&occupationId=2 |
||||
|
``` |
||||
|
|
||||
|
成功响应为 `ApiResponse<SkillCloudResponse>`。`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 <accessToken> |
||||
|
``` |
||||
|
|
||||
|
匹配集合严格取当前筛选结果的 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<SkillMatchResponse>`: |
||||
|
|
||||
|
```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 <accessToken> |
||||
|
``` |
||||
|
|
||||
|
学习路径只消费个人匹配结果中的 `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`。 |
||||
Loading…
Reference in new issue