# 进程间通信

<cite>
**本文引用的文件**   
- [electron/main.ts](file://electron/main.ts)
- [electron/preload.ts](file://electron/preload.ts)
- [src/types/electron.d.ts](file://src/types/electron.d.ts)
- [src/components/layout/app-layout.tsx](file://src/components/layout/app-layout.tsx)
- [src/components/layout/right-sidebar.tsx](file://src/components/layout/right-sidebar.tsx)
- [src/components/onboarding.tsx](file://src/components/onboarding.tsx)
- [scripts/build-electron.mjs](file://scripts/build-electron.mjs)
- [package.json](file://package.json)
</cite>

## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)

## 简介
本文件聚焦于本项目的Electron主进程与渲染进程间的进程间通信（IPC）实现，系统性解析以下主题：
- 主进程ipcMain.handle注册机制与事件处理器实现
- 数据传递协议与类型约束
- 文件系统监视、剪贴板操作、对话框交互等具体IPC接口
- 预加载脚本的安全隔离机制、上下文注入与API暴露策略
- 错误处理、超时控制与通信协议设计
- IPC性能优化、消息队列管理与并发控制
- IPC调试技巧、安全最佳实践与扩展开发指南

## 项目结构
本项目采用“主进程+预加载桥接+渲染进程”的经典Electron架构：
- 主进程负责系统级能力（文件系统、剪贴板、对话框、子进程、网络服务等）
- 预加载脚本通过contextBridge在受限上下文中向渲染进程暴露受控API
- 渲染进程通过ipcRenderer调用预加载暴露的方法，完成跨进程交互

```mermaid
graph TB
subgraph "主进程"
MAIN["electron/main.ts<br/>注册ipcMain.handle处理器"]
FS["文件系统监视"]
CLIP["剪贴板读写"]
DIALOG["对话框选择目录"]
SHELL["系统外壳操作"]
SERVER["独立Node服务器"]
end
subgraph "预加载"
PRELOAD["electron/preload.ts<br/>contextBridge.exposeInMainWorld"]
end
subgraph "渲染进程"
UI["React组件<br/>app-layout.tsx / onboarding.tsx / right-sidebar.tsx"]
end
UI --> PRELOAD
PRELOAD <-- "ipcRenderer.invoke/on" --> MAIN
MAIN --> FS
MAIN --> CLIP
MAIN --> DIALOG
MAIN --> SHELL
MAIN --> SERVER
```

图表来源
- [electron/main.ts:177-289](file://electron/main.ts#L177-L289)
- [electron/preload.ts:5-23](file://electron/preload.ts#L5-L23)
- [src/components/layout/app-layout.tsx:344-352](file://src/components/layout/app-layout.tsx#L344-L352)
- [src/components/onboarding.tsx:77-92](file://src/components/onboarding.tsx#L77-L92)
- [src/components/layout/right-sidebar.tsx:335-338](file://src/components/layout/right-sidebar.tsx#L335-L338)

章节来源
- [electron/main.ts:177-289](file://electron/main.ts#L177-L289)
- [electron/preload.ts:5-23](file://electron/preload.ts#L5-L23)
- [src/types/electron.d.ts:1-20](file://src/types/electron.d.ts#L1-L20)

## 核心组件
- 主进程处理器（ipcMain.handle）
  - 对话框：选择目录
  - 文件系统：递归监视目录变更并去抖通知
  - 剪贴板：macOS Finder复制文件路径读取与过滤
  - 系统外壳：打开路径、在资源管理器中显示条目
  - 应用数据目录：返回用户数据目录路径
- 预加载桥接（contextBridge）
  - 暴露统一的electronAPI对象，包含平台信息、数据目录、对话框、文件系统监听、剪贴板、外壳操作等方法
- 类型定义（electron.d.ts）
  - 为window.electronAPI提供完整的类型声明，确保TS在渲染侧进行静态校验

章节来源
- [electron/main.ts:179-270](file://electron/main.ts#L179-L270)
- [electron/preload.ts:5-23](file://electron/preload.ts#L5-L23)
- [src/types/electron.d.ts:1-20](file://src/types/electron.d.ts#L1-L20)

## 架构总览
下图展示从渲染进程发起调用到主进程执行系统操作的完整链路，并标注关键错误处理与超时控制点。

```mermaid
sequenceDiagram
participant R as "渲染进程<br/>React组件"
participant P as "预加载<br/>electron/preload.ts"
participant M as "主进程<br/>electron/main.ts"
participant FS as "文件系统"
participant CB as "剪贴板"
participant DLG as "对话框"
participant SH as "系统外壳"
R->>P : 调用 electronAPI.openDirectoryDialog()
P->>M : ipcRenderer.invoke("dialog : openDirectory")
M->>DLG : 显示目录选择对话框
DLG-->>M : 返回选中路径
M-->>P : Promise结果路径或空
P-->>R : Promise结果
R->>P : 调用 electronAPI.watchDirectory(dir)
P->>M : ipcRenderer.invoke("fs : watch", dir)
M->>FS : watch(dir, { recursive : true })
FS-->>M : 变更事件
M->>M : 去抖200ms合并事件
M-->>P : webContents.send("fs : changed")
R->>P : 订阅 fs : changed
P-->>R : onFsChanged(handler) 返回取消订阅函数
```

图表来源
- [electron/main.ts:179-219](file://electron/main.ts#L179-L219)
- [electron/preload.ts:9-15](file://electron/preload.ts#L9-L15)

章节来源
- [electron/main.ts:179-219](file://electron/main.ts#L179-L219)
- [electron/preload.ts:9-15](file://electron/preload.ts#L9-L15)

## 详细组件分析

### 组件A：对话框与目录选择
- 主进程处理器
  - 注册通道：dialog:openDirectory
  - 行为：弹出目录选择对话框，返回首个选中路径或空值
  - 安全要点：限定选择目录属性，避免文件选择；优先使用当前焦点窗口作为父窗口
- 预加载桥接
  - 暴露方法：openDirectoryDialog
  - 返回Promise<string|null>
- 渲染侧使用
  - 在应用布局与引导流程中调用，用于打开项目根目录

```mermaid
sequenceDiagram
participant UI as "渲染组件"
participant API as "electronAPI"
participant IPC as "ipcRenderer/ipcMain"
participant DLG as "对话框"
UI->>API : openDirectoryDialog()
API->>IPC : invoke("dialog : openDirectory")
IPC->>DLG : showOpenDialog({ properties : ["openDirectory"] })
DLG-->>IPC : { filePaths : [path] }
IPC-->>API : Promise.resolve(path)
API-->>UI : Promise.resolve(path)
```

图表来源
- [electron/main.ts:179-189](file://electron/main.ts#L179-L189)
- [electron/preload.ts:9](file://electron/preload.ts#L9)
- [src/components/layout/app-layout.tsx:344-352](file://src/components/layout/app-layout.tsx#L344-L352)
- [src/components/onboarding.tsx:77-92](file://src/components/onboarding.tsx#L77-L92)

章节来源
- [electron/main.ts:179-189](file://electron/main.ts#L179-L189)
- [electron/preload.ts:9](file://electron/preload.ts#L9)
- [src/components/layout/app-layout.tsx:344-352](file://src/components/layout/app-layout.tsx#L344-L352)
- [src/components/onboarding.tsx:77-92](file://src/components/onboarding.tsx#L77-L92)

### 组件B：文件系统监视与变更通知
- 主进程处理器
  - 注册通道：fs:watch
  - 行为：关闭旧监视器，对新路径开启递归监控；忽略特定目录；对频繁事件进行去抖（200ms），合并为单次通知
  - 通知通道：webContents.send("fs:changed")
- 预加载桥接
  - 暴露方法：watchDirectory、onFsChanged
  - onFsChanged返回取消订阅函数，便于组件卸载时清理
- 渲染侧使用
  - 在右侧文件树组件中订阅变更，触发UI刷新

```mermaid
flowchart TD
Start(["调用 watchDirectory(dir)"]) --> CloseOld["关闭旧FSWatcher"]
CloseOld --> Validate{"dir 是否有效？"}
Validate --> |否| End(["结束"])
Validate --> |是| Watch["watch(dir, recursive=true)"]
Watch --> OnChange["收到文件变更事件"]
OnChange --> Debounce{"是否已有去抖定时器？"}
Debounce --> |是| Reset["重置定时器=200ms"]
Debounce --> |否| Set["设置定时器=200ms"]
Reset --> Wait["等待"]
Set --> Wait
Wait --> Notify["webContents.send('fs:changed')"]
Notify --> End
```

图表来源
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [electron/preload.ts:10-15](file://electron/preload.ts#L10-L15)

章节来源
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [electron/preload.ts:10-15](file://electron/preload.ts#L10-L15)
- [src/components/layout/right-sidebar.tsx:335-338](file://src/components/layout/right-sidebar.tsx#L335-L338)

### 组件C：剪贴板与系统外壳
- 主进程处理器
  - 剪贴板读取：clipboard:readFiles（macOS下解析NSFilenamesPboardType，过滤临时/缓存路径，仅保留存在路径）
  - 剪贴板写入：clipboard:writeText
  - 系统外壳：shell:showInFolder、shell:openPath
- 预加载桥接
  - 暴露方法：readClipboardFiles、copyToClipboard、showInFolder、openPath
- 渲染侧使用
  - 在右侧文件树中调用showInFolder以在资源管理器中定位文件

```mermaid
sequenceDiagram
participant UI as "渲染组件"
participant API as "electronAPI"
participant IPC as "ipcRenderer/ipcMain"
participant CB as "剪贴板"
participant SH as "系统外壳"
UI->>API : readClipboardFiles()
API->>IPC : invoke("clipboard : readFiles")
IPC->>CB : read("NSFilenamesPboardType")macOS
CB-->>IPC : XML字符串含多个路径
IPC-->>API : 过滤后的路径数组
API-->>UI : Promise.resolve(paths)
UI->>API : showInFolder(path)
API->>IPC : invoke("shell : showInFolder", path)
IPC->>SH : showItemInFolder(path)
SH-->>IPC : 完成
IPC-->>API : Promise.resolve()
UI->>API : copyToClipboard(text)
API->>IPC : invoke("clipboard : writeText", text)
IPC->>CB : writeText(text)
CB-->>IPC : 完成
IPC-->>API : Promise.resolve()
```

图表来源
- [electron/main.ts:221-270](file://electron/main.ts#L221-L270)
- [electron/preload.ts:17-22](file://electron/preload.ts#L17-L22)

章节来源
- [electron/main.ts:221-270](file://electron/main.ts#L221-L270)
- [electron/preload.ts:17-22](file://electron/preload.ts#L17-L22)
- [src/components/layout/right-sidebar.tsx:335-338](file://src/components/layout/right-sidebar.tsx#L335-L338)

### 组件D：应用数据目录
- 主进程处理器
  - 注册通道：app:getDataPath
  - 行为：返回用户家目录下的应用数据路径（例如 ~/.forge）
- 预加载桥接
  - 暴露方法：getDataPath
- 渲染侧使用
  - 在设置页中读取并打开数据目录

章节来源
- [electron/main.ts:262-265](file://electron/main.ts#L262-L265)
- [electron/preload.ts:21](file://electron/preload.ts#L21)
- [src/components/views/settings-view.tsx:62](file://src/components/views/settings-view.tsx#L62)

### 组件E：预加载脚本与安全隔离
- 安全隔离
  - webPreferences启用contextIsolation，禁用nodeIntegration，sandbox可按需配置
- 上下文注入
  - 使用contextBridge.exposeInMainWorld将electronAPI注入到window
- API暴露策略
  - 仅暴露必要方法，参数与返回值均受控
  - 提供类型定义electron.d.ts，确保TS在渲染侧进行类型检查

章节来源
- [electron/main.ts:151-156](file://electron/main.ts#L151-L156)
- [electron/preload.ts:5-23](file://electron/preload.ts#L5-L23)
- [src/types/electron.d.ts:1-20](file://src/types/electron.d.ts#L1-L20)

## 依赖关系分析
- 构建与打包
  - 使用esbuild将main.ts与preload.ts分别构建为dist-electron/main.js与dist-electron/preload.js
  - package.json中main指向dist-electron/main.js
- 运行时依赖
  - Electron主进程运行时加载构建产物
  - 预加载脚本在渲染进程中通过webPreferences.preload注入

```mermaid
graph LR
ES["scripts/build-electron.mjs<br/>esbuild构建"] --> OUT1["dist-electron/main.js"]
ES --> OUT2["dist-electron/preload.js"]
PKG["package.json<br/>main指向dist-electron/main.js"] --> RUNTIME["Electron运行时"]
RUNTIME --> WIN["BrowserWindow"]
WIN --> PRELOAD["preload.js 注入"]
PRELOAD --> RENDER["渲染进程"]
```

图表来源
- [scripts/build-electron.mjs:7-32](file://scripts/build-electron.mjs#L7-L32)
- [package.json:12](file://package.json#L12)

章节来源
- [scripts/build-electron.mjs:7-32](file://scripts/build-electron.mjs#L7-L32)
- [package.json:12](file://package.json#L12)

## 性能考量
- 文件系统监视
  - 递归监控+忽略列表+去抖（200ms）降低事件风暴，减少渲染层抖动
  - 单一FSWatcher实例，切换目录时先关闭旧实例，避免资源泄漏
- 剪贴板读取（macOS）
  - 解析XML plist并过滤临时/缓存路径，仅返回真实存在的路径，避免无效IO
- 服务器启动与就绪检测
  - 启动独立Node服务器时，使用端口探测与轮询检测就绪状态，避免过早访问
- 并发与背压（扩展建议）
  - 当前IPC处理器为同步/异步任务，未见显式并发限制
  - 建议在需要的地方引入令牌桶/信号量或队列长度限制，防止系统资源被瞬时高负载占用

章节来源
- [electron/main.ts:38-71](file://electron/main.ts#L38-L71)
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [electron/main.ts:221-250](file://electron/main.ts#L221-L250)

## 故障排查指南
- 服务器启动失败
  - 现象：启动阶段抛错并弹出错误框
  - 排查：确认系统Node.js可用；检查PATH扩展逻辑；查看子进程输出
- 文件系统监视无响应
  - 现象：修改文件后未触发fs:changed
  - 排查：确认传入目录有效且不在忽略列表内；检查去抖时间是否过短导致事件被吞；验证主进程watch回调是否被异常捕获
- 剪贴板读取为空
  - 现象：readClipboardFiles返回[]
  - 排查：确认平台为macOS；确认Finder复制的是文件而非文本；检查过滤规则是否过于严格
- 对话框不显示
  - 现象：openDirectoryDialog无响应
  - 排查：确认主进程已注册对应handle；确认父窗口句柄正确；检查权限与沙盒配置
- 预加载API缺失
  - 现象：window.electronAPI未定义
  - 排查：确认webPreferences.preload正确；确认contextBridge注入成功；检查类型定义是否生效

章节来源
- [electron/main.ts:272-288](file://electron/main.ts#L272-L288)
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [electron/main.ts:221-250](file://electron/main.ts#L221-L250)
- [electron/preload.ts:5-23](file://electron/preload.ts#L5-L23)
- [src/types/electron.d.ts:17-19](file://src/types/electron.d.ts#L17-L19)

## 结论
本项目在Electron IPC方面实现了清晰的职责分离与安全隔离：
- 主进程集中处理系统能力，预加载脚本提供受控API，渲染进程通过类型化接口调用
- 关键功能（对话框、文件系统监视、剪贴板、系统外壳）均有明确的处理器与类型约束
- 安全方面采用contextIsolation与严格的API暴露策略
- 性能方面通过去抖与忽略列表缓解高频事件带来的开销
后续可在需要的场景引入并发控制与背压策略，进一步提升稳定性与可维护性。

## 附录

### IPC接口一览（通道名与用途）
- dialog:openDirectory：打开目录选择对话框，返回选中路径
- fs:watch：监视指定目录（递归），合并频繁变更事件
- clipboard:readFiles：读取系统剪贴板中的文件路径（macOS）
- clipboard:writeText：写入文本到剪贴板
- shell:showInFolder：在资源管理器中显示条目
- shell:openPath：使用系统默认程序打开路径
- app:getDataPath：返回应用数据目录路径

章节来源
- [electron/main.ts:179-270](file://electron/main.ts#L179-L270)

### 扩展开发指南
- 新增IPC处理器步骤
  - 在主进程注册ipcMain.handle("your:channel", handler)
  - 在预加载脚本暴露对应方法到electronAPI
  - 在渲染侧通过window.electronAPI调用，并添加类型定义
- 安全最佳实践
  - 始终启用contextIsolation，避免直接暴露Node.js全局
  - 严格校验输入参数，避免路径穿越与命令注入
  - 对外部系统调用（如shell.openPath）记录日志以便审计
- 调试技巧
  - 使用开发者工具观察IPC消息与错误堆栈
  - 在主进程为handle添加try/catch并记录错误上下文
  - 对高频事件（如文件变更）增加节流/去抖，避免UI卡顿