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.
 
 
 

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*NET importance_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

  1. Flyway 先建立表和约束,再导入代表性词库数据。
  2. 查询服务校验行业、职位、分类关系;ESCO 作为职位与通用技能主表,O*NET software_skills 仅补充 ESCO 缺失的具体技术,构建统一的排序技能列表。
  3. 匹配服务解析当前学生 skills_json;解析失败按空技能处理并记录告警,不输出原始 JSON。
  4. 学生技能和技能别名统一执行 Unicode 规范化、去首尾空白、大小写折叠、连续空白折叠及常见标点归一化。
  5. TOP10 大小取 min(10, 列表大小);匹配度按 round(matched * 100.0 / size) 计算,size=0 时返回 0。
  6. 学习路径遍历缺失技能,按列表顺序生成连续优先级,并依据 suggestion_template_code 产生建议。
  7. 所有计算在只读事务中完成,不写入匹配或学习路径记录。

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 数据变更、没有未确认的兼容性或权限决策。实现和验收不在本设计阶段启动。