<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>项目实战课程：构建 SSH TUI 个人简历应用 on Go Home</title>
    <link>https://blog.911015.com/termimal_resume/</link>
    <description>Recent content in 项目实战课程：构建 SSH TUI 个人简历应用 on Go Home</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 10 Jun 2026 11:13:14 +0800</lastBuildDate>
    <atom:link href="https://blog.911015.com/termimal_resume/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>课程介绍与技术选型</title>
      <link>https://blog.911015.com/termimal_resume/01.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/01.html</guid>
      <description>01 - 课程介绍与技术选型 项目背景 传统简历通常以 PDF 或网页形式呈现。本项目探索一种更具极客风格的展示方式：通过 SSH 协议直接在终端中展示交互式简历。这种形式有以下优势：&#xA;差异化 — 在 HR 和面试官面前留下深刻印象 技术展示 — 本身就是技术能力的体现 轻量访问 — 任何有终端的设备都能访问，无需浏览器 复古美学 — 终端风格自带程序员文化认同感 灵感来源：terminal.shop — 一个完全通过 SSH 访问的咖啡订购商店。&#xA;技术选型 核心库：Charm 生态 本项目基于 Charm 公司开源的三个核心库构建：&#xA;┌─────────────────────────────────────────────────────────────┐ │ 应用架构分层 │ ├─────────────────────────────────────────────────────────────┤ │ 表现层 (View) │ Lipgloss — 声明式终端样式 │ │ ───────────────────── │ 颜色、边框、布局、文字样式 │ │ 交互层 (Update/Model) │ Bubble Tea — TUI 框架 │ │ ───────────────────── │ Elm 架构：Model/Update/View/Cmd │ │ 传输层 (SSH Server) │ Wish — SSH 服务器框架 │ │ ───────────────────── │ 将 SSH 会话包装为 Bubble Tea 程序 │ └─────────────────────────────────────────────────────────────┘ 各库定位详解 1.</description>
    </item>
    <item>
      <title>环境准备与项目初始化</title>
      <link>https://blog.911015.com/termimal_resume/02.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/02.html</guid>
      <description>02 - 环境准备与项目初始化 前置要求 Go 1.21+ 已安装 基本的 Go 语法知识（struct、method、interface） 熟悉 Git 基本操作 一个终端（Windows Terminal、iTerm2、GNOME Terminal 等） 步骤 1：创建项目目录 mkdir terminal_resume cd terminal_resume go mod init terminal_resume 模块名 terminal_resume 将作为后续所有内部包的导入路径前缀。&#xA;步骤 2：添加依赖 go get github.com/charmbracelet/wish@latest go get github.com/charmbracelet/bubbletea@latest go get github.com/charmbracelet/lipgloss@latest go get gopkg.in/yaml.v3@latest 执行后 go.mod 将自动记录依赖版本。&#xA;步骤 3：规划目录结构 terminal_resume/ ├── main.go # SSH 服务器入口 ├── cmd/ │ └── local/ │ └── main.go # 本地测试入口（无需 SSH） ├── internal/ │ ├── app/ │ │ └── model.</description>
    </item>
    <item>
      <title>数据层：结构定义与 YAML 配置</title>
      <link>https://blog.911015.com/termimal_resume/03.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/03.html</guid>
      <description>03 - 数据层：结构定义与 YAML 配置 设计目标 简历内容需要：&#xA;可配置 — 用户只需修改 YAML 文件即可更新简历 类型安全 — Go struct 提供编译时检查 自包含 — 编译后二进制独立运行，不依赖外部文件 步骤 1：定义数据结构 创建 internal/data/resume.go：&#xA;package data // Resume 简历顶层结构 type Resume struct { Name string `yaml:&amp;#34;name&amp;#34;` Title string `yaml:&amp;#34;title&amp;#34;` Location string `yaml:&amp;#34;location&amp;#34;` Email string `yaml:&amp;#34;email&amp;#34;` GitHub string `yaml:&amp;#34;github&amp;#34;` Website string `yaml:&amp;#34;website&amp;#34;` About About `yaml:&amp;#34;about&amp;#34;` Experience []Experience `yaml:&amp;#34;experience&amp;#34;` Skills Skills `yaml:&amp;#34;skills&amp;#34;` Projects []Project `yaml:&amp;#34;projects&amp;#34;` Contact Contact `yaml:&amp;#34;contact&amp;#34;` } type About struct { Summary string `yaml:&amp;#34;summary&amp;#34;` Details []string `yaml:&amp;#34;details&amp;#34;` } type Experience struct { Company string `yaml:&amp;#34;company&amp;#34;` Role string `yaml:&amp;#34;role&amp;#34;` Period string `yaml:&amp;#34;period&amp;#34;` Location string `yaml:&amp;#34;location&amp;#34;` Highlights []string `yaml:&amp;#34;highlights&amp;#34;` } type Skills struct { Languages []string `yaml:&amp;#34;languages&amp;#34;` Frameworks []string `yaml:&amp;#34;frameworks&amp;#34;` Tools []string `yaml:&amp;#34;tools&amp;#34;` Others []string `yaml:&amp;#34;others&amp;#34;` } type Project struct { Name string `yaml:&amp;#34;name&amp;#34;` Description string `yaml:&amp;#34;description&amp;#34;` TechStack []string `yaml:&amp;#34;techStack&amp;#34;` URL string `yaml:&amp;#34;url&amp;#34;` Highlights []string `yaml:&amp;#34;highlights&amp;#34;` } type Contact struct { Email string `yaml:&amp;#34;email&amp;#34;` GitHub string `yaml:&amp;#34;github&amp;#34;` Website string `yaml:&amp;#34;website&amp;#34;` LinkedIn string `yaml:&amp;#34;linkedIn&amp;#34;` Twitter string `yaml:&amp;#34;twitter&amp;#34;` Message string `yaml:&amp;#34;message&amp;#34;` } Struct Tag 详解 `yaml:&amp;quot;name&amp;quot;` 告诉 yaml.</description>
    </item>
    <item>
      <title>样式系统：复古终端主题</title>
      <link>https://blog.911015.com/termimal_resume/04.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/04.html</guid>
      <description>04 - 样式系统：复古终端主题 设计目标 创建一套可复用的复古终端样式，统一整个应用的视觉风格。灵感来源：老式 CRT 显示器的磷光绿 + 黑色背景。&#xA;配色方案 ┌─────────────────────────────────────────────┐ │ 复古终端配色表 │ ├─────────────────────────────────────────────┤ │ #0a0a0a Black │ 背景色 │ │ #00ff41 Green │ 主色调（高亮文字） │ │ #008f11 DimGreen │ 次要绿（边框、分隔线） │ │ #003b00 DarkGreen │ 暗绿（装饰） │ │ #e0e0e0 White │ 正文文字 │ │ #808080 Gray │ 淡化文字 │ │ #ffb000 Amber │ 强调色（标题） │ │ #ff3333 Red │ 错误提示 │ └─────────────────────────────────────────────┘ Lipgloss 基础 Lipgloss 是一个声明式样式库，核心概念：&#xA;// 创建样式 style := lipgloss.</description>
    </item>
    <item>
      <title>TUI 基础：Bubble Tea 框架入门</title>
      <link>https://blog.911015.com/termimal_resume/05.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/05.html</guid>
      <description>05 - TUI 基础：Bubble Tea 框架入门 什么是 TUI？ TUI（Terminal User Interface）是在终端中渲染的图形界面，区别于：&#xA;CLI：命令行输入输出，无交互界面 GUI：图形窗口界面，需要桌面环境 TUI：终端内的交互界面，键盘驱动 Elm 架构 Bubble Tea 采用 Elm 架构，将程序分为三个纯函数：&#xA;┌─────────────┐ Msg ┌─────────────┐ │ Init() │ ───────────► │ Update() │ │ (初始状态) │ │ (处理消息) │ └─────────────┘ └──────┬──────┘ │ │ New Model ▼ ┌─────────────┐ Cmd ┌─────────────┐ │ View() │ ◄─────────── │ Model │ │ (渲染界面) │ │ (当前状态) │ └─────────────┘ └─────────────┘ │ │ 用户看到界面，按下按键 ▼ New Msg (循环) 核心接口 // Model 接口：任何 struct 只要实现这三个方法就是 Bubble Tea 模型 type Model interface { Init() Cmd // 初始化命令（如启动定时器） Update(Msg) (Model, Cmd) // 处理消息，返回新状态和后续命令 View() string // 渲染当前状态为字符串 } 最简单的 Bubble Tea 程序 package main import ( &amp;#34;fmt&amp;#34; &amp;#34;os&amp;#34; tea &amp;#34;github.</description>
    </item>
    <item>
      <title>应用模型：页面与导航</title>
      <link>https://blog.911015.com/termimal_resume/06.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/06.html</guid>
      <description>06 - 应用模型：页面与导航 模型结构 创建 internal/app/model.go，实现 Bubble Tea 的三个核心方法。&#xA;模型定义 package app import ( &amp;#34;fmt&amp;#34; &amp;#34;strings&amp;#34; tea &amp;#34;github.com/charmbracelet/bubbletea&amp;#34; &amp;#34;github.com/charmbracelet/lipgloss&amp;#34; &amp;#34;terminal_resume/internal/data&amp;#34; &amp;#34;terminal_resume/internal/style&amp;#34; ) type Model struct { width int height int resume *data.Resume currentPage style.PageType cursor int // 详情页内滚动位置 ready bool } func NewModel() Model { return Model{ resume: data.LoadResumeOrDefault(), currentPage: style.DashboardPage, cursor: 0, } } Init 方法 func (m Model) Init() tea.Cmd { return nil // 本应用无需初始命令 } Update 方法：消息处理核心 func (m Model) Update(msg tea.</description>
    </item>
    <item>
      <title>页面渲染：各板块实现</title>
      <link>https://blog.911015.com/termimal_resume/07.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/07.html</guid>
      <description>07 - 页面渲染：各板块实现 本章实现 6 个页面的渲染逻辑。所有渲染方法返回 string，由 Lipgloss 样式包装。&#xA;Dashboard 首页 Dashboard 是应用的&amp;quot;门面&amp;quot;，包含：ASCII 艺术标题 + 名片信息 + 快速导航提示。&#xA;func (m Model) renderDashboard() string { r := m.resume // ASCII 艺术字（可用 figlet 生成） title := ` ____ _ _ __ __ / ___|(_)_ __ ___ | | ___ | \/ | ___ _ __ __ _ ___ _ _ ___ \___ \| | &amp;#39;_ &amp;#39; _ \| |/ _ \ | |\/| |/ _ \ &amp;#39;_ \ / _&amp;#39; |/ _ \ | | / __| ___) | | | | | | | | __/ | | | | __/ | | | (_| | __/ |_| \__ \ |____/|_|_| |_| |_|_|\___| |_| |_|\___|_| |_|\__, |\___|\__,_|___/ |___/ ` titleStyled := style.</description>
    </item>
    <item>
      <title>SSH 服务器：Wish 框架集成</title>
      <link>https://blog.911015.com/termimal_resume/08.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/08.html</guid>
      <description>08 - SSH 服务器：Wish 框架集成 SSH 协议基础 SSH（Secure Shell）通常用于远程登录服务器执行命令。但 SSH 协议支持 PTY（伪终端）分配，允许服务器向客户端发送交互式界面。&#xA;传统 SSH 服务器流程：&#xA;用户 ──ssh──► SSHD ──exec──► bash/shell Wish 的 SSH 服务器流程：&#xA;用户 ──ssh──► Wish ──Bubble Tea──► TUI 界面 Wish 核心概念 Middleware 模式 Wish 使用中间件（Middleware）处理 SSH 会话。每个中间件可以对会话进行加工：&#xA;wish.WithMiddleware( logging.Middleware(), // 记录日志 activeterm.Middleware(), // 强制要求交互式终端 bubbletea.Middleware(teaHandler), // 将会话转为 TUI ) 中间件执行顺序：从外到内，后添加的中间件先处理原始会话。&#xA;teaHandler 签名 // bubbletea.Middleware 需要这个签名的函数 func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) 参数 s ssh.Session：当前 SSH 会话对象 返回值 tea.Model：Bubble Tea 模型 返回值 []tea.</description>
    </item>
    <item>
      <title>本地测试与调试</title>
      <link>https://blog.911015.com/termimal_resume/09.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/09.html</guid>
      <description>09 - 本地测试与调试 为什么需要本地入口？ 每次测试都通过 SSH 连接效率太低。本地入口直接运行 Bubble Tea，无需 SSH 服务器：&#xA;┌──────────────┐ ┌──────────────┐ │ 本地调试 │ 对比 │ SSH 模式 │ ├──────────────┤ ├──────────────┤ │ go run │ │ 生成 Host Key │ │ ./cmd/local │ 更简单 │ 启动服务器 │ │ │ ───────► │ ssh 连接 │ │ 直接看到界面 │ │ │ └──────────────┘ └──────────────┘ 实现本地入口 创建 cmd/local/main.go：&#xA;package main import ( &amp;#34;fmt&amp;#34; &amp;#34;os&amp;#34; tea &amp;#34;github.com/charmbracelet/bubbletea&amp;#34; &amp;#34;terminal_resume/internal/app&amp;#34; ) func main() { m := app.</description>
    </item>
    <item>
      <title>部署与运维</title>
      <link>https://blog.911015.com/termimal_resume/10.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/10.html</guid>
      <description>10 - 部署与运维 部署方案对比 方案 难度 成本 适合 云服务器 (VPS) 中 低 长期运行，完全控制 Fly.io 低 免费额度 快速部署，自动 HTTPS Docker 中 取决于托管 标准化部署，易于迁移 GitHub Actions 低 免费 CI/CD 自动化 方案 1：Docker 部署 Dockerfile # 构建阶段 FROM golang:1.21-alpine AS builder WORKDIR /app COPY . . RUN go build -o terminal-resume . # 运行阶段 FROM alpine:latest RUN apk add --no-cache ca-certificates WORKDIR /app # 复制二进制文件和 SSH Host Key COPY --from=builder /app/terminal-resume . COPY --from=builder /app/.</description>
    </item>
    <item>
      <title>练习 01：修改配色主题</title>
      <link>https://blog.911015.com/termimal_resume/exercise-01.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/exercise-01.html</guid>
      <description>练习 01：修改配色主题 目标 将应用的复古绿黑主题改为琥珀色主题（Amber on Black），类似老式 IBM 单色显示器。&#xA;参考配色 背景: #0a0a0a (Black) 主色: #ffb000 (Amber) 次色: #cc8800 (DimAmber) 暗色: #553300 (DarkAmber) 强调: #00ff41 (Green, 保留用于选中状态) 错误: #ff4444 (Red) 任务 修改 internal/style/theme.go 中的颜色常量 确保所有页面正确显示新配色 运行本地版本验证效果 提示 只需要修改 theme.go 文件顶部的颜色定义 样式对象（如 TitleStyle、BoxStyle）使用颜色常量，会自动生效 建议保留 SelectedItem 的反色设计（深色背景 + 亮色文字） 进阶 尝试实现蓝色主题（Phosphor Blue）：&#xA;主色: #00bfff (DeepSkyBlue) 次色: #0066aa (DimBlue) 暗色: #002244 (DarkBlue) </description>
    </item>
    <item>
      <title>练习 02：添加 Education 页面</title>
      <link>https://blog.911015.com/termimal_resume/exercise-02.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/exercise-02.html</guid>
      <description>练习 02：添加 Education 页面 目标 在现有 5 个内容页基础上，添加第 6 个页面 Education（教育经历）。&#xA;任务 1. 更新数据结构 修改 internal/data/resume.go：&#xA;// 添加 Education 结构 type Education struct { School string `yaml:&amp;#34;school&amp;#34;` Degree string `yaml:&amp;#34;degree&amp;#34;` Major string `yaml:&amp;#34;major&amp;#34;` Period string `yaml:&amp;#34;period&amp;#34;` Location string `yaml:&amp;#34;location&amp;#34;` Highlights []string `yaml:&amp;#34;highlights&amp;#34;` } // Resume 结构添加字段 type Resume struct { // ... 现有字段 Education []Education `yaml:&amp;#34;education&amp;#34;` // ... 现有字段 } 2. 更新 YAML 在 internal/data/resume.yaml 中添加：&#xA;education: - school: &amp;#34;清华大学&amp;#34; degree: &amp;#34;硕士&amp;#34; major: &amp;#34;计算机科学与技术&amp;#34; period: &amp;#34;2016.</description>
    </item>
    <item>
      <title>练习 03：实现打字机动画效果</title>
      <link>https://blog.911015.com/termimal_resume/exercise-03.html</link>
      <pubDate>Wed, 10 Jun 2026 11:13:14 +0800</pubDate>
      <guid>https://blog.911015.com/termimal_resume/exercise-03.html</guid>
      <description>练习 03：实现打字机动画效果 目标 在 Dashboard 页面添加打字机效果：标题文字逐字显示，营造复古终端氛围。&#xA;效果示意 终端显示过程： T (0ms) Te (50ms) Ter (100ms) Term (150ms) ... Terminal Resume (完成) 任务 1. 添加打字机消息 在 internal/app/model.go 中添加：&#xA;// 自定义消息 type typewriterMsg struct { text string index int } 2. 添加打字机命令 func typewriterCmd(text string, index int) tea.Cmd { return tea.Tick(time.Millisecond*50, func(t time.Time) tea.Msg { return typewriterMsg{text: text, index: index} }) } 3. 修改 Model type Model struct { // ... 现有字段 typewriterText string // 当前显示的打字机文字 typewriterDone bool // 是否完成 } 4.</description>
    </item>
  </channel>
</rss>
