第 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 做的。它的原理是:
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 通道"通信,就像发消息一样:
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 让你用网页技术做桌面应用
课后练习
- 打开 Chrome 开发者工具(在 Electron 窗口里按 Ctrl+Shift+I),看看 Console 里有没有日志
- 在脑子里跟踪一遍数据流:你输入一句话 → 它经过了哪些组件 → 最终显示在哪里
- 想一想:如果你要把这个应用改成 Web 版(不用 Electron),哪些部分需要改?