feat: v0.3.0 版本 - SSH终端、SFTP、端口转发功能实现
核心功能: - SSH终端: 密码/密钥认证、XTerm.js终端、多主题配色、IP高亮 - SFTP管理: 双栏文件管理、上传下载、传输队列 - 端口转发: 本地/远程转发、SOCKS5动态代理、规则管理 架构改进: - 拆分大型组件 (SFTPView/TabBar/Sidebar) - 添加统一日志和错误处理 - 修复React严格模式下的终端黑屏问题 UI更新: - 新增端口转发视图 - 更新UI预览文档 - 左侧栏右键菜单支持SFTP快捷打开
This commit is contained in:
1 parent
cf1c054381
commit
484d850abd
105 files changed
+25330
-64
No files matched your search
@@ -0,0 +1,781 @@
|
||||
# AGENTS.md - OpsXShell 项目开发指南
|
||||
|
||||
本文件为 AI 编程助手提供项目开发指导。
|
||||
|
||||
---
|
||||
|
||||
## 项目概述
|
||||
|
||||
**OpsXShell** 是跨平台 SSH/SFTP 终端工具 · 简洁高效 · 安全可靠 · 基于 Electron + React + TypeScript 构建。
|
||||
|
||||
### 核心功能
|
||||
|
||||
- 🔐 **SSH 终端** - 支持密码、密钥、SSH Agent 多种认证方式
|
||||
- 📁 **SFTP 文件管理** - 双栏可视化文件管理,拖拽上传下载
|
||||
- 🔄 **端口转发** - 本地/远程端口转发支持
|
||||
- 📑 **多标签页** - 类浏览器标签管理,支持会话恢复
|
||||
- 🎨 **现代 UI** - 基于 Tailwind CSS 的深色主题界面
|
||||
- 🔒 **安全可靠** - 敏感数据使用 AES-256-GCM 加密存储
|
||||
- 🌐 **跨平台** - 支持 Windows、macOS、Linux
|
||||
- ⚡ **Zmodem 支持** - 集成 rz/sz 文件传输协议
|
||||
|
||||
### 当前版本状态
|
||||
|
||||
- **版本**: v1.0.0
|
||||
- **状态**: 已发布 (Release)
|
||||
- **已完成**: 核心 SSH/SFTP 功能、加密存储、多标签页系统、Zmodem 传输
|
||||
- **技术债务**: 部分组件仍在迁移到新的连接池架构
|
||||
|
||||
---
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 层级 | 技术 | 版本 |
|
||||
|------|------|------|
|
||||
| 框架 | Electron | 28+ |
|
||||
| 前端框架 | React | 18 |
|
||||
| 语言 | TypeScript | 5 |
|
||||
| 构建工具 | Vite | 5 |
|
||||
| 状态管理 | Zustand | 4.4+ |
|
||||
| 终端仿真 | XTerm.js | 5.5+ |
|
||||
| SSH/SFTP | ssh2 | 1.15+ |
|
||||
| Zmodem | zmodem-ts | 1.0.8 |
|
||||
| 数据库 | better-sqlite3 | 9.4+ |
|
||||
| 样式 | Tailwind CSS | 3.3+ |
|
||||
| 图标 | Lucide React | 0.294+ |
|
||||
| 测试 | Vitest | 4.1+ |
|
||||
|
||||
---
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
opsxshell/
|
||||
├── electron/ # Electron 主进程
|
||||
│ ├── main.ts # 主进程入口 - 窗口创建、应用生命周期、菜单
|
||||
│ ├── preload.ts # 上下文桥接,暴露 API 到渲染进程
|
||||
│ ├── tsconfig.json # 主进程 TS 配置 (CommonJS)
|
||||
│ ├── ipc/ # IPC 处理器(按功能模块组织)
|
||||
│ │ ├── index.ts # IPC 注册入口
|
||||
│ │ ├── ssh.ipc.ts # SSH 连接、Shell、端口转发
|
||||
│ │ ├── sftp.ipc.ts # SFTP 文件操作
|
||||
│ │ ├── session.ipc.ts # 会话 CRUD
|
||||
│ │ ├── settings.ipc.ts # 应用设置
|
||||
│ │ ├── dialog.ipc.ts # 文件对话框
|
||||
│ │ ├── file.ipc.ts # 文件系统操作
|
||||
│ │ ├── zmodem.ipc.ts # Zmodem 传输
|
||||
│ │ └── app.ipc.ts # 应用信息
|
||||
│ └── services/ # 主进程服务
|
||||
│ ├── ssh-service.ts # SSH 连接、Shell 会话、SFTP 操作
|
||||
│ ├── database-service.ts # SQLite 持久化存储
|
||||
│ ├── crypto.ts # AES-256-GCM 加密工具
|
||||
│ ├── key-service.ts # 主密钥管理
|
||||
│ └── zmodem-service.ts # Zmodem 文件传输服务
|
||||
├── src/ # 渲染进程 (React)
|
||||
│ ├── components/ # UI 组件
|
||||
│ │ ├── index.ts # 🆕 统一导出
|
||||
│ │ ├── App.tsx # 根组件
|
||||
│ │ ├── SessionDialog.tsx # 会话编辑对话框
|
||||
│ │ ├── QuickConnectDialog.tsx # 快速连接对话框
|
||||
│ │ ├── WelcomeView.tsx # 欢迎页面
|
||||
│ │ │
|
||||
│ │ ├── layout/ # 🆕 布局组件
|
||||
│ │ │ ├── Sidebar.tsx # 侧边栏
|
||||
│ │ │ ├── TabBar.tsx # 标签栏
|
||||
│ │ │ ├── TabContent.tsx # 标签内容
|
||||
│ │ │ └── StatusBar.tsx # 状态栏
|
||||
│ │ │
|
||||
│ │ ├── sftp/ # SFTP 模块
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ ├── SFTPView.tsx # SFTP 主视图
|
||||
│ │ │ ├── Breadcrumb.tsx
|
||||
│ │ │ ├── ConnectionSelector.tsx
|
||||
│ │ │ ├── ContextMenu.tsx
|
||||
│ │ │ ├── FileRow.tsx
|
||||
│ │ │ ├── SortHeader.tsx
|
||||
│ │ │ ├── TransferItem.tsx
|
||||
│ │ │ ├── types.ts
|
||||
│ │ │ └── utils.ts
|
||||
│ │ │
|
||||
│ │ ├── terminal/ # 终端模块
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ ├── TerminalView.tsx # 终端视图
|
||||
│ │ │ └── types.ts
|
||||
│ │ │
|
||||
│ │ ├── settings/ # 🆕 设置模块
|
||||
│ │ │ └── SettingsView.tsx # 设置页面
|
||||
│ │ │
|
||||
│ │ └── common/ # 🆕 通用组件
|
||||
│ │ ├── index.ts
|
||||
│ │ ├── Button.tsx # 按钮
|
||||
│ │ ├── Modal.tsx # 模态框
|
||||
│ │ ├── Loading.tsx # 加载状态
|
||||
│ │ └── ErrorBoundary.tsx # 错误边界
|
||||
│ ├── stores/ # Zustand 状态管理
|
||||
│ │ ├── index.ts # 🆕 统一导出
|
||||
│ │ ├── tabStore.ts # 标签页状态管理
|
||||
│ │ ├── settingsStore.ts # 应用设置
|
||||
│ │ ├── terminalSnapshotStore.ts # 终端快照
|
||||
│ │ └── connection/ # 连接管理架构
|
||||
│ │ ├── index.ts # 统一导出
|
||||
│ │ ├── types.ts # 类型定义
|
||||
│ │ ├── poolStore.ts # 连接池核心
|
||||
│ │ ├── lifecycleStore.ts # 连接生命周期
|
||||
│ │ ├── storeHooks.ts # Store Hooks
|
||||
│ │ └── sessionHelper.ts # 会话配置获取
|
||||
│ ├── themes/ # 主题配置
|
||||
│ │ └── terminal.ts # 终端主题
|
||||
│ │
|
||||
│ ├── hooks/ # 🆕 自定义 Hooks
|
||||
│ │ ├── index.ts # 统一导出
|
||||
│ │ ├── useTerminal.ts # 终端管理
|
||||
│ │ ├── useSFTP.ts # SFTP 操作
|
||||
│ │ ├── useConnection.ts # 连接管理
|
||||
│ │ └── useSettings.ts # 设置操作
|
||||
│ │
|
||||
│ ├── constants/ # 🆕 常量管理
|
||||
│ │ ├── index.ts # 统一导出
|
||||
│ │ ├── terminal.ts # 终端常量
|
||||
│ │ ├── sftp.ts # SFTP 常量
|
||||
│ │ └── keybindings.ts # 快捷键
|
||||
│ │
|
||||
│ ├── utils/ # 工具函数
|
||||
│ │ ├── index.ts # 统一导出
|
||||
│ │ ├── logger.ts # 统一日志系统
|
||||
│ │ ├── error-handler.ts # 统一错误处理
|
||||
│ │ ├── validators.ts # 输入校验
|
||||
│ │ └── formatters.ts # 格式化工具
|
||||
│ │
|
||||
│ ├── electron.d.ts # Electron API 类型声明
|
||||
│ ├── main.tsx # React 渲染入口
|
||||
│ └── index.css # 全局样式(Tailwind + 自定义样式)
|
||||
├── shared/ # 共享类型定义(主进程/渲染进程共用)
|
||||
│ └── types/
|
||||
│ ├── session.types.ts # 会话相关类型
|
||||
│ ├── connection.types.ts # 连接相关类型
|
||||
│ ├── sftp.types.ts # SFTP 类型
|
||||
│ ├── ipc.types.ts # IPC 类型
|
||||
│ └── index.ts # 统一导出
|
||||
├── test/ # 测试文件
|
||||
│ ├── setup.ts # 测试环境配置(mock Electron API)
|
||||
│ ├── stores/ # Store 测试
|
||||
│ │ ├── tabStore.test.ts
|
||||
│ │ └── connectionPool.test.ts
|
||||
│ └── components/sftp/ # 组件测试
|
||||
│ └── utils.test.ts
|
||||
├── dist/ # 前端构建输出 (Vite)
|
||||
├── dist-electron/ # 主进程构建输出
|
||||
├── package.json # 项目配置 + electron-builder 配置
|
||||
├── vite.config.ts # Vite 配置
|
||||
├── vitest.config.ts # Vitest 测试配置
|
||||
├── tailwind.config.js # Tailwind CSS 配置
|
||||
├── tsconfig.json # 渲染进程 TS 配置
|
||||
├── tsconfig.node.json # Node 类型配置
|
||||
├── index.html # 入口 HTML(含 CSP 配置)
|
||||
└── .eslintrc.cjs # ESLint 配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 架构详解
|
||||
|
||||
### Electron 双进程模型
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 主进程 (Node.js) │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
||||
│ │ ssh-service │ │ database │ │ key-service │ │
|
||||
│ │ (ssh2) │ │ (SQLite) │ │ (加密密钥管理) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │zmodem-service│ │ IPC │ │
|
||||
│ │ (zmodem-ts) │ │ Handlers │ │
|
||||
│ └─────────────┘ └──────┬──────┘ │
|
||||
│ │ │
|
||||
│ ipcMain.handle() / event.sender.send() │
|
||||
│ │ │
|
||||
│ ┌──────────────┴──────────────┐ │
|
||||
│ │ contextBridge │ │
|
||||
│ │ (electron/preload.ts) │ │
|
||||
│ └──────────────┬──────────────┘ │
|
||||
└──────────────────────────┼──────────────────────────────────┘
|
||||
│ IPC (安全隔离)
|
||||
┌──────────────────────────┼──────────────────────────────────┐
|
||||
│ 渲染进程 (Chromium) │
|
||||
│ ┌──────────────┴──────────────┐ │
|
||||
│ │ window.electronAPI │ │
|
||||
│ └──────────────┬──────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────┐ ┌──────┴──────┐ ┌─────────────────────┐ │
|
||||
│ │ React UI │ │ Zustand │ │ XTerm.js 终端 │ │
|
||||
│ │ 组件 │ │ Stores │ │ │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### IPC 通信接口
|
||||
|
||||
渲染进程通过 `window.electronAPI` 与主进程通信。完整类型定义在 `src/electron.d.ts`。
|
||||
|
||||
#### 核心 IPC 通道
|
||||
|
||||
```typescript
|
||||
// SSH 操作
|
||||
const { success, connectionId, error } = await window.electronAPI.sshConnect(config)
|
||||
const { success, shellId } = await window.electronAPI.sshCreateShell(connectionId, { cols, rows })
|
||||
await window.electronAPI.sshShellWrite(shellId, data)
|
||||
await window.electronAPI.sshShellResize(shellId, cols, rows)
|
||||
await window.electronAPI.sshDisconnect(connectionId)
|
||||
|
||||
// Shell 数据推送(主进程 → 渲染进程)
|
||||
window.electronAPI.onShellData((event, connectionId, shellId, data: Buffer) => {
|
||||
terminal.write(data)
|
||||
})
|
||||
|
||||
// SFTP 操作
|
||||
const { sftpId } = await window.electronAPI.sftpConnect(connectionId)
|
||||
const { files } = await window.electronAPI.sftpListDirectory(sftpId, path)
|
||||
await window.electronAPI.sftpUpload(sftpId, localPath, remotePath)
|
||||
|
||||
// 会话管理
|
||||
const { sessions } = await window.electronAPI.sessionGetAll()
|
||||
const { session } = await window.electronAPI.sessionCreate(sessionData)
|
||||
await window.electronAPI.sessionUpdate(id, updates)
|
||||
await window.electronAPI.sessionDelete(id)
|
||||
```
|
||||
|
||||
### 数据流
|
||||
|
||||
1. **SSH 连接流程**:
|
||||
```
|
||||
QuickConnectDialog/SessionDialog
|
||||
↓
|
||||
window.electronAPI.sshConnect()
|
||||
↓
|
||||
ipcMain.handle('ssh:connect')
|
||||
↓
|
||||
ssh-service.connect()
|
||||
↓
|
||||
ssh2.Client.connect()
|
||||
```
|
||||
|
||||
2. **Shell 数据流**:
|
||||
```
|
||||
ssh2 ClientChannel (data事件)
|
||||
↓
|
||||
event.sender.send('ssh:shellData')
|
||||
↓
|
||||
ipcRenderer.on('ssh:shellData')
|
||||
↓
|
||||
window.electronAPI.onShellData()
|
||||
↓
|
||||
XTerm.write(data)
|
||||
```
|
||||
|
||||
3. **会话持久化**:
|
||||
```
|
||||
connectionStore.addSession()
|
||||
↓
|
||||
window.electronAPI.sessionCreate()
|
||||
↓
|
||||
database-service.createSession()
|
||||
↓
|
||||
SQLite (密码加密存储)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 构建和运行命令
|
||||
|
||||
### 环境要求
|
||||
|
||||
- Node.js 18+
|
||||
- npm 9+ 或 yarn 1.22+
|
||||
|
||||
### 开发命令
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 启动开发服务器(Vite + Electron)
|
||||
npm run dev
|
||||
# 说明:会先启动 Vite dev server (端口 5173),然后启动 Electron
|
||||
|
||||
# 运行 ESLint 检查
|
||||
npm run lint
|
||||
|
||||
# 运行测试
|
||||
npm run test # 运行所有测试
|
||||
npm run test:watch # 监视模式
|
||||
npm run test:coverage # 生成覆盖率报告
|
||||
```
|
||||
|
||||
### 生产构建
|
||||
|
||||
```bash
|
||||
# 构建生产版本(前端 + Electron 主进程)
|
||||
npm run build
|
||||
|
||||
# 构建步骤分解:
|
||||
# 1. tsc - 检查渲染进程 TypeScript
|
||||
# 2. vite build - 构建前端到 dist/
|
||||
# 3. npm run build:electron - 编译主进程到 dist-electron/
|
||||
```
|
||||
|
||||
### 打包分发
|
||||
|
||||
```bash
|
||||
# 打包当前平台
|
||||
npm run dist
|
||||
|
||||
# 平台特定打包
|
||||
npm run dist:win # Windows (NSIS)
|
||||
npm run dist:mac # macOS (DMG, x64 + arm64)
|
||||
npm run dist:linux # Linux (AppImage)
|
||||
|
||||
# 输出目录: release/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 代码规范
|
||||
|
||||
### 文件组织
|
||||
|
||||
- **组件**: `src/components/*.tsx` - PascalCase 命名
|
||||
- **子组件目录**: `src/components/{feature}/*.tsx` - 功能相关子组件
|
||||
- **Stores**: `src/stores/*Store.ts` 或 `src/stores/{feature}/store.ts` - camelCase
|
||||
- **服务**: `electron/services/*-service.ts` - kebab-case
|
||||
- **IPC 处理器**: `electron/ipc/{feature}.ipc.ts` - kebab-case
|
||||
- **类型**: 共享类型定义在 `shared/types/*.types.ts`
|
||||
|
||||
### 命名约定
|
||||
|
||||
```typescript
|
||||
// 组件: PascalCase
|
||||
export default function TerminalView() {}
|
||||
|
||||
// Hooks: camelCase + use 前缀
|
||||
const useConnectionStore = create<ConnectionState>(...)
|
||||
|
||||
// 接口: PascalCase
|
||||
interface SSHConfig { ... }
|
||||
interface TabState { ... }
|
||||
|
||||
// 常量: UPPER_SNAKE_CASE (局部) 或 camelCase (模块级)
|
||||
const ALGORITHM = 'aes-256-gcm'
|
||||
const keyService = new KeyService()
|
||||
```
|
||||
|
||||
### 类型安全
|
||||
|
||||
- 所有 IPC 接口必须在 `src/electron.d.ts` 中声明
|
||||
- 共享类型定义在 `shared/types/` 目录
|
||||
- 使用严格的 TypeScript 配置 (`strict: true`)
|
||||
- Zustand Store 必须定义 State 接口
|
||||
|
||||
```typescript
|
||||
// 示例:完整的类型定义
|
||||
interface Tab {
|
||||
id: string
|
||||
title: string
|
||||
type: 'welcome' | 'terminal' | 'sftp' | 'settings'
|
||||
sessionId?: string
|
||||
connectionConfig?: SSHConnectionConfig
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
interface TabState {
|
||||
tabs: Tab[]
|
||||
activeTabId: string | null
|
||||
addTab: (tab: Omit<Tab, 'id' | 'isActive'>) => void
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### IPC 错误处理
|
||||
|
||||
所有 IPC 处理器必须返回统一的响应格式:
|
||||
|
||||
```typescript
|
||||
// 主进程
|
||||
ipcMain.handle('xxx:action', async (event, params) => {
|
||||
try {
|
||||
const result = await doSomething(params)
|
||||
return { success: true, data: result }
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
})
|
||||
|
||||
// 渲染进程
|
||||
const result = await window.electronAPI.xxxAction(params)
|
||||
if (result.success) {
|
||||
// 处理成功
|
||||
} else {
|
||||
// 处理错误: result.error
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 安全规范
|
||||
|
||||
### ✅ 必须遵守
|
||||
|
||||
1. **始终启用 contextIsolation**
|
||||
```typescript
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
contextIsolation: true, // ✅ 必须
|
||||
nodeIntegration: false, // ✅ 必须
|
||||
}
|
||||
```
|
||||
|
||||
2. **通过 preload 暴露 API**
|
||||
```typescript
|
||||
// preload.ts - 唯一使用 node 能力的地方
|
||||
contextBridge.exposeInMainWorld('electronAPI', api)
|
||||
```
|
||||
|
||||
3. **敏感数据加密**
|
||||
- 所有密码使用 `simpleEncrypt/simpleDecrypt` 加密存储
|
||||
- 主密钥存储在 `app.getPath('userData')/encryption.key`
|
||||
- 密钥文件权限设置为 `0o600`
|
||||
|
||||
4. **资源清理**
|
||||
```typescript
|
||||
app.on('before-quit', () => {
|
||||
sshService.disconnectAll()
|
||||
dbService?.close()
|
||||
})
|
||||
```
|
||||
|
||||
5. **CSP 配置**
|
||||
```html
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';">
|
||||
```
|
||||
|
||||
### ❌ 禁止行为
|
||||
|
||||
1. ❌ 在渲染进程直接使用 `require('ssh2')` 或其他 Node 模块
|
||||
2. ❌ 暴露 `nodeIntegration: true` 或 `contextIsolation: false`
|
||||
3. ❌ 明文存储密码或密钥
|
||||
4. ❌ 忽略 SSH 连接关闭(会导致资源泄漏)
|
||||
5. ❌ 事件监听不清理(组件卸载时调用 `removeAllListeners`)
|
||||
|
||||
---
|
||||
|
||||
## 日志与错误处理
|
||||
|
||||
### 统一日志系统
|
||||
|
||||
使用 `src/utils/logger.ts` 替代 `console.log`:
|
||||
|
||||
```typescript
|
||||
import { createLogger } from '../utils/logger'
|
||||
|
||||
const logger = createLogger('ModuleName')
|
||||
|
||||
// 使用
|
||||
logger.debug('调试信息', { detail: 'xxx' })
|
||||
logger.info('操作成功', { id: 'xxx' })
|
||||
logger.warn('警告信息')
|
||||
logger.error('操作失败', error)
|
||||
```
|
||||
|
||||
**日志级别**:
|
||||
- `DEBUG` - 开发调试信息(生产环境不输出)
|
||||
- `INFO` - 关键操作记录
|
||||
- `WARN` - 警告信息
|
||||
- `ERROR` - 错误信息
|
||||
|
||||
### 统一错误处理
|
||||
|
||||
使用 `src/utils/error-handler.ts`:
|
||||
|
||||
```typescript
|
||||
import { handleError, createError, ErrorCode } from '../utils/error-handler'
|
||||
|
||||
// 1. 转换未知错误
|
||||
try {
|
||||
await riskyOperation()
|
||||
} catch (error) {
|
||||
const appError = handleError(error, 'context')
|
||||
// appError.message - 用户友好消息
|
||||
// appError.recoverable - 是否可恢复
|
||||
// appError.action - 建议操作
|
||||
}
|
||||
|
||||
// 2. 创建特定错误
|
||||
const error = createError(ErrorCode.CONNECTION_TIMEOUT, 'detail')
|
||||
```
|
||||
|
||||
**错误码分类**:
|
||||
- `1xxx` - 连接错误
|
||||
- `11xx` - SSH 错误
|
||||
- `12xx` - SFTP 错误
|
||||
- `13xx` - 会话错误
|
||||
- `14xx` - 验证错误
|
||||
- `9xxx` - 系统错误
|
||||
|
||||
---
|
||||
|
||||
## 数据库架构
|
||||
|
||||
SQLite 数据库存储在 `app.getPath('userData')/opsxshell.db`:
|
||||
|
||||
### 表结构
|
||||
|
||||
```sql
|
||||
-- 会话表
|
||||
CREATE TABLE sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
host TEXT NOT NULL,
|
||||
port INTEGER DEFAULT 22,
|
||||
username TEXT NOT NULL,
|
||||
auth_type TEXT CHECK(auth_type IN ('password', 'key', 'agent')),
|
||||
password TEXT, -- 加密存储
|
||||
private_key_path TEXT, -- 私钥路径(不加密)
|
||||
passphrase_encrypted TEXT, -- 私钥密码(加密)
|
||||
group_name TEXT DEFAULT 'default',
|
||||
tags TEXT, -- JSON 数组
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 连接历史表
|
||||
CREATE TABLE connection_history (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT REFERENCES sessions(id),
|
||||
connected_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
disconnected_at DATETIME,
|
||||
duration INTEGER,
|
||||
status TEXT
|
||||
);
|
||||
|
||||
-- 设置表
|
||||
CREATE TABLE settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT, -- JSON 格式
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 密钥表
|
||||
CREATE TABLE keys (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT CHECK(type IN ('rsa', 'ed25519', 'ecdsa')),
|
||||
private_key_encrypted TEXT NOT NULL,
|
||||
public_key TEXT,
|
||||
passphrase_encrypted TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 端口转发规则表
|
||||
CREATE TABLE port_forwards (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT REFERENCES sessions(id),
|
||||
type TEXT CHECK(type IN ('local', 'remote', 'dynamic')),
|
||||
local_host TEXT,
|
||||
local_port INTEGER,
|
||||
remote_host TEXT,
|
||||
remote_port INTEGER,
|
||||
is_active BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 测试框架
|
||||
|
||||
- **测试运行器**: Vitest
|
||||
- **测试环境**: jsdom
|
||||
- **断言库**: @testing-library/jest-dom
|
||||
- **React 测试**: @testing-library/react
|
||||
|
||||
### 测试文件位置
|
||||
|
||||
```
|
||||
test/
|
||||
├── setup.ts # 测试环境配置
|
||||
├── stores/ # Store 测试
|
||||
│ ├── tabStore.test.ts
|
||||
│ └── connectionPool.test.ts
|
||||
└── components/ # 组件测试
|
||||
└── sftp/
|
||||
└── utils.test.ts
|
||||
```
|
||||
|
||||
### 编写测试
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { useTabStore } from '../../src/stores/tabStore'
|
||||
|
||||
describe('TabStore', () => {
|
||||
beforeEach(() => {
|
||||
// Reset store to initial state
|
||||
useTabStore.setState({
|
||||
tabs: [{ id: 'welcome', title: '欢迎', type: 'welcome', isActive: true }],
|
||||
activeTabId: 'welcome',
|
||||
})
|
||||
})
|
||||
|
||||
it('应该添加新的终端标签', () => {
|
||||
const { addTab } = useTabStore.getState()
|
||||
addTab({ title: 'Terminal', type: 'terminal' })
|
||||
|
||||
const { tabs } = useTabStore.getState()
|
||||
expect(tabs).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Mock Electron API
|
||||
|
||||
测试环境中已配置完整的 Electron API mock,详见 `test/setup.ts`。
|
||||
|
||||
---
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 添加新的 IPC 接口
|
||||
|
||||
1. **在 `src/electron.d.ts` 添加类型声明**:
|
||||
```typescript
|
||||
interface ElectronAPI {
|
||||
newMethod: (param: string) => Promise<IPCResponse>
|
||||
}
|
||||
```
|
||||
|
||||
2. **在 `electron/preload.ts` 暴露 API**:
|
||||
```typescript
|
||||
const api = {
|
||||
newMethod: (param: string) => ipcRenderer.invoke('xxx:newMethod', param),
|
||||
}
|
||||
```
|
||||
|
||||
3. **在 `electron/ipc/` 添加处理器**:
|
||||
```typescript
|
||||
// electron/ipc/feature.ipc.ts
|
||||
export const FEATURE_CHANNELS = {
|
||||
NEW_METHOD: 'xxx:newMethod',
|
||||
} as const
|
||||
|
||||
export function registerFeatureIPCHandlers(options: Options): void {
|
||||
ipcMain.handle(FEATURE_CHANNELS.NEW_METHOD, async (event, param) => {
|
||||
try {
|
||||
const result = await doSomething(param)
|
||||
return { success: true, data: result }
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
4. **在 `electron/ipc/index.ts` 注册处理器**:
|
||||
```typescript
|
||||
import { registerFeatureIPCHandlers } from './feature.ipc'
|
||||
|
||||
export function registerAllIPCHandlers(options: IPCOptions): void {
|
||||
registerFeatureIPCHandlers({ ... })
|
||||
}
|
||||
```
|
||||
|
||||
### 添加新的 UI 组件
|
||||
|
||||
1. 在 `src/components/` 创建组件文件(PascalCase)
|
||||
2. 如果需要状态,在 `src/stores/` 创建对应的 Store
|
||||
3. 在 `TabContent.tsx` 添加渲染逻辑(如果是新标签类型)
|
||||
|
||||
### 添加终端主题
|
||||
|
||||
1. 在 `src/themes/terminal.ts` 添加主题配置:
|
||||
```typescript
|
||||
'my-theme': {
|
||||
name: 'My Theme',
|
||||
background: '#xxxxxx',
|
||||
foreground: '#xxxxxx',
|
||||
// ... 所有必需的颜色
|
||||
}
|
||||
```
|
||||
|
||||
2. 在 `src/stores/settingsStore.ts` 的 `terminalTheme` 类型中添加新主题
|
||||
|
||||
---
|
||||
|
||||
## 连接池架构(新)
|
||||
|
||||
项目正在迁移到新的连接池架构,位于 `src/stores/connection/`。
|
||||
|
||||
### 连接状态
|
||||
|
||||
```typescript
|
||||
type ConnectionState =
|
||||
| 'disconnected' // 初始/已关闭
|
||||
| 'connecting' // 正在连接
|
||||
| 'connected' // 活跃连接
|
||||
| 'background' // 后台保持
|
||||
| 'suspended' // 挂起(长时间未操作)
|
||||
| 'error' // 连接错误
|
||||
| 'reconnecting' // 正在重连
|
||||
```
|
||||
|
||||
### 使用新的连接管理
|
||||
|
||||
```typescript
|
||||
import { useConnectionStore, getSessionConfig } from '../stores/connection'
|
||||
|
||||
// 在组件中使用
|
||||
const connectionStore = useConnectionStore()
|
||||
|
||||
// 创建连接
|
||||
const result = await connectionStore.createConnection(tabId, config)
|
||||
|
||||
// 断开连接
|
||||
await connectionStore.disconnectConnection(connectionId, 'reason')
|
||||
```
|
||||
|
||||
### 旧版兼容
|
||||
|
||||
`src/stores/connectionPool.ts` 提供向后兼容的导出,旧代码仍可继续使用:
|
||||
|
||||
```typescript
|
||||
import { useConnectionPool } from '../stores/connectionPool'
|
||||
// 实际指向新的实现
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
| 问题 | 解决方案 |
|
||||
|------|----------|
|
||||
| 开发时 Electron 无法加载页面 | 确保 Vite 开发服务器在端口 5173 运行 |
|
||||
| SSH 连接失败 | 检查主机、端口、认证方式配置;查看主进程控制台日志 |
|
||||
| XTerm.js 显示异常 | 确保容器有明确宽高,调用 `fitAddon.fit()` |
|
||||
| 数据库初始化失败 | 检查应用数据目录权限 (`app.getPath('userData')`) |
|
||||
| 加密解密失败 | 检查主密钥文件是否存在且权限正确 |
|
||||
| 构建后应用白屏 | 检查 `vite.config.ts` 中 `base: './'` 配置 |
|
||||
| better-sqlite3 构建失败 | 运行 `npm run rebuild` 或 `npx electron-rebuild` |
|
||||
|
||||
---
|
||||
|
||||
## 参考资源
|
||||
|
||||
- [Electron 文档](https://www.electronjs.org/docs)
|
||||
- [React 文档](https://react.dev)
|
||||
- [XTerm.js 文档](https://xtermjs.org)
|
||||
- [ssh2 文档](https://github.com/mscdex/ssh2)
|
||||
- [Zustand 文档](https://zustand.docs.pmnd.rs)
|
||||
- [Tailwind CSS 文档](https://tailwindcss.com/docs)
|
||||
- [better-sqlite3 文档](https://github.com/WiseLibs/better-sqlite3)
|
||||
- [zmodem-ts 文档](https://github.com/FGasper/zmodem-ts)
|
||||
Reference in new issue
Block a user