构建个人技能库:用YAML+GitHub Actions打造可验证的技术图谱
1. 项目概述:一个技能库的诞生与价值
最近在整理自己的技术栈和项目经验时,我一直在思考一个问题:如何系统化地管理一个开发者(或者说任何专业人士)不断增长的技能树?简历上的“精通Java”、“熟悉React”太单薄了,GitHub上的项目又过于分散。我需要一个地方,不仅能记录我会什么,还能清晰地展示我掌握的程度、相关的项目证明、学习路径,甚至未来的学习方向。这就是我启动yashasvigirdhar/skills这个个人技能库项目的初衷。
简单来说,skills项目就是一个用代码和结构化数据来管理个人技能图谱的仓库。它不仅仅是一个简单的列表,更是一个动态的、可验证的、可生长的知识体系。对于正在求职的朋友,它能帮你梳理优势,制作更具说服力的简历;对于团队管理者,它可以作为评估成员技术广度和深度的参考;对于任何有终身学习习惯的人,它都是一个绝佳的成长记录仪和规划工具。
这个项目适合所有希望对自己的能力进行量化管理和可视化展示的人,尤其是开发者、设计师、产品经理等技术从业者。接下来,我将详细拆解我是如何设计、构建并维护这个技能库的,包括技术选型、数据结构设计、自动化更新以及一些我踩过的坑和总结的经验。
2. 整体架构设计与核心思路
2.1 为什么不用现成工具?自建技能库的考量
市面上有很多技能管理工具,比如 LinkedIn 的技能标签、Notion 的数据库,或者一些在线的技能矩阵模板。我选择自建,主要基于以下几点核心考量:
- 所有权与控制力:数据完全掌握在自己手中,存储在自己的GitHub仓库,格式自定义,无需担心服务关闭或数据迁移问题。
- 可集成与自动化:可以与GitHub Actions、个人博客、简历生成器等开发者工作流无缝集成,实现技能信息的自动更新和同步。
- 深度定制化:我可以定义适合我自己的技能分类体系、熟练度等级(不仅仅是“初学/熟练/精通”),并且关联具体的代码仓库、文章、证书等证据。
- 版本化与可追溯:利用Git的版本管理,我可以清晰地看到自己技能树随时间成长的轨迹,哪一年掌握了什么新技术,一目了然。
基于这些需求,我决定采用“结构化数据文件(如YAML/JSON)+ 生成脚本 + 静态站点”的轻量级架构。核心数据用人类和机器都易读的YAML格式存储,通过脚本处理生成可视化的网页或文档,最终部署到GitHub Pages或Vercel等静态托管服务。
2.2 技术栈选型与工具链搭建
为了让这个项目保持极简且高效,我选择了以下技术栈:
- 数据层:YAML。相比JSON,YAML支持注释,结构更清晰,写起来更友好,非常适合作为配置文件。我的核心技能数据都存放在
skills.yaml文件中。 - 处理层:Python。利用其丰富的库(如PyYAML, Jinja2)进行YAML解析、数据加工和模板渲染。Python脚本足够应对这种数据转换任务。
- 呈现层:静态HTML或Markdown。为了最大化兼容性和可访问性,我选择用Jinja2模板将YAML数据渲染成美观的HTML页面。同时,也生成一份简明的Markdown版本,便于直接粘贴到README或其它地方。
- 自动化与部署:GitHub Actions。这是项目的“大脑”。我设置了工作流,每当我对
skills.yaml文件进行修改并推送后,Actions会自动触发Python脚本处理数据,重新生成静态页面,并自动部署到GitHub Pages。 - 可视化(可选):Mermaid.js。用于生成技能关系的思维导图或流程图,可以嵌入到生成的HTML中,让技能树的关系更直观。
这个工具链的优势在于完全基于开源生态,零成本,且每个环节都高度可控。整个项目的目录结构大致如下:
skills-repo/ ├── .github/ │ └── workflows/ │ └── deploy.yml # GitHub Actions 自动化部署工作流 ├── data/ │ └── skills.yaml # 核心技能数据文件 ├── scripts/ │ └── generate_site.py # 数据处理和静态站点生成脚本 ├── templates/ │ └── index.html.j2 # HTML页面模板 ├── public/ # 生成的静态文件(由脚本自动填充) │ ├── index.html │ └── assets/ ├── README.md # 项目说明,包含生成的Markdown技能摘要 └── requirements.txt # Python依赖列表注意:在工具选型上,避免追求“高大上”。最初我考虑过用React + GraphQL来做一个动态应用,但很快意识到这引入了不必要的复杂性。对于个人技能库这种读多写少、内容驱动的项目,静态生成是黄金标准,它简单、快速、稳定且SEO友好。
3. 核心数据结构与技能建模
3.1 定义技能的属性字段
如何描述一项“技能”?这需要深思熟虑。一个扁平的字符串(如“Python”)信息量太低。我设计了一个包含多个维度的数据结构:
skills: - id: python_core name: Python category: programming_language level: 4 # 使用数字等级,例如1-5 level_label: 高级 years_of_experience: 6 last_used: 2023-10 keywords: ["异步编程", "数据分析", "Web后端", "自动化脚本"] evidence: - type: project title: 自动化部署系统 url: https://github.com/username/auto-deploy description: 使用asyncio和FastAPI构建的CI/CD工具。 - type: certificate title: Python高级软件工程师认证 issuer: 某机构 date: 2022-05 - type: blog title: “深入理解Python装饰器” url: https://myblog.com/posts/python-decorator projects: ["project_slug_1", "project_slug_2"] # 关联项目ID learning_goal: # 下一步学习目标 description: 深入理解CPython解释器内存管理机制。 resources: ["《Python源码剖析》", "某视频课程链接"]关键字段解析:
id: 唯一标识符,用于内部关联,避免名称冲突。level: 采用数字等级(1-5),便于排序和计算。我定义的等级是:1-了解概念,2-基本使用,3-熟练应用,4-深入理解/可解决复杂问题,5-专家级/可设计体系。evidence(证据): 这是技能库的灵魂。空口无凭,这里关联具体的项目、证书、博客文章、演讲等。这是你技能真实性的背书。learning_goal: 记录下一步学习方向,让技能库不仅是历史记录,也是未来路线图。
3.2 设计分类体系与关联关系
技能不是孤立存在的。我建立了两个维度的关联:
- 分类体系:我定义了如
programming_language,framework,database,cloud,tool,soft_skill等大类。每个技能通过category字段归属到某一类下。 - 技能关联:通过
related_skills字段或是在项目中共同出现来建立联系。例如,“FastAPI”技能会关联到“Python”和“RESTful API”设计。这有助于形成知识网络。
在skills.yaml的顶层,我还会定义这些分类的元信息,方便前端展示时进行分组和着色:
categories: programming_language: name: 编程语言 color: "#3498db" framework: name: 框架与库 color: "#2ecc71" # ... 其他分类实操心得:设计数据结构时,一定要考虑扩展性。最初我的
evidence只是一个URL列表,后来发现需要区分类型和补充描述。使用YAML的列表和字典嵌套结构,可以很方便地后续添加字段,而不会破坏已有数据的结构。同时,为id、category这类字段预先定义一个可选的枚举值列表,能有效保持数据的一致性。
4. 自动化工作流与静态站点生成
4.1 核心生成脚本解析
generate_site.py脚本是这个项目的心脏。它的工作流程如下:
- 加载与解析:使用
yaml.safe_load()读取data/skills.yaml。 - 数据加工与计算:
- 根据
level数字生成进度条或星级显示。 - 按
category对技能进行分组。 - 计算每个分类下的技能数量、平均等级等汇总信息。
- 处理“最近使用”时间,高亮近期常用的技能。
- 根据
- 模板渲染:将加工后的数据字典传递给Jinja2模板引擎。模板
templates/index.html.j2中包含了HTML骨架和数据占位符。Jinja2的强大之处在于它支持条件判断、循环和过滤器,可以灵活地控制页面展示逻辑。 - 输出生成:将渲染好的HTML写入
public/index.html。同时,生成一份简化的README.md到项目根目录,用于GitHub仓库预览。
一个简单的脚本片段示例:
import yaml from jinja2 import Environment, FileSystemLoader import os def main(): # 1. 加载数据 with open('data/skills.yaml', 'r', encoding='utf-8') as f: data = yaml.safe_load(f) # 2. 简单加工:按分类分组 skills_by_category = {} for skill in data['skills']: cat = skill['category'] skills_by_category.setdefault(cat, []).append(skill) data['skills_by_category'] = skills_by_category data['category_meta'] = data['categories'] # 传递分类元信息 # 3. 设置Jinja2环境并渲染 env = Environment(loader=FileSystemLoader('templates')) template = env.get_template('index.html.j2') html_content = template.render(**data) # 4. 确保输出目录存在并写入 os.makedirs('public', exist_ok=True) with open('public/index.html', 'w', encoding='utf-8') as f: f.write(html_content) print("站点生成成功!") if __name__ == '__main__': main()4.2 配置GitHub Actions实现CI/CD
自动化是让这个技能库保持活力的关键。我配置了一个GitHub Actions工作流,文件位于.github/workflows/deploy.yml:
name: Deploy Skills Site on: push: branches: [ main ] paths: - 'data/skills.yaml' # 只有技能数据或脚本变更时才触发 - 'scripts/**' - 'templates/**' workflow_dispatch: # 允许手动触发 jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.10' - name: Install dependencies run: | pip install -r requirements.txt - name: Generate static site run: | python scripts/generate_site.py - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./public publish_branch: gh-pages # 部署到gh-pages分支这个工作流的意思是:每当main分支上的skills.yaml或生成脚本等核心文件发生变更时,就会自动在一个干净的Ubuntu环境中,安装Python依赖,运行生成脚本,然后将生成的public目录推送到gh-pages分支。GitHub Pages服务会自动托管gh-pages分支的内容,我的技能库网站就更新了。
注意事项:
secrets.GITHUB_TOKEN是GitHub自动提供的,无需额外配置。但要确保仓库设置中已启用GitHub Pages,并指定源为gh-pages分支。第一次运行后,你的技能库在线地址通常是https://[你的用户名].github.io/[仓库名]/。
5. 前端展示与用户体验优化
5.1 设计清晰直观的展示页面
生成的index.html需要清晰、美观地展示所有信息。我采用了以下设计原则:
- 分类导航:页面顶部或侧边栏,按分类(编程语言、框架、工具等)快速筛选技能。
- 技能卡片:每个技能用一个卡片展示,包含技能名称、熟练度等级(用进度条或星星可视化)、使用年限、最后使用时间。
- 证据抽屉:每个技能卡片可以展开(或通过模态框),详细列出关联的项目、证书等证据,并配有链接。
- 全局视图:提供一个“全景视图”,用标签云或矩阵图展示所有技能,技能字体大小或颜色深浅代表熟练度或近期使用频率。
- 搜索与过滤:实现客户端搜索,可以根据技能名、关键词快速定位。
我使用简单的CSS框架(如Tailwind CSS或PureCSS)来快速构建响应式界面,确保在手机和电脑上都有良好体验。Jinja2模板中,通过循环和条件判断来动态生成这些卡片。
<!-- templates/index.html.j2 片段示例 --> <div class="skills-container"> {% for category_id, category_info in category_meta.items() %} <h2>{{ category_info.name }}</h2> <div class="category-skills"> {% for skill in skills_by_category.get(category_id, []) %} <div class="skill-card">