# GO-DESK-10: SFTP 直连支持

> 版本: v0.5.0 | 状态: 开发中 | 分支: fs-only-v3 → u-desk-sftp

## 概述

为 U-Desk 新增 **SFTP（SSH File Transfer Protocol）直连模式**，作为第三种文件系统传输方式，与现有的本地模式（Wails IPC）和远程 HTTP Agent 模式并列。

用户无需在目标机器部署任何 Agent 服务，仅需 SSH 账号即可直接浏览和操作远程 Linux 服务器的文件系统。

## 架构设计

```
FsTransport (interface)
├── WailsTransport     → Wails IPC → FileSystemService (本地 os)
├── HttpTransport      → HTTP REST → u-fs-agent (远程部署)
└── SftpTransport [NEW]→ Wails IPC → SftpService → pkg/sftp (SSH/SFTP)
```

**核心原则**：复用现有 `FsTransport` 接口抽象，前端组件无感知。

## 技术方案

### 后端（Go）

**新增依赖**：
- `github.com/pkg/sftp` — SFTP 客户端库
- `golang.org/x/crypto/ssh` — SSH 协议（已在 indirect 中，提升为 direct）

**新增包 `internal/sftp/`**（4 个文件）：

| 文件 | 职责 |
|------|------|
| `config.go` | `Config` 结构体：Host/Port/Username/Password/KeyPath/Timeout |
| `client.go` | `Manager`（sync.Map 连接池，以 host:port 为 key）+ `Client`（单连接封装：SSH 握手、健康检查、自动重连、双认证） |
| `service.go` | `Service`（对齐 FileSystemService 返回格式的文件操作方法） |
| `errors.go` | `ConnectionError` + `ToUserMessage()` 中文友好错误映射 |

**连接管理**：
- 以 `host:port` 为 key 的 `sync.Map` 连接池
- 支持密码认证 / 私钥文件认证（二选一）
- `WithRetry(fn)` — 操作前检查健康度，断线自动重连（3 次，指数退避）
- `IsHealthy()` — 通过 `Stat("/")` 探测
- 切换 profile 时复用已有连接（避免重复 SSH 握手）

**App 层新增绑定方法**（12 个）：

```
SftpConnect(req)        → connID          建立连接
SftpDisconnect(connID)                   断开连接
SftpListDir(connID, path)                列目录
SftpReadFile(connID, path)              读文件
SftpWriteFile(req)                       写文件
SftpGetFileInfo(connID, path)            文件信息
SftpCreateDir(connID, path)             创建目录
SftpCreateFile(connID, path)            创建文件
SftpDeletePath(connID, path)            删除
SftpRenamePath(req)                     重命名
SftpDownloadToTemp(connID, path)        下载到临时目录（预览用）
SftpGetCommonPaths(connID)              远程主机常用路径
```

### 前端（TypeScript/Vue）

**新建 `frontend/src/api/sftp-transport.ts`**：
- 实现 `FsTransport` 接口的完整 23 个方法
- `connect()/disconnect()/requireConn()` 管理 SFTP 会话生命周期
- `downloadForPreview(remotePath)` 下载远程文件到本地临时目录（带缓存）
- ZIP/回收站/openPath 等不适用方法抛出 "暂未实现"

**修改 `connection-manager.ts`**：
- `ConnectionType` 扩展：`'local' | 'remote' | 'sftp'`
- `ConnectionProfile` 新增字段：`username?`, `password?`, `keyPath?`
- `applyActive()` 增加 sftp 分支（创建 SftpTransport → connect() → 连通性检查）
- 新增 `isSftp()` 方法
- `isRemote()` 扩展为包含 `'sftp'`
- `getFileServerBaseURL()` sftp 模式返回 `'http://localhost:8073'`
- 切换/删除 profile 时显式断开 SFTP 连接

**修改 `ConnectionDialog.vue`**：
- 新增连接类型选择器（RadioGroup: HTTP Agent / SFTP）
- SFTP 类型显示额外字段：用户名（默认 root）、密码、私钥路径
- HTTP Agent 类型保持原有 Token 字段
- 默认端口根据类型切换（SFTP=22，HTTP=9876）
- `addProfile` 时 type 使用表单选择的值

**修改 `ConnectionIndicator.vue`**：
- `.dot.sftp` 样式（紫色 `#7c3aed`）
- `dotClass(p)` 函数返回 local/remote/sftp
- "更多操作"按钮条件从 `type === 'remote'` 改为 `type !== 'local'`

**修改 `Sidebar.vue`**：
- 模式标签区分显示：本地(绿)/远程(蓝)/SFTP(紫)
- 新增 `isSftp` 响应式变量

**修改 `useFilePreview.ts`**：
- SFTP 模式下 `updatePreviewUrl()` 先调用 `downloadForPreview()` 下载到本地临时目录
- 下载完成后使用 `http://localhost:8073/localfs/{temp_path}` 预览（复用现有 LocalFileServer）
- 已下载文件缓存避免重复下载

### 文件预览方案

**策略：下载到本地临时目录 + 复用 LocalFileServer**

```
用户点击 SFTP 远程文件
  → useFilePreview 检测 isSftp()
  → SftpTransport.downloadForPreview(remotePath)
  → Go 后端 SFTP 下载到 %TEMP%/udesk-sftp-preview-{filename}
  → 返回本地绝对路径
  → 预览 URL = http://localhost:8073/localfs/{temp_path}
  → 现有 LocalFileServer 直接提供服务
```

**临时文件管理**：
- 启动时清理上次遗留的 `udesk-sftp-preview-*` 文件（`ServiceStartup` 中调用 `CleanupTempFiles()`）
- 关闭时清理本次创建的临时文件（`ServiceShutdown` 中调用）
- SftpTransport 内部维护 remotePath → localTempPath 缓存

## 文件变更清单

### 新增（5 个）

| 文件 | 说明 |
|------|------|
| `internal/sftp/config.go` | SFTP 配置结构体 |
| `internal/sftp/client.go` | SSH/SFTP 连接管理器 |
| `internal/sftp/service.go` | SFTP 文件操作服务 |
| `internal/sftp/errors.go` | 错误处理 + 用户友好消息 |
| `frontend/src/api/sftp-transport.ts` | 前端 FsTransport 实现 |

### 修改（8 个）

| 文件 | 变更 |
|------|------|
| `go.mod` | 添加 `github.com/pkg/sftp` 依赖 |
| `internal/filesystem/fs.go` | `formatBytes` 导出为 `FormatBytes` |
| `app.go` | +sftpService 字段 + 12 个绑定方法 + Shutdown 扩展 + 清理临时文件 |
| `frontend/src/api/connection-manager.ts` | 类型扩展 + sftp 分支 + isSftp() |
| `frontend/src/.../ConnectionDialog.vue` | 类型选择器 + SFTP 表单 |
| `frontend/src/.../ConnectionIndicator.vue` | sftp 样式 + dotClass |
| `frontend/src/.../Sidebar.vue` | SFTP 模式标签 |
| `frontend/src/.../useFilePreview.ts` | SFTP 下载预览 |

### 自动生成

| 文件 | 触发方式 |
|------|---------|
| `frontend/src/wailsjs/v3-bindings/u-desk/app.ts` | `wails dev` 启动时重新生成 |

## 与现有模式的对比

| 特性 | 本地 (WailsTransport) | 远程 (HttpTransport) | SFTP (SftpTransport) |
|------|----------------------|---------------------|---------------------|
| 协议 | Wails IPC | HTTP REST | SSH/SFTP |
| 目标要求 | 本地桌面 | 部署 u-fs-agent | SSH 服务 |
| 认证 | 无 | Bearer Token | 密码 / 私钥 |
| 文件预览 | LocalFileServer | Agent 反向代理 | 下载到临时目录 |
| ZIP 支持 | ✅ | ❌ | ❌ |
| 回收站 | ✅ | ❌ | ❌ |
| 延迟 | < 10ms | 取决于网络 | 取决于网络（首次握手 ~2s） |
| 连接复用 | N/A | 每次请求 HTTP | sync.Map 连接池 |

## 后续迭代方向

### Phase 2（体验完善）
- 密钥文件选择器（Wails OpenFileDialog）
- 断线重连 UI 提示 + 手动重连按钮
- 大文件传输进度显示
- saveBase64File 二进制上传支持

### Phase 3（高级功能）
- SSH known_hosts 安全验证（替换 InsecureIgnoreHostKey）
- TCP KeepAlive + 应用层心跳防空闲断开
- 端口转发（SOCKS5/本地转发）
- 符号链接处理选项
- 并发传输队列 + 带宽限制
