Private
Public Access
1
0
Files
u-desk/docs/04-功能迭代/GO-DESK-10.SFTP直连支持/开发经验.md

115 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SFTP 直连 + autoConnect 开发经验
> 日期2026-05-04 | 对应分支fs-only-v3
---
## 架构决策
### 1. 连接池模式替代单连接
**背景**: 原方案切换 profile 时断开旧连接再建新连接,切换慢且丢失状态。
**决策**: `Map<profileId, FsTransport>` 连接池。所有 profile 可同时在线,切换为 O(1)。
**关键实现**:
- `buildAndPool()`: 创建 transport 并入池
- `connect()`: 池中已有则直接复用,否则新建
- `disconnectProfile(id)`: 断开指定 profile从池移除
- `disconnectAll()`: 清空池,保留 local
### 2. 文件服务器 URL 集中管理
**背景**: 前端 8+ 处硬编码 `localhost:2652`,端口冲突时全部失效。
**决策**: 新建 `file-server.ts`,从后端动态获取 URL。
**原则**: **单一数据源**`connectionManager` 不再缓存 URL所有模块从 `file-server.ts` 读取。
### 3. 端口自动回退
**背景**: 端口被占用时应用崩溃,用户必须手动杀进程。
**决策**: `listenWithFallback(basePort, handler)` 尝试 basePort + 0..9,直接 `srv.Serve(l)` 消除 TOCTOU。
**关键**: 不用 `Listen → Close → ListenAndServe`(有竞态),改为把 listener 传给 `Serve`
---
## 踩坑记录
### 踩坑 1: autoConnect 不工作 — 三层嵌套根因
**现象**: 开启"启动时自动连接",重启后服务器不连接。手动点击则正常。
**排查过程**:
1. 加诊断日志 → 发现 `loadFromDB` 正常执行DB 返回 4 条记录
2. 日志显示 `lastConnected: null`(所有 profile`lc: false`
3. 原因链:
- Go JSON tag 是 `last_connected`,前端读 `p.lastConnected`**字段名不匹配**
- `SaveProfileRequest` 没有 `LastConnected` 字段 → **从未持久化**
- autoConnect 守卫 `p.lastConnected` → 即使修了前两层,旧数据仍是 null → **守卫逻辑有误**
**修复**:
```ts
// 1. 字段名 fallback
lastConnected: p.lastConnected || p.last_connected ? ... : undefined
// 2. persistProfile 传递 lastConnected
lastConnected: profile.lastConnected ? Math.floor(profile.lastConnected / 1000) : undefined
// 3. 去掉 lastConnected 守卫(核心修复)
if (p.type !== 'local') { // 原来是 p.type !== 'local' && p.lastConnected
```
**教训**: 数据链路问题要追踪完整链路:前端写入 → Go 接收 → DB 存储 → DB 读取 → Go 返回 → 前端读取。每一步都可能有字段名/类型/单位不匹配。
### 踩坑 2: Settings 弹窗被 overflow:hidden 裁剪
**现象**: 点击表头 `···` 按钮,弹窗不出现。
**根因**: `settings-panel``server-content``overflow: hidden`)内部。
**修复**: DOM 上移到 `server-content` 外部,改用 `position: absolute` 相对 `sidebar-section`
### 踩坑 3: More-menu 被收藏夹遮挡
**现象**: 最后一个服务器行的 `···` 菜单被收藏夹区块盖住。
**根因**: `.section-content``overflow: hidden`(用于折叠动画),裁剪了 `.more-menu`。同时服务器 section 的 z-index 低于收藏夹。
**修复**:
- `.server-content``overflow: visible` 覆盖通用规则
- 服务器 section 在菜单打开时加 `z-index: 30``.section-on-top` 类)
### 踩坑 4: require() 在 Vite ESM 构建中不可靠
**现象**: `const { getFileServerBaseURL } = require('./file-server')` 在 dev 模式正常production build 失败。
**修复**: 全部改为 ES 静态 `import`
### 踩坑 5: Go time.Unix 返回值不能直接赋给 *time.Time
```go
// ❌ 编译错误
p.LastConnected = time.Unix(*req.LastConnected, 0)
// ✅ 中间变量
t := time.Unix(*req.LastConnected, 0)
p.LastConnected = &t
```
---
## 最佳实践
### SFTP 二进制文件写入
前端剪贴板图片 → `canvas.toDataURL()` 得到 base64 → `SftpWriteBase64File` Go binding → base64 解码 → SFTP Create + Write。
### CSS overflow 与弹窗
`overflow: hidden` 用于折叠动画时,会裁剪子元素的 `position: absolute` 弹窗。解法:弹窗放在 overflow 容器外部,用 `position: absolute` 相对最近的 `position: relative` 祖先定位。
### z-index 层级管理
- 基础层: 1-10
- 弹出菜单/面板: 20-30
- 模态/对话框: 40-50
- 确保弹出元素的父容器也有足够的 z-index