11 KiB
行业技能词云与学习路径设计
事实与约束
- 仓库当前为 Spring Boot 3.5.16、Java 17、MySQL/Flyway/JPA,最新已存在迁移为
V34。 - 学生技能存储在
student_profiles.skills_json,历史数据可能为空、重复或无法解析。 - 企业行业与岗位业务数据是自由文本,不能作为稳定的“行业 → 职位 → 技能”分类源。
- ESCO 提供职位、技能和技能分类关系;O*NET 提供 Importance 数值。两者均需保留来源版本与许可归属。
- 首版只导入互联网、金融、教育及代表性职位,避免全量翻译和超大迁移。
- 三个接口均为只读、仅 STUDENT,身份只取 JWT,响应统一使用
ApiResponse。 - 词云热度是静态 O*NET Importance,不随平台岗位发布和投递行为变化。
- 计算完全确定,不调用 AI、网络课程服务或第三方运行时接口。
Goals
- 建立可持续扩展的开源技能知识库,并允许项目维护中文名称、别名和细粒度分类。
- 提供完整、稳定、可筛选的热度排序技能词云。
- 以同一排序结果计算个人 TOP10 匹配度,避免不同接口结果不一致。
- 基于未命中技能生成最小可用的学习优先级和建议。
- 保持现有模块接口语义不变,并满足项目测试、OpenAPI 和黑盒验收约定。
Non-Goals
- 不做岗位实时文本挖掘、平台岗位热度聚合或用户行为周榜。
- 不导入 ESCO/O*NET 全量数据,不建设后台维护界面。
- 不保存匹配快照、学习进度、完成状态或提醒。
- 不提供课程、讲师、价格、时长、链接和 AI 对话。
- 不向 COMPANY/ADMIN 暴露个人匹配和学习路径。
Decisions
D1: 以 ESCO + O*NET 作为静态知识底座
Choice:ESCO 作为职位、通用技能和分类主表;当具体技术(如 React、Docker、Spring Boot)没有可靠 ESCO 节点时,允许使用 ONET software_skills 作为补充技能来源。ONET 继续提供可追溯 Importance;项目只维护中文标签、别名和映射。
Rationale:能用开源数据覆盖技能层级,同时保留 O*NET 明确的重要度字段,不需要自行创造热度权重。
Alternatives:
- 仅 O*NET:重要度完整,但英文和美国职业体系适配成本高。
- 严格仅 ESCO:来源单一,但会丢失 React、Docker 等 ESCO 未收录的具体技能。
- 自建词典:可控性高,但需要长期运营且缺少来源可信度。
Consequences:需要明确区分 ESCO 主来源技能与 ONET 补充技术;每条热度必须记录 ONET SOC 和 Element ID,无法映射时保留技能并写空热度。
D2: 词库使用关系表落 Flyway
Choice:新增行业、职位、技能分类、技能、别名、职位技能关系和来源版本表,通过 Flyway 迁移导入代表性数据。
Rationale:接口需要多条件过滤、稳定排序和可追溯来源;关系表可利用外键、唯一约束和索引保证数据质量。
Alternatives:
- 静态 JSON:实现快,但无法方便地按节点过滤、校验引用和演进。
- 图数据库:适合复杂关系,但超出当前技术栈和首版范围。
Consequences:迁移文件需要严格审查,后续词库更新必须创建新迁移,不能修改已执行迁移。
D3: 三个接口共享同一条排名管道
Choice:先查询筛选范围内的技能列表并按热度排序,再从同一列表截取 TOP10;匹配和学习路径均调用同一领域服务,不重新排序。
Rationale:词云、匹配度和学习路径必须对“热门技能集合”保持一致,减少重复逻辑和边界分歧。
Alternatives:
- 每个接口独立查询和排序:实现分散,容易出现排序不一致。
- 持久化统一排名快照:可缓存,但当前数据规模不需要额外调度和一致性成本。
Consequences:排序规则、空热度规则和别名规则只有一个实现位置;单元测试必须聚焦该共享管道。
D4: TOP10 不单独存储或返回
Choice:词云接口返回完整排序列表,前端直接取前十条;匹配服务在内部最多取前十条。
Rationale:用户已确认不需要独立 TOP10 数据源,列表本身是唯一事实来源。
Alternatives:响应同时返回 top10 字段或提供独立接口;会重复数据并增加一致性风险。
Consequences:前端必须按约定截取前十条;接口文档需明确列表已按排名排序。
D5: 匹配和建议按请求实时计算且不持久化
Choice:每次读取当前学生技能、词库和筛选条件后计算匹配度和学习路径,不保存计算结果。
Rationale:数据量小、算法确定,且用户未要求历史记录;避免引入无用状态和隐私数据留存。
Alternatives:保存每日匹配快照或学习计划;会增加表、清理与一致性复杂度,首版无收益。
Consequences:学生技能或词库更新后,结果立即变化;响应必须包含足够字段让前端独立展示。
D6: 学习建议使用分类模板
Choice:技能分类映射到编程语言、框架、工具和通用四类建议模板。
Rationale:满足“技能 + 优先级 + 简化建议”,无需依赖外部课程数据。
Alternatives:调用 AI 或维护课程表;前者不确定且超出约束,后者缺少可用授权数据。
Consequences:建议保持简短、行业无关和可重复;后续可在不改变接口结构的前提下细化模板。
Data Model
| 表 | 关键字段 | 作用 |
|---|---|---|
skill_industries |
id, code, name_zh, source_code, sort_order |
行业节点 |
skill_occupations |
id, industry_id, code, name_zh, source_code, sort_order |
行业下职位 |
skill_categories |
id, occupation_id, code, name_zh, suggestion_template_code, sort_order |
职位下技能分类 |
skills |
id, code, name_zh, name_en, source_type, source_code |
标准技能 |
skill_aliases |
id, skill_id, alias, normalized_alias |
中英文、大小写和写法别名 |
occupation_skills |
occupation_id, skill_id, category_id, importance, importance_source_code, importance_element_id, importance_is_proxy, relationship_type, source_version |
职位技能关系、热度及其机器可追溯来源 |
skill_source_versions |
source_name, source_version, license, attribution_url, imported_at |
来源与许可追溯 |
约束与索引:
skill_occupations.industry_id + code唯一,防止同一行业内职位重复。skill_categories.occupation_id + code唯一。skill_aliases.normalized_alias全局唯一,避免同一写法映射到多个技能。occupation_skills.occupation_id + skill_id唯一。- 每条非空
importance必须有 O*NETimportance_source_code和importance_element_id;代理映射必须设置importance_is_proxy=1。 - 查询索引覆盖
occupation_id + category_id和occupation_id + importance。 category_id必须属于occupation_id,通过服务校验并在必要测试中验证。
API Contract
GET /api/v1/skills/word-cloud
查询参数:industryId 必填,occupationId 必填,categoryId 可选。
返回 data:
industry、occupation、category:实际筛选节点。total:技能总数。items:按heat DESC, nameZh ASC, skillId ASC排序,包含rank、skillId、code、nameZh、nameEn、categoryId、categoryName、heat、heatSource。
GET /api/v1/skills/match
使用与词云相同的三个查询参数。返回 data:
matchScore:0–100 整数。matchedCount与totalCount。matchedSkills与missingSkills:使用精简技能项,缺失技能保持热度顺序。
GET /api/v1/skills/learning-path
使用与词云相同的三个查询参数。返回 data:
total。items:每项包含priority、skillId、skillName、categoryId、categoryName、suggestion。
Processing Rules
- Flyway 先建立表和约束,再导入代表性词库数据。
- 查询服务校验行业、职位、分类关系;ESCO 作为职位与通用技能主表,O*NET
software_skills仅补充 ESCO 缺失的具体技术,构建统一的排序技能列表。 - 匹配服务解析当前学生
skills_json;解析失败按空技能处理并记录告警,不输出原始 JSON。 - 学生技能和技能别名统一执行 Unicode 规范化、去首尾空白、大小写折叠、连续空白折叠及常见标点归一化。
- TOP10 大小取
min(10, 列表大小);匹配度按round(matched * 100.0 / size)计算,size=0时返回 0。 - 学习路径遍历缺失技能,按列表顺序生成连续优先级,并依据
suggestion_template_code产生建议。 - 所有计算在只读事务中完成,不写入匹配或学习路径记录。
Security and Privacy
- 在 SecurityFilterChain 中将
/skills/**限制为已认证请求,Controller/Service 再断言 STUDENT 角色。 - 身份只取 SecurityContext/JWT,不接受
studentUserId、userId或skills请求参数。 - 响应不包含原始技能 JSON、手机号、邮箱、简历内容或词库内部敏感字段。
- 日志只记录异常类型与请求上下文,不记录完整学生技能数组。
Risks and Verification
| 风险 | 处理 | 验证证据 |
|---|---|---|
| ESCO/O*NET 中文覆盖不足 | 首版只导入代表性职位,所有入选技能必须有中文标准名 | 种子数据检查:中文名非空率 100% |
| 别名冲突导致匹配不确定 | normalized_alias 全局唯一,迁移失败即阻断 |
Flyway 唯一约束测试和别名单测 |
| Importance 缺失 | heat=null,排序末位,保留技能 |
排序单元测试和 H2 Repository 测试 |
| 分类关系错配 | 校验分类属于职位、职位属于行业 | 非法组合 MockMvc 400 场景 |
| 迁移过大影响启动 | 首版限制三类行业和代表性职位,种子数据可控 | 迁移在 H2/MySQL 测试库执行耗时记录 |
| 学生技能 JSON 损坏 | 捕获解析异常并按空技能处理 | 异常 JSON 服务测试,断言 200 和匹配度 0 |
| 三个接口结果漂移 | 使用共享排名服务与同一 DTO 排序 | 集成测试断言 TOP10、匹配和学习路径顺序一致 |
| 许可或版本遗漏 | 保存来源版本、许可和归属 URL | 来源说明文档审查与迁移数据断言 |
Completion Boundary
设计完成的标准是:四个规划产物一致、所有需求有可测试场景、任务覆盖三个接口和 Flyway 数据变更、没有未确认的兼容性或权限决策。实现和验收不在本设计阶段启动。