48 KiB
u-fs-agent 远程文件服务 - 设计文档
目录
- 1. 项目概述
- 2. 架构设计
- 3. 技术决策
- 4. API 端点设计(MVP v0.1)
- 5. 后端实现细节
- 6. 前端改造方案
- 7. 目录结构
- 8. 分阶段实施计划
- 9. 安全设计
- 10. 部署方案
- 11. 配置说明
- 12. 测试策略
1. 项目概述
1.1 背景
u-desk 当前是一个基于 Wails 的桌面应用,所有文件操作通过 Wails Bindings 在本地执行。随着使用场景扩展,用户需要能够访问和管理远程服务器上的文件 -- 例如开发机、测试环境、生产服务器的文件系统。
1.2 目标
在 u-desk 同一 Git 仓库中新增 cmd/agent 入口,编译为独立的 HTTP 服务程序 u-fs-agent,部署到远端服务器后,本地 u-desk 通过 REST API 远程操作远端文件系统。
核心价值:
| 维度 | 本地模式 | 远程模式 |
|---|---|---|
| 访问范围 | 本地文件系统 | 远程服务器文件系统 |
| 通信方式 | Wails Bindings(进程内) | REST HTTP(网络) |
| 文件系统 | filesystem 包直接调用 | filesystem 包通过 HTTP 代理调用 |
| 用户体验 | 无感知切换 | Transport 抽象层自动路由 |
1.3 核心原则
- 单仓库双入口:
cmd/desk(桌面端)和cmd/agent(HTTP 服务)共存于同一仓库 - filesystem 零拷贝:
internal/filesystem包被两个入口共享引用,无代码复制 - 前端无感知:通过 Transport 抽象层,前端不关心底层是本地还是远程
- 渐进式增强:MVP 先跑通核心链路,后续 Wave 迭代增强能力
2. 架构设计
2.1 整体架构
┌─────────────────────────────────────────────────────────────────────┐
│ u-desk 前端 (Vue 3) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ FileExplorer │ │ Editor │ │ ConnectionManager │ │
│ └──────┬───────┘ └──────┬───────┘ └───────────┬───────────────┘ │
│ │ │ │ │
│ └────────┬────────┘ │ │
│ ▼ │ │
│ ┌───────────────┐ │ │
│ │ Transport │◄──────────────────────┘ │
│ │ (抽象接口) │ 根据 ConnectionProfile 选择实现 │
│ └───────┬───────┘ │
└──────────────────┼──────────────────────────────────────────────────┘
│ ┌──────────────────────────────┐
┌───────┴───────┐ │ │
▼ ▼ ▼ │
┌──────────────────┐ ┌────────────────────┐ │
│ WailsTransport │ │ HttpTransport │ REST over HTTP │
│ (本地 Wails 调用) │ │ (远程 HTTP 调用) │ Bearer Token │
└────────┬─────────┘ └────────┬───────────┘ │
│ │ │
▼ ▼ │
┌──────────────────┐ ┌──────────────────────────────────────────┐ │
│ cmd/desk │ │ cmd/agent (u-fs-agent) │ │
│ Wails 桌面端 │ │ Echo v4 HTTP 服务 │ │
│ main.go │ │ main.go │ │
└────────┬─────────┘ └─────────────────┬────────────────────────┘ │
│ │ │
└──────────────┬───────────────┘ │
▼ │
┌───────────────────────┐ │
│ internal/filesystem │ ◄── 共享包,零拷贝 │
│ (17 个 Go 源文件) │ │
│ │ │
│ ┌──────────────────┐ │ │
│ │FileSystemService │ │ │
│ ├──────────────────┤ │ │
│ │PathValidator │ │ │
│ │FileTypeManager │ │ │
│ │AssetHandler │ │ │
│ │AuditLogger │ │ │
│ │RecycleBin │ │ │
│ │FileLockChecker │ │ │
│ └──────────────────┘ │ │
└───────────────────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ 操作系统文件系统 │ ◄── 本地磁盘 / 远程服务器磁盘 │
└─────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
2.2 单仓库双入口模式
u-desk/
├── cmd/
│ ├── desk/
│ │ └── main.go # Wails 桌面端入口(已有)
│ └── agent/
│ └── main.go # u-fs-agent HTTP 服务入口(新增)
│
├── internal/
│ ├── filesystem/ # ★ 共享包:两个入口都引用
│ │ ├── service.go # FileSystemService 核心
│ │ ├── config.go # 安全/性能/功能配置
│ │ ├── path_validator.go # 路径安全验证
│ │ ├── filetype_manager.go # 文件类型管理
│ │ ├── asset_handler.go # 静态资源处理
│ │ ├── audit_log.go # 审计日志
│ │ ├── recycle_bin.go # 回收站
│ │ ├── file_lock.go # 文件锁检查
│ │ ├── content_detector.go # 内容类型检测
│ │ ├── zip.go / zip_helper.go # ZIP 操作
│ │ ├── directory_stats.go # 目录统计
│ │ ├── fs.go # 文件系统工具函数
│ │ ├── errors.go # 错误定义
│ │ ├── constants.go # 常量定义
│ │ └── logger.go # 日志工具
│ │
│ ├── agent/ # ★ Agent 专用模块(仅 agent 入口使用)
│ │ ├── config/ # Agent 配置加载
│ │ ├── handler/ # Echo Handler(HTTP → FileSystemService)
│ │ ├── middleware/ # 认证/CORS/恢复中间件
│ │ └── model/ # API 请求/响应模型
│ │
│ ├── app/ # 桌面端 App 结构体(仅 desk 使用)
│ ├── common/ # 公共工具
│ ├── service/ # 业务服务
│ ├── storage/ # 存储层
│ └── system/ # 系统信息
│
├── frontend/src/ # 前端 Vue 3 项目
│ └── api/
│ ├── transport.ts # ★ Transport 接口定义
│ ├── wails-transport.ts # ★ WailsTransport 实现
│ ├── http-transport.ts # ★ HttpTransport 实现
│ ├── connection-manager.ts # ★ 连接管理器
│ ├── types.ts # 类型定义
│ └── index.ts # 统一导出
│
└── configs/
└── agent.yaml # ★ Agent 运行时配置
关键约束:
internal/filesystem不导入任何 Wails 或 Echo 依赖,保持纯净internal/agent只导入internal/filesystem,不导入internal/appcmd/desk和cmd/agent编译产物完全独立,可分别部署
2.3 共享包零拷贝
filesystem 包的 17 个源文件在编译时被两个二进制文件共同链接,不存在运行时代码复制:
编译流程:
go build ./cmd/desk → u-desk.exe (包含 filesystem 全部代码)
go build ./cmd/agent → u-fs-agent (包含 filesystem 全部代码)
两者独立编译,各自包含一份 filesystem 的机器码
源码层面零拷贝 -- 同一套 .go 文件,import 路径一致
filesystem 包对外暴露的核心能力(Agent Handler 直接调用的方法):
| 方法 | 说明 | Agent 端点映射 |
|---|---|---|
ListDir(path) |
列出目录内容 | GET /api/v1/files/* |
GetFileInfo(path) |
获取文件/目录信息 | GET /api/v1/files/*?action=stat |
ReadFile(path) |
读取文件内容 | GET /api/v1/files/*/content |
WriteFile(path, content) |
写入文件内容 | PUT /api/v1/files/*/content |
CreateFile(path) |
创建空文件 | POST /api/v1/files/* (type=file) |
CreateDir(path) |
创建目录 | POST /api/v1/files/* (type=dir) |
DeletePath(path) |
删除文件/目录 | DELETE /api/v1/files/* |
RenamePath(old, new) |
重命名 | PATCH /api/v1/files/* |
SaveBase64File(path, b64) |
Base64 写入 | POST /api/v1/files/*/upload |
DetectContentType(data) |
内容类型检测 | GET /api/v1/files/*/detect-type |
StartLocalFileServer() |
启动内置文件服务器 | 代理端点内部调用 |
2.4 数据流
本地模式数据流:
用户点击文件 → Vue Component → WailsTransport.ReadFile(path)
→ window.go.main.App.ReadFile(path) // Wails Binding
→ app.go → filesystem.FileSystemService.ReadFile(path)
→ os.ReadFile(path) → 返回内容给前端
远程模式数据流:
用户点击文件 → Vue Component → HttpTransport.readFile(path)
→ fetch(`http://remote:9876/api/v1/files/${path}/content`, { headers: { Authorization: 'Bearer xxx' } })
→ [网络] → u-fs-agent Echo Router → handler.ReadFile
→ filesystem.FileSystemService.ReadFile(path)
→ os.ReadFile(path) → JSON Response → [网络] → 前端解析展示
3. 技术决策
| 决策项 | 选择 | 备选方案 | 选择理由 |
|---|---|---|---|
| 工程组织 | 单仓库双入口 | 独立仓库 / monorepo | filesystem 包零拷贝,避免同步噩梦;同一仓库便于 CI 统一管理 |
| HTTP 框架 | Echo v4 | Gin / Chi / net/http | 已在项目依赖树中;API 定义简洁优雅;中间件生态成熟 |
| 通信协议 | REST + JSON | gRPC / WebSocket | CRUD 天然映射 HTTP 动词;前端 fetch 原生支持;调试友好(curl 即可) |
| 认证方式 | Bearer Token (MVP) | JWT / OAuth2 / mTLS | MVP 最简实现;Token 可配置为空(内网信任场景);后续 Wave 4 升级 JWT |
| 前端架构 | Transport 接口 | 条件分支 / 策略模式 | Composable 设计,业务代码零感知;新增协议只需实现接口 |
| 配置格式 | YAML | JSON / TOML / 环境变量 | 可读性好;支持注释;Go 生态 yaml.v3 成熟 |
| 文件上传 | Base64 JSON | multipart/form-data | MVP 简化实现;与现有 Wails Base64 接口对齐;Wave 2 升级 multipart |
| 内嵌文件服务器 | 复用 AssetHandler | 独立代理服务 | 复用现有 8073 端口逻辑;HTML 预览代理无缝衔接 |
4. API 端点设计(MVP v0.1)
4.1 概览
| Method | Path | 说明 | Handler |
|---|---|---|---|
| GET | /api/v1/ping |
健康检查 | Ping |
| GET | /api/v1/info |
Agent 信息 | Info |
| GET | /api/v1/files/* |
列目录 / 文件信息 | ListOrStat |
| GET | /api/v1/files/*/content |
读文件内容 | ReadFile |
| PUT | /api/v1/files/*/content |
写文件内容 | WriteFile |
| POST | /api/v1/files/* |
新建文件/目录 | Create |
| DELETE | /api/v1/files/* |
删除 | Delete |
| PATCH | /api/v1/files/* |
重命名 | Rename |
| POST | /api/v1/files/*/upload |
Base64 上传 | Upload |
| GET | /api/v1/files/*/detect-type |
内容类型检测 | DetectType |
| GET | /api/v1/system/common-paths |
常用路径列表 | CommonPaths |
| GET | /api/v1/system/drives |
磁盘分区列表 | Drives |
| GET | /api/v1/proxy/localfs/* |
文件服务器代理 | FileServerProxy |
| GET | /api/v1/proxy/html-preview |
HTML 预览代理 | HTMLPreviewProxy |
Base URL: http://{host}:{port}/api/v1
默认端口: 9876
4.2 系统端点
GET /api/v1/ping
健康检查端点,用于负载均衡探活和客户端连通性检测。
请求:无参数
响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"status": "up",
"timestamp": "2026-04-26T10:30:00Z",
"version": "0.1.0"
}
}
GET /api/v1/info
返回 Agent 实例信息,用于前端展示连接状态和版本校验。
请求:无参数
响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"hostname": "dev-server-01",
"os": "linux",
"arch": "amd64",
"version": "0.1.0",
"uptime_seconds": 3600,
"root_path": "/home/user",
"features": {
"recycle_bin": true,
"audit_log": true,
"zip_extraction": true
}
}
}
4.3 文件操作端点
GET /api/v1/files/*
根据查询参数决定行为:默认列目录,?action=stat 返回单个文件信息。
路径参数:* -- 文件或目录路径(URL 编码)
查询参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| action | string | list |
list=列目录, stat=文件信息 |
列目录响应 (200):
{
"code": 0,
"message": "ok",
"data": [
{
"name": "src",
"path": "/home/user/project/src",
"is_dir": true,
"size": 4096,
"mod_time": "2026-04-26 10:00:00"
},
{
"name": "main.go",
"path": "/home/user/project/main.go",
"is_dir": false,
"size": 2048,
"mod_time": "2026-04-26 09:30:00"
}
]
}
文件信息响应 (200, ?action=stat):
{
"code": 0,
"message": "ok",
"data": {
"name": "main.go",
"path": "/home/user/project/main.go",
"size": 2048,
"size_str": "2 KB",
"is_dir": false,
"mod_time": "2026-04-26 09:30:00",
"mode": "0644"
}
}
GET /api/v1/files/*/content
读取文件文本内容(限制最大 10MB)。
路径参数:* -- 文件路径
响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"content": "package main\n\nimport \"fmt\"\n...",
"size": 2048,
"encoding": "utf-8"
}
}
错误 (413):文件超过 10MB 限制
{
"code": 41301,
"message": "文件过大 (15.2 MB),超过读取上限 (10 MB)"
}
PUT /api/v1/files/*/content
写入文件文本内容。
路径参数:* -- 目标文件路径
请求体:
{
"content": "package main\n\n..."
}
响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"path": "/home/user/project/main.go",
"name": "main.go",
"size": 2048,
"is_dir": false
}
}
POST /api/v1/files/*
新建文件或目录。
路径参数:* -- 目标路径
请求体:
{
"type": "file" // "file" | "dir"
}
响应 (201):
{
"code": 0,
"message": "created",
"data": {
"path": "/home/user/project/newfile.txt",
"name": "newfile.txt",
"size": 0,
"is_dir": false
}
}
DELETE /api/v1/files/*
删除文件或目录。
路径参数:* -- 要删除的路径
响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"path": "/home/user/project/oldfile.txt",
"name": "oldfile.txt",
"size": 1024,
"is_dir": false,
"deleted": true
}
}
PATCH /api/v1/files/*
重命名文件或目录。
路径参数:* -- 原路径
请求体:
{
"new_name": "renamed.txt" // 仅新名称(非完整路径)
}
响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"path": "/home/user/project/renamed.txt",
"name": "renamed.txt",
"old_path": "/home/user/project/oldfile.txt"
}
}
POST /api/v1/files/*/upload
以 Base64 编码上传二进制文件内容(图片等非文本文件)。
路径参数:* -- 目标文件路径
请求体:
{
"base64_content": "iVBORw0KGgoAAAANSUhEUgAA...",
"filename": "screenshot.png"
}
响应 (201):
{
"code": 0,
"message": "created",
"data": {
"path": "/home/user/images/screenshot.png",
"name": "screenshot.png",
"size": 15360,
"is_dir": false
}
}
GET /api/v1/files/*/detect-type
检测文件内容的实际类型(基于 magic bytes,而非扩展名)。
路径参数:* -- 文件路径
响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"detected_type": "image/png",
"extension": ".png",
"confidence": "high"
}
}
4.4 系统信息端点
GET /api/v1/system/common-paths
返回当前系统的常用路径列表(用户主目录、桌面、文档等)。
响应 (200):
{
"code": 0,
"message": "ok",
"data": {
"home": "/home/user",
"desktop": "/home/user/Desktop",
"documents": "/home/user/Documents",
"downloads": "/home/user/Downloads",
"temp": "/tmp"
}
}
GET /api/v1/system/drives
返回磁盘分区列表(Windows 显示 C:/ D:/ 等,Linux 显示挂载点)。
响应 (200):
{
"code": 0,
"message": "ok",
"data": [
{ "letter": "C:", "path": "C:\\", "total_gb": 256, "free_gb": 80, "fs_type": "NTFS" },
{ "letter": "D:", "path": "D:\\", "total_gb": 512, "free_gb": 200, "fs_type": "NTFS" }
]
}
4.5 代理端点
GET /api/v1/proxy/localfs/*
代理访问 Agent 内嵌的本地文件服务器(端口 8073),用于媒体文件预览。
路径参数:* -- 相对于文件服务器根目录的路径
行为:反向代理到 http://127.0.0.1:8073/{path},透传响应。
用途:前端 <img> / <video> 标签可直接引用此端点加载远程媒体资源。
GET /api/v1/proxy/html-preview?path=
代理渲染 HTML 文件预览,处理相对路径资源引用问题。
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| path | string | 是 | HTML 文件的绝对路径 |
行为:读取指定 HTML 文件,将其中相对路径的资源引用替换为 /api/v1/proxy/localfs/ 前缀后返回。
4.6 统一响应格式
所有 API 端点遵循统一的 JSON 响应信封:
interface ApiResponse<T = any> {
code: number; // 业务错误码,0 表示成功
message: string; // 人类可读消息
data: T; // 业务载荷(成功时),错误详情(失败时可选)
timestamp?: string; // 服务端时间戳(可选)
}
HTTP 状态码与业务 code 映射:
| HTTP Status | code | 场景 |
|---|---|---|
| 200 | 0 | 成功 |
| 201 | 0 | 创建成功 |
| 400 | 400xx | 请求参数错误 |
| 401 | 401xx | 未认证 / Token 无效 |
| 403 | 403xx | 无权限 / 路径禁止访问 |
| 404 | 404xx | 资源不存在 |
| 413 | 41301 | 文件过大 |
| 500 | 500xx | 服务器内部错误 |
4.7 错误码规范
0xxxx -- 成功
4xxxx -- 客户端错误
40001 -- 参数缺失
40002 -- 参数格式错误
40003 -- 路径格式非法
40101 -- Token 缺失
40102 -- Token 无效
40301 -- 路径越权(目录穿越)
40302 -- 敏感路径禁止访问
40303 -- 禁止的文件类型
40401 -- 文件/目录不存在
40402 -- 端点不存在
40901 -- 文件已存在(创建冲突)
41301 -- 文件超过大小限制
5xxxx -- 服务端错误
50001 -- 文件系统 I/O 错误
50002 -- 内部服务异常
5. 后端实现细节
5.1 Agent 入口 (cmd/agent/main.go)
Agent 的启动流程:
main()
├─ 1. 加载配置 (config.Load)
│ └─ 读取 configs/agent.yaml,缺失则使用默认值
│
├─ 2. 初始化 FileSystemService
│ └─ filesystem.NewFileSystemService(fsConfig)
│ ├─ PathValidator(路径安全验证)
│ ├─ FileTypeManager(文件类型管理)
│ ├─ AuditLogger(审计日志)
│ └─ RecycleBin(回收站)
│
├─ 3. 创建 Echo 实例 & 注册全局中间件
│ ├─ Recovery(panic 恢复)
│ ├─ RequestLogger(结构化请求日志)
│ ├─ CORS(跨域配置)
│ └─ Auth(Bearer Token 认证,可选)
│
├─ 4. 初始化 Handler & 注册路由
│ └─ handler.New(fsSvc, cfg) → 路由组注册
│
├─ 5. 启动 HTTP 服务器 (goroutine)
│ └─ e.Start(addr)
│
├─ 6. 启动内嵌文件服务器 (goroutine)
│ └─ filesystem.StartLocalFileServer() :8073
│
└─ 7. 信号监听 & 优雅关闭
├─ SIGINT / SIGTERM
├─ e.Shutdown(ctx) -- 10s 超时
├─ filesystem.ShutdownLocalFileServer()
└─ fsSvc.Close(ctx)
关键设计点:
- HTTP 服务器和内嵌文件服务器并行启动(两个 goroutine)
- 优雅关闭按序执行:先停 Echo(等待请求完成),再停文件服务器,最后释放 FileSystemService
- 所有启动步骤任一失败均
log.Fatalf退出,不做部分降级
5.2 配置管理 (internal/agent/config)
配置结构:
type Config struct {
Server ServerConfig `yaml:"server"` // HTTP 服务监听
Auth AuthConfig `yaml:"auth"` // Bearer Token
CORS CORSConfig `yaml:"cors"` // 跨域策略
Log LogConfig `yaml:"log"` // 日志配置
FileServer FileServerConfig `yaml:"file_server"` // 内嵌文件服务器
Security SecurityConfig `yaml:"security"` // 安全策略
}
配置加载策略:
- 优先读取
configs/agent.yaml(相对于工作目录) - 文件不存在时使用
Default()全部默认值 - YAML 解析失败返回错误(不静默降级)
- 支持环境变量覆盖(未来扩展点)
5.3 Handler 层 (internal/agent/handler)
Handler 是 HTTP 请求和 FileSystemService 之间的桥梁,职责单一:
Handler 职责:
1. 解析请求参数(路径、查询参数、请求体)
2. 调用 FileSystemService 对应方法
3. 将结果包装为统一 ApiResponse 格式
4. 将 filesystem 错误翻译为 HTTP 状态码 + 业务错误码
Handler 结构:
type Handler struct {
fsSvc *filesystem.FileSystemService
cfg *config.Config
}
func New(fsSvc *filesystem.FileSystemService, cfg *config.Config) *Handler
各 Handler 方法签名:
| 方法 | HTTP 方法 | 功能 | FileSystemService 调用 |
|---|---|---|---|
Ping(c echo.Context) |
GET | 健康检查 | 无(直接返回) |
Info(c echo.Context) |
GET | Agent 信息 | os.Hostname + runtime 信息 |
ListOrStat(c echo.Context) |
GET | 列目录/文件信息 | ListDir / GetFileInfo |
ReadFile(c echo.Context) |
GET | 读文件 | ReadFile |
WriteFile(c echo.Context) |
PUT | 写文件 | WriteFile |
Create(c echo.Context) |
POST | 新建 | CreateFile / CreateDir |
Delete(c echo.Context) |
DELETE | 删除 | DeletePath |
Rename(c echo.Context) |
PATCH | 重命名 | RenamePath |
Upload(c echo.Context) |
POST | Base64 上传 | SaveBase64File |
DetectType(c echo.Context) |
GET | 类型检测 | DetectContentType |
CommonPaths(c echo.Context) |
GET | 常用路径 | 系统函数 |
Drives(c echo.Context) |
GET | 磁盘列表 | 系统函数 |
FileServerProxy(c echo.Context) |
GET | 文件服务代理 | http.ReverseProxy |
HTMLPreviewProxy(c echo.Context) |
GET | HTML 预览 | ReadFile + 路径替换 |
5.4 中间件 (internal/agent/middleware)
Auth 中间件
func Auth(token string) echo.MiddlewareFunc
- 从
Authorization: Bearer <token>提取 Token - 与配置中的
auth.token比较 - 配置为空字符串时跳过认证(内网/开发模式)
- 认证失败返回
401 {"code": 40102, "message": "无效的认证令牌"}
CORS 中间件
- 使用 Echo 内置
middleware.CORS - 默认允许所有来源 (
*),生产环境应限定 - 允许的方法:GET/PUT/POST/DELETE/PATCH/OPTIONS
- 允许的头:Origin/Content-Type/Authorization/Accept
Recovery 中间件
- 使用 Echo 内置
middleware.Recovery - panic 时返回
500 {"code": 50002, "message": "内部服务异常"} - 不泄露堆栈信息到响应体
5.5 响应模型 (internal/agent/model/response.go)
// Response 统一 API 响应信封
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Timestamp time.Time `json:"timestamp,omitempty"`
}
// Ok 成功响应快捷构造
func Ok(data interface{}) *Response
// Created 创建成功响应快捷构造 (HTTP 201)
func Created(data interface{}) *Response
// BadRequest 错误响应 (400)
func BadRequest(code int, message string) *Response
// Unauthorized 未认证 (401)
func Unauthorized(message string) *Response
// Forbidden 禁止访问 (403)
func Forbidden(code int, message string) *Response
// NotFound 资源不存在 (404)
func NotFound(message string) *Response
// InternalError 内部错误 (500)
func InternalError(message string) *Response
6. 前端改造方案
6.1 Transport 接口定义
核心抽象 -- 所有文件操作通过 Transport 接口进行,前端业务组件不关心底层实现:
// frontend/src/api/transport.ts
/**
* 文件信息
*/
export interface FileInfo {
name: string
path: string
is_dir: boolean
size: number
size_str?: string
mod_time?: string
mode?: string
}
/**
* 文件操作结果
*/
export interface FileOperationResult {
path: string
name: string
size: number
size_str?: string
is_dir: boolean
mod_time?: string
mode?: string
old_path?: string
deleted?: boolean
}
/**
* Transport 抽象接口
* 定义所有文件操作的能力契约,本地和远程实现必须满足此接口
*/
export interface Transport {
// === 基础文件操作 ===
/** 列出目录内容 */
listDir(path: string): Promise<FileInfo[]>
/** 获取文件/目录详细信息 */
getFileInfo(path: string): Promise<FileInfo>
/** 读取文件文本内容 */
readFile(path: string): Promise<string>
/** 写入文件文本内容 */
writeFile(path: string, content: string): Promise<FileOperationResult>
/** 创建空文件 */
createFile(path: string): Promise<FileOperationResult>
/** 创建目录 */
createDir(path: string): Promise<FileOperationResult>
/** 删除文件或目录 */
deletePath(path: string): Promise<FileOperationResult>
/** 重命名文件或目录 */
renamePath(oldPath: string, newPath: string): Promise<FileOperationResult>
/** Base64 上传(二进制文件) */
saveBase64File(path: string, base64Content: string): Promise<FileOperationResult>
/** 检测文件内容类型 */
detectContentType(path: string): Promise<string>
// === 系统信息 ===
/** 获取常用路径 */
getCommonPaths(): Promise<Record<string, string>>
/** 获取磁盘列表 */
getDrives(): Promise<Array<{ letter: string; path: string; total_gb: number; free_gb: number }>>
// === 连接状态 ===
/** 检查连接是否可用 */
ping(): Promise<boolean>
/** 获取连接信息 */
getInfo(): Promise<Record<string, any>>
}
6.2 ConnectionProfile 数据模型
// frontend/src/api/connection-manager.ts (部分)
/**
* 连接配置文件
*/
export interface ConnectionProfile {
/** 唯一标识 */
id: string
/** 显示名称 */
name: string
/** 连接类型 */
type: 'local' | 'remote'
/** 远程连接配置 (type=remote 时必填) */
remote?: {
/** Agent 地址,如 http://192.168.1.100:9876 */
url: string
/** Bearer Token */
token: string
/** 连接超时 (ms) */
timeout?: number
}
/** 是否置顶 */
pinned?: boolean
/** 最后连接时间 */
lastConnectedAt?: string
/** 创建时间 */
createdAt: string
}
6.3 连接管理器
负责创建和管理 Transport 实例:
// frontend/src/api/connection-manager.ts
export class ConnectionManager {
private activeTransport: Transport | null = null
private profiles: Map<string, ConnectionProfile> = new Map()
/** 根据配置创建对应的 Transport 实现 */
createTransport(profile: ConnectionProfile): Transport {
if (profile.type === 'local' || !profile.remote) {
return new WailsTransport()
}
return new HttpTransport(profile.remote!)
}
/** 切换活跃连接 */
async switchConnection(profileId: string): Promise<void>
/** 获取当前活跃 Transport */
getTransport(): Transport
/** 测试连接是否可达 */
async testConnection(profile: ConnectionProfile): Promise<boolean>
/** 保存/更新配置 */
saveProfile(profile: ConnectionProfile): void
/** 删除配置 */
removeProfile(id: string): void
}
6.4 Pinia Store 设计
// frontend/src/stores/connection.ts
export const useConnectionStore = defineStore('connection', () => {
const manager = ref(new ConnectionManager())
const activeProfileId = ref<string>('local')
const isConnected = ref(true)
const connectionStatus = ref<'connected' | 'disconnected' | 'connecting' | 'error'>('connected')
const profiles = ref<ConnectionProfile[]>([
{ id: 'local', name: '本地文件系统', type: 'local', createdAt: '' }
])
/** 获取当前 Transport(供所有组件使用) */
function getTransport(): Transport {
return manager.value.getTransport()
}
/** 切换连接 */
async switchTo(profileId: string): Promise<void> { ... }
/** 刷新连接状态 */
async refreshStatus(): Promise<void> { ... }
return { activeProfileId, isConnected, connectionStatus, profiles, getTransport, switchTo, refreshStatus }
})
6.5 Toolbar 连接指示器
在 Toolbar 区域嵌入连接状态指示器,提供快速切换入口:
┌─────────────────────────────────────────────────────────┐
│ [📁] [🔍] [⚙] ... [● 本地文件系统 ▾] [➕ 添加连接] │
│ ↑ │
│ 连接指示器 │
│ ● 绿色 = 已连接 │
● ● 黄色 = 连接中 │
● ● 红色 = 断开 │
│ 点击展开连接列表 │
└─────────────────────────────────────────────────────────┘
交互行为:
- 默认显示「本地文件系统」(绿色圆点)
- 点击展开下拉列表,显示所有已保存的 ConnectionProfile
- 选择远程连接后,自动 ping 测试并切换 Transport
- 连接失败时红点闪烁,提示错误信息
- 右侧「+」按钮打开连接配置弹窗
6.6 文件变更清单
新增文件 (7):
| 文件 | 说明 |
|---|---|
frontend/src/api/transport.ts |
Transport 接口定义 |
frontend/src/api/wails-transport.ts |
WailsTransport 实现(封装现有 Wails 调用) |
frontend/src/api/http-transport.ts |
HttpTransport 实现(fetch + Bearer Token) |
frontend/src/api/connection-manager.ts |
连接管理器 |
frontend/src/stores/connection.ts |
Pinia Store |
frontend/src/components/ConnectionIndicator.vue |
连接状态指示器组件 |
frontend/src/components/ConnectionDialog.vue |
连接配置弹窗组件 |
改造文件 (5):
| 文件 | 改造内容 |
|---|---|
frontend/src/api/index.ts |
新增 export transport 相关模块 |
frontend/src/api/types.ts |
新增 ConnectionProfile 等类型 |
frontend/src/components/Toolbar.vue |
嵌入 ConnectionIndicator |
frontend/src/views/FileExplorer.vue |
从直接 Wails 调用改为 Transport 调用 |
frontend/src/views/EditorView.vue |
从直接 Wails 调用改为 Transport 调用 |
7. 目录结构
u-desk/
│
├── cmd/
│ ├── desk/
│ │ └── main.go # 现有 Wails 桌面端入口
│ │
│ └── agent/
│ └── main.go # ★ u-fs-agent HTTP 服务入口
│
├── internal/
│ ├── agent/ # ★ Agent 专用模块
│ │ ├── config/
│ │ │ └── config.go # 配置加载 (Server/Auth/CORS/Log/Security)
│ │ │
│ │ ├── handler/
│ │ │ └── handler.go # Echo Handler 集合
│ │ │ # (Ping/Info/ListOrStat/ReadFile/WriteFile/
│ │ │ # Create/Delete/Rename/Upload/DetectType/
│ │ │ # CommonPaths/Drives/FileServerProxy/HTMLPreviewProxy)
│ │ │
│ │ ├── middleware/
│ │ │ └── auth.go # Bearer Token 认证中间件
│ │ │
│ │ └── model/
│ │ └── response.go # 统一响应结构 (Response/Ok/Created/Error...)
│ │
│ ├── filesystem/ # ★ 共享包(17 文件,双入口共用)
│ │ ├── service.go # FileSystemService 核心
│ │ ├── config.go # 安全/性能/功能配置
│ │ ├── path_validator.go # 路径安全验证
│ │ ├── filetype_manager.go # 文件类型管理
│ │ ├── asset_handler.go # 静态资源处理
│ │ ├── audit_log.go # 审计日志
│ │ ├── recycle_bin.go # 回收站
│ │ ├── file_lock.go # 文件锁检查
│ │ ├── content_detector.go # 内容类型检测
│ │ ├── zip.go # ZIP 读取
│ │ ├── zip_helper.go # ZIP 辅助
│ │ ├── directory_stats.go # 目录统计
│ │ ├── fs.go # 文件系统工具
│ │ ├── errors.go # 错误定义
│ │ ├── constants.go # 常量
│ │ └── logger.go # 日志
│ │
│ ├── app/ # 桌面端 App(仅 desk 引用)
│ ├── common/ # 公共工具
│ ├── service/ # 业务服务
│ ├── storage/ # 存储层
│ └── system/ # 系统信息
│
├── configs/
│ └── agent.yaml # ★ Agent 运行时配置
│
├── frontend/src/
│ ├── api/
│ │ ├── transport.ts # ★ Transport 接口定义
│ │ ├── wails-transport.ts # ★ WailsTransport 实现
│ │ ├── http-transport.ts # ★ HttpTransport 实现
│ │ ├── connection-manager.ts # ★ 连接管理器
│ │ ├── types.ts # 类型定义(扩展)
│ │ ├── system.ts # 系统API(不变)
│ │ └── index.ts # 统一导出(扩展)
│ │
│ ├── stores/
│ │ └── connection.ts # ★ Pinia 连接状态 Store
│ │
│ └── components/
│ ├── ConnectionIndicator.vue # ★ 连接状态指示器
│ └── ConnectionDialog.vue # ★ 连接配置弹窗
│
├── go.mod
├── go.sum
└── wails.json
8. 分阶段实施计划
8.1 Wave 1: 核心连通(MVP)
目标:本地 u-desk 能够通过 HTTP 连接到远端 u-fs-agent,完成基本的文件浏览和读写操作。
范围:
| 模块 | 任务 | 优先级 |
|---|---|---|
| 后端 | cmd/agent/main.go 入口实现 |
P0 |
| 后端 | internal/agent/config 配置加载 |
P0 |
| 后端 | internal/agent/handler 14 个 Handler |
P0 |
| 后端 | internal/agent/middleware/auth 认证中间件 |
P0 |
| 后端 | internal/agent/model/response 响应模型 |
P0 |
| 后端 | configs/agent.yaml 默认配置 |
P0 |
| 前端 | transport.ts 接口定义 |
P0 |
| 前端 | wails-transport.ts 本地实现 |
P0 |
| 前端 | http-transport.ts 远程实现 |
P0 |
| 前端 | connection-manager.ts 连接管理 |
P0 |
| 前端 | connection.ts Pinia Store |
P1 |
| 前端 | ConnectionIndicator.vue 指示器 |
P1 |
| 前端 | Toolbar.vue 嵌入指示器 |
P1 |
| 前端 | FileExplorer.vue Transport 化改造 |
P0 |
| 前端 | EditorView.vue Transport 化改造 |
P0 |
验收标准:
go build ./cmd/agent编译通过,生成u-fs-agent可执行文件- 启动 agent 后
GET /api/v1/ping返回{"code":0} - 前端添加远程连接后,文件浏览器能列出远端目录
- 能在编辑器中打开、编辑、保存远端文件
- 本地/远程切换时 UI 无卡顿
- 断网时给出明确错误提示
8.2 Wave 2: 编辑体验增强
目标:远程编辑体验接近本地,解决网络延迟带来的交互问题。
范围:
| 特性 | 说明 |
|---|---|
| ETag 缓存 | 文件读写带 ETag,减少不必要的传输 |
| Multipart 上传 | 替代 Base64,大文件传输效率提升 |
| 写入防抖 (Debounce) | 编辑器输入防抖,减少网络请求数量 |
| 乐观更新 | 先更新 UI 再发请求,回滚失败 |
| 断点续传 | 大文件上传中断后可续传 |
| 并发锁 | 多标签编辑同一文件时的冲突检测 |
8.3 Wave 3: 增强功能
目标:补齐高级文件操作能力。
| 特性 | 说明 |
|---|---|
| ZIP 远程操作 | 远端打包/解压,流式传输 |
| 回收站远程管理 | 查看/恢复/清空远端回收站 |
| 批量操作 | 批量删除、批量移动 |
| 文件搜索 | 远端文件名/内容搜索(grep) |
| 文件监控 | 远端文件变更实时推送(WebSocket/SSE) |
| 权限信息 | 显示远端文件 owner/group/mode |
8.4 Wave 4: 用户间文件共享
目标:多用户协作场景支持。
| 特性 | 说明 |
|---|---|
| JWT 认证 | 替换 Bearer Token,支持用户身份 |
| 分享链接 | 生成临时访问链接(含过期时间) |
| WebSocket | 实时协同光标/选区 |
| 操作审计 API | 查询远端操作日志 |
| 配额管理 | 用户/目录级别的存储配额 |
| Web 终端 | 通过 Agent 代理 SSH/WebShell |
9. 安全设计
9.1 认证
MVP 阶段 (Wave 1):
- Bearer Token 静态配置在
agent.yaml - Token 为空时跳过认证(适用于内网/隧道场景)
- Token 存储在前端 ConnectionProfile 中(内存态,不持久化明文)
后续升级 (Wave 4):
- JWT Token,支持过期时间和刷新
- 可对接外部 IdP(OAuth2/OIDC)
9.2 路径安全
复用 filesystem 包已有的 PathValidator 三层防护:
- 路径清洗:消除
..、重复分隔符、空白字符 - 路径规范化:统一为绝对路径,解析符号链接
- 路径黑名单:禁止访问系统敏感目录(
/etc,C:\Windows等)
9.3 传输安全
| 场景 | 推荐方案 |
|---|---|
| 本地网络 | HTTP 明文(Token 认证) |
| 公网部署 | HTTPS 反向代理(Nginx/Caddy 终止 TLS) |
| 高安全要求 | mTLS 双向证书认证 |
9.4 CORS 策略
- 默认允许所有来源(
*)-- 开发便利 - 生产环境建议限定为 u-desk 前端来源
- 仅允许必要的方法和 Header
9.5 审计日志
复用 filesystem 包的 AuditLogger,记录所有远程操作的:
- 操作时间戳
- 操作类型(读/写/删/重命名...)
- 操作路径
- 操作结果(成功/失败)
- 来源 IP(从 Echo Context 提取)
10. 部署方案
10.1 编译
# Linux amd64 (最常见的服务器架构)
GOOS=linux GOARCH=amd64 go build -o u-fs-agent-linux-amd64 ./cmd/agent
# Windows amd64 (远程 Windows 服务器)
GOOS=windows GOARCH=amd64 go build -o u-fs-agent.exe ./cmd/agent
# Linux arm64 (ARM 服务器/树莓派)
GOOS=linux GOARCH=arm64 go build -o u-fs-agent-linux-arm64 ./cmd/agent
# 交叉编译(在 Windows 开发机上编译 Linux 版本)
set GOOS=linux
set GOARCH=amd64
go build -o u-fs-agent ./cmd/agent
10.2 远端部署
方式一:直接运行
# 上传编译好的二进制和配置文件
scp u-fs-agent configs/agent.yaml user@server:/opt/u-fs-agent/
# SSH 登录后执行
cd /opt/u-fs-agent
chmod +x u-fs-agent
./u-fs-agent
方式二:systemd 服务(Linux 推荐)
# /etc/systemd/system/u-fs-agent.service
[Unit]
Description=u-fs-agent Remote File Service
After=network.target
[Service]
Type=simple
User=nobody
WorkingDirectory=/opt/u-fs-agent
ExecStart=/opt/u-fs-agent/u-fs-agent -config /opt/u-fs-agent/agent.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl enable u-fs-agent
sudo systemctl start u-fs-agent
sudo systemctl status u-fs-agent
方式三:Docker
FROM golang:1.23-alpine AS builder
COPY . /app
WORKDIR /app
RUN CGO_ENABLED=0 go build -o /u-fs-agent ./cmd/agent
FROM alpine:3.19
COPY --from=builder /u-fs-agent /usr/local/bin/u-fs-agent
COPY configs/agent.yaml /etc/u-fs-agent/agent.yaml
EXPOSE 9876 8073
ENTRYPOINT ["u-fs-agent", "-config", "/etc/u-fs-agent/agent.yaml"]
10.3 网络打通
| 方案 | 适用场景 | 配置复杂度 |
|---|---|---|
| 公网 IP 直连 | 云服务器有公网 IP | 低 |
| SSH 隧道 | 无公网 IP,有 SSH 访问权限 | 中 |
| frp/Tailscale | NAT 内网穿透 | 中 |
| Cloudflare Tunnel | 零配置公网暴露 | 低 |
SSH 隧道示例(推荐开发阶段使用):
# 本地执行:将远端的 9876 端口映射到本地 19876
ssh -N -L 19876:127.0.0.1:9876 user@remote-server
# u-desk 前端连接地址填写: http://127.0.0.1:19876
11. 配置说明
agent.yaml 完整配置
# u-fs-agent 运行时配置
server:
port: 9876 # HTTP API 监听端口
host: "0.0.0.0" # 监听地址(0.0.0.0 = 所有网卡)
auth:
token: "" # Bearer Token(空 = 不需要认证)
cors:
allowed_origins: # 允许的跨域来源
- "*"
log:
level: info # 日志级别: debug / info / warn / error
format: json # 日志格式: json / text
file_server:
port: 8073 # 内嵌文件服务器端口(用于媒体预览代理)
max_file_size: 524288000 # 文件服务器最大文件大小 (500MB)
security:
allow_symlinks: false # 是否允许符号链接
check_system_paths: true # 是否检查系统敏感路径
配置优先级
命令行参数 > 环境变量 > agent.yaml > 默认值
(MVP 阶段仅支持 YAML + 默认值,命令行和环境变量覆盖在 Wave 2 补充)
12. 测试策略
12.1 单元测试
| 模块 | 测试重点 | 覆盖率目标 |
|---|---|---|
agent/config |
配置加载、默认值、YAML 解析 | 90%+ |
agent/model/response |
响应构造、错误码 | 100% |
agent/middleware/auth |
Token 校验、空 Token 跳过 | 95%+ |
agent/handler |
参数解析、错误翻译、边界条件 | 85%+ |
12.2 集成测试
- 启动完整 agent 进程,使用
net/http发送真实请求 - 覆盖全部 14 个端点的正常和异常路径
- 测试认证中间件的拦截效果
- 测试文件服务器代理的透传正确性
12.3 端到端测试
- 前端连接远程 agent,走通完整的浏览-编辑-保存流程
- 模拟网络断开,验证错误处理和重连机制
- 本地/远程切换的功能回归
12.4 安全测试
- 路径穿越攻击:
../../../etc/passwd - 超大文件上传:超过 10MB 限制
- 无 Token 访问:401 拦截
- CORS 跨域:Origin 验证
文档版本: 1.0 创建日期: 2026-04-26 所属迭代: GO-DESK-7 关联任务: u-fs-agent 远程文件服务