之前的 resume-generator 是一个命令行工具——在终端里运行就行。但这次我们要做一个有界面的桌面应用,所以需要更多"层"。

第 3 课:整体架构


为什么需要三层架构?

之前的 resume-generator 是一个命令行工具——在终端里运行就行。但这次我们要做一个有界面的桌面应用,所以需要更多"层"。

graph LR subgraph "命令行工具(之前)" T1["终端"] --> S1["SDK"] --> O1["输出文件"] end
graph LR subgraph "桌面应用(现在)" R1["React 界面"] -->|"发送请求"| E1["Electron 主进程"] --> S2["SDK"] --> O2["输出文件"] O2 -->|"消息流"| E2["Electron 主进程"] -->|"返回结果"| R2["React 界面"] end

多了一个"界面层"和一个"中间层"。

Electron 是什么?

如果你没接触过 Electron,一句话解释:用网页技术(HTML/CSS/JS)做桌面应用的框架

VS Code、Discord、Slack——这些都是用 Electron 做的。它的原理是:

code
Electron = Chrome 浏览器(渲染网页)+ Node.js(访问系统功能)

你写的 React 代码 → 跑在 Chrome 里(渲染进程)
你写的 Node.js 代码 → 跑在 Node 里(主进程)
两边通过 IPC(进程间通信)交流

三层架构图

graph TD subgraph Electron["Electron 应用窗口"] subgraph React["渲染进程(React)"] CI["ChatInterface"] --> ML["MessageList"] --> MSG["Message"] MSG --> MSG1["文本内容"] MSG --> MSG2["工具展示"] MSG --> MSG3["文件下载"] MI["MessageInput"] MI --> MI1["文字输入"] MI --> MI2["文件上传(拖放)"] end React -->|"IPC 通信"| Main subgraph Main["主进程(Node.js)"] RCV["接收消息"] --> SAVE["保存上传文件"] RCV --> SDK_CALL["调用 Claude Agent SDK"] SDK_CALL --> QUERY["query() 发送任务给 AI"] SDK_CALL --> AWAIT["for await 接收消息流"] AWAIT --> FWD["转发给渲染进程显示"] DETECT["检测输出文件"] --> NOTIFY["通知渲染进程显示下载按钮"] end Main --> AI subgraph AI["AI 后端(Claude SDK + Python)"] MODEL["Claude 模型"] MODEL --> SK["读取 xlsx Skill"] MODEL --> UNDERSTAND["理解用户需求"] MODEL --> WRITE["写 Python 代码(openpyxl/pandas)"] MODEL --> EXEC["Bash 执行 Python"] MODEL --> OUTPUT["产出 .xlsx 文件"] end end

数据怎么流的?

用一个具体例子来跟踪数据流:用户说"帮我做个预算表"。

graph TD A["第 1 步:用户输入<br/>MessageInput 组件<br/>「帮我做个预算表」+ 发送"] B["第 2 步:主进程接收<br/>main.ts 的 ipcMain.on()<br/>收到消息,调用 SDK query()"] C["第 3 步:AI 开始工作<br/>Claude 模型<br/>思考 → 读取 xlsx 技能 → 写 Python 代码<br/>→ Bash 执行代码 → 产出 budget.xlsx<br/>每一步都发出一条消息"] D["第 4 步:主进程转发消息<br/>main.ts 的 for await 循环<br/>收到消息 → event.reply() → UI"] E["第 5 步:前端更新界面<br/>ChatInterface 组件<br/>监听 IPC → 更新 messages 状态<br/>→ MessageList 重新渲染<br/>→ 显示 AI 回复 + 工具过程"] F["第 6 步:文件检测 + 下载<br/>main.ts 检测到新的 .xlsx 文件<br/>→ IPC 通知 → React 显示下载按钮"] A -->|"IPC: claude-code:query"| B B -->|"query({ prompt, options })"| C C -->|"消息流(一条一条的)"| D D -->|"IPC: claude-code:response"| E E --> F

IPC 通信:前端和后端怎么说话?

Electron 的前端(渲染进程)和后端(主进程)不能直接互相调用函数。它们靠"IPC 通道"通信,就像发消息一样:

code
IPC 通道一览:

前端 → 后端:
  'claude-code:query'        → 发送用户消息
  'download-file'            → 请求下载某个文件
  'open-output-directory'    → 打开输出文件夹

后端 → 前端:
  'claude-code:response'     → AI 的每条回复
  'claude-code:error'        → 发生了错误
  'claude-code:output-files' → 有新文件生成了

打个比方:IPC 就像对讲机。前端拿一个对讲机说"用户要做预算表",后端的对讲机听到后开始干活,干完了拿对讲机说"文件做好了"。

为什么不用 Web 应用?

你可能会问:"为什么做桌面应用?做个网页不行吗?"

可以做网页,但桌面应用有几个优势:

桌面应用(Electron) Web 应用
文件访问 可以直接读写本地文件 受浏览器沙箱限制
Python 执行 可以直接跑 Python 需要后端服务器
离线使用 除了 AI 调用,其他都可离线 需要网络
用户体验 像原生应用 在浏览器标签里

对于需要操作本地文件和运行本地代码的 AI 工具来说,桌面应用是更自然的选择。

本课小结

  • 三层架构:React(界面)→ Electron 主进程(桥梁)→ Claude SDK(AI)
  • 数据流:用户输入 → IPC → SDK → AI 工作 → 消息流 → IPC → 界面更新
  • IPC 是前端和后端的通信方式,像对讲机
  • Electron 让你用网页技术做桌面应用

课后练习

  1. 打开 Chrome 开发者工具(在 Electron 窗口里按 Ctrl+Shift+I),看看 Console 里有没有日志
  2. 在脑子里跟踪一遍数据流:你输入一句话 → 它经过了哪些组件 → 最终显示在哪里
  3. 想一想:如果你要把这个应用改成 Web 版(不用 Electron),哪些部分需要改?

沿着当前专题继续,或返回课程目录重新整理阅读顺序。

返回课程目录