// [fs-only] 数据库客户端模块已移除(feature/fs-only 分支) // 保留模块:文件系统 | Markdown编辑器 | 版本历史(抽屉) | 系统信息 | 更新检查 | PDF导出 // 顶部Tab仅:file-system(数据库 db-cli 已删除) package main import ( "context" "fmt" "os" "path/filepath" "strconv" stdruntime "runtime" "strings" "sync" "time" "golang.org/x/sys/windows/registry" "u-desk/internal/api" "u-desk/internal/common" "u-desk/internal/filesystem" "u-desk/internal/service" "u-desk/internal/sftp" "u-desk/internal/storage" "u-desk/internal/storage/models" "u-desk/internal/system" "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/w32" ) // App 应用结构体 type App struct { ctx context.Context mainWindow *application.WebviewWindow updateAPI *api.UpdateAPI updateTicker *time.Ticker configAPI *api.ConfigAPI pdfAPI *api.PdfAPI filesystem *filesystem.FileSystemService sftpService *sftp.Service isAlwaysOnTop bool mu sync.Mutex } // App 方法命名约定: // - 多参数操作 → XxxRequest 结构体(Wails 自动生成 TS 类型) // - 单参数查询/简单操作 → 直接参数 // NewApp 创建新的应用实例 func NewApp() *App { return &App{} } // SetMainWindow 设置主窗口引用(由 main.go 在创建窗口后调用) func (a *App) SetMainWindow(w *application.WebviewWindow) { a.mainWindow = w } // ServiceStartup Wails v3 服务启动生命周期(替代 v2 的 Startup) func (a *App) ServiceStartup(ctx context.Context, _ application.ServiceOptions) error { a.ctx = ctx // 1. 核心初始化:SQLite(必须同步,很快) if _, err := storage.InitFast(); err != nil { return fmt.Errorf("SQLite 初始化失败,应用无法启动: %w", err) } // 2. 初始化配置服务 configService, err := api.NewConfigAPI() if err != nil { return fmt.Errorf("配置服务初始化失败: %w", err) } a.configAPI = configService // 2.5. 迁移旧配置 _ = a.configAPI.MigrateTabConfig() // 2.6. 初始化PDF导出API fmt.Println("[启动] 初始化PDF导出模块...") pdfAPI, err := api.NewPdfAPI() if err != nil { fmt.Printf("[启动] PDF导出API初始化失败: %v\n", err) } else { a.pdfAPI = pdfAPI fmt.Println("[启动] PDF导出模块初始化完成") } // 3. 初始化版本号(提前触发缓存,避免后续重复计算) version := service.GetCurrentVersion() fmt.Printf("[启动] 当前版本: %s\n", version) // 4. 读取配置,获取可见的 Tabs visibleTabs := a.getVisibleTabs() fmt.Printf("[启动] 可用的模块: %v\n", visibleTabs) // 4. 根据配置初始化模块(条件初始化) if err := a.initModulesByConfig(visibleTabs); err != nil { return fmt.Errorf("模块初始化失败: %w", err) } // 5. 清理遗留的 SFTP 临时预览文件 sftp.CleanupTempFiles() // 6. 异步初始化:UpdateAPI(涉及网络请求,完全异步) go func() { if updateAPI, err := api.NewUpdateAPI("https://c.1216.top/last-version.json"); err == nil { a.mu.Lock() a.updateAPI = updateAPI a.mu.Unlock() a.updateAPI.SetContext(ctx) a.updateAPI.SetEventEmitter(func(name string, data ...any) { if a.mainWindow != nil { a.mainWindow.EmitEvent(name, data...) } }) a.startAutoUpdateCheck() } }() return nil } // getVisibleTabs 获取配置中的可见 Tabs func (a *App) getVisibleTabs() []string { config, err := a.configAPI.GetAppConfig() if err != nil { fmt.Printf("[启动] 读取配置失败,使用默认配置: %v\n", err) return common.DefaultVisibleTabs } success, ok := config["success"].(bool) if !ok || !success { fmt.Printf("[启动] 配置读取失败,使用默认配置\n") return common.DefaultVisibleTabs } data, ok := config["data"].(map[string]interface{}) if !ok { return common.DefaultVisibleTabs } visibleTabsInterface, ok := data["visibleTabs"].([]interface{}) if !ok { return common.DefaultVisibleTabs } visibleTabs := common.InterfaceSliceToStringSlice(visibleTabsInterface) if len(visibleTabs) == 0 { return common.DefaultVisibleTabs } return visibleTabs } // initModulesByConfig 根据配置初始化模块 func (a *App) initModulesByConfig(visibleTabs []string) error { if common.Contains(visibleTabs, common.TabFileSystem) { fmt.Println("[启动] 初始化文件系统模块...") fsConfig := filesystem.DefaultConfig() var err error a.filesystem, err = filesystem.NewFileSystemService(fsConfig) if err != nil { return fmt.Errorf("文件系统服务初始化失败: %w", err) } go a.startFileServer() fmt.Println("[启动] 文件系统模块初始化完成") } else { fmt.Println("[启动] 跳过文件系统模块(未启用)") } return nil } // startFileServer 启动文件服务器 func (a *App) startFileServer() { if _, err := filesystem.StartLocalFileServer(); err != nil { fmt.Printf("[文件服务器] 启动失败: %v\n", err) return } fmt.Printf("[文件服务器] 启动在 http://%s\n", filesystem.GetLocalFileServerAddr()) } // ServiceShutdown Wails v3 服务关闭生命周期(替代 v2 的 Shutdown) func (a *App) ServiceShutdown() error { if a.updateTicker != nil { a.updateTicker.Stop() } shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if a.filesystem != nil { fmt.Println("[文件系统服务] 正在关闭...") if err := a.filesystem.Close(shutdownCtx); err != nil { fmt.Printf("[文件系统服务] 关闭失败: %v\n", err) } else { fmt.Println("[文件系统服务] 已关闭") } } fmt.Println("[文件服务器] 正在关闭...") if err := filesystem.ShutdownLocalFileServer(); err != nil { fmt.Printf("[文件服务器] 关闭失败: %v\n", err) } else { fmt.Println("[文件服务器] 已关闭") } // 关闭所有 SFTP 连接 + 清理临时文件 if a.sftpService != nil { sftp.GetManager().Shutdown() } sftp.CleanupTempFiles() return nil } // GetSystemInfo 获取系统信息 func (a *App) GetSystemInfo() (map[string]interface{}, error) { return system.GetSystemInfo() } // GetCPUInfo 获取 CPU 信息 func (a *App) GetCPUInfo() (map[string]interface{}, error) { return system.GetCPUInfo() } // GetMemoryInfo 获取内存信息 func (a *App) GetMemoryInfo() (map[string]interface{}, error) { return system.GetMemoryInfo() } // GetDiskInfo 获取磁盘信息 func (a *App) GetDiskInfo() ([]map[string]interface{}, error) { return system.GetDiskInfo() } // ReadFile 读取文件 func (a *App) ReadFile(path string) (string, error) { return a.filesystem.ReadFile(path) } // WriteFileRequest 写入文件请求结构体 type WriteFileRequest struct { Path string `json:"path"` Content string `json:"content"` } // WriteFile 写入文件 func (a *App) WriteFile(req WriteFileRequest) error { return a.filesystem.WriteFile(req.Path, req.Content) } // SaveBase64FileRequest 保存 Base64 编码的二进制文件 type SaveBase64FileRequest struct { Path string `json:"path"` Content string `json:"content"` // base64 编码的文件内容 } // SaveBase64File 将 base64 内容解码后写入文件(用于图片等二进制数据) func (a *App) SaveBase64File(req SaveBase64FileRequest) error { return a.filesystem.SaveBase64File(req.Path, req.Content) } // ListDir 列出目录 func (a *App) ListDir(path string) ([]map[string]interface{}, error) { return a.filesystem.ListDir(path) } // CreateDir 创建目录 func (a *App) CreateDir(path string) (*filesystem.FileOperationResult, error) { return a.filesystem.CreateDir(path) } // CreateFile 创建文件 func (a *App) CreateFile(path string) (*filesystem.FileOperationResult, error) { return a.filesystem.CreateFile(path) } // DeletePath 删除文件或目录 func (a *App) DeletePath(path string) (*filesystem.FileOperationResult, error) { return a.filesystem.DeletePath(path) } // RenamePathRequest 重命名文件或目录请求结构体 type RenamePathRequest struct { OldPath string `json:"oldPath"` NewPath string `json:"newPath"` } // RenamePath 重命名文件或目录 func (a *App) RenamePath(req RenamePathRequest) (*filesystem.FileOperationResult, error) { return a.filesystem.RenamePath(req.OldPath, req.NewPath) } // GetFileInfo 获取文件信息 func (a *App) GetFileInfo(path string) (map[string]interface{}, error) { return a.filesystem.GetFileInfo(path) } // GetEnvVars 获取环境变量 func (a *App) GetEnvVars() (map[string]string, error) { envVars := make(map[string]string) for _, env := range os.Environ() { if key, value, found := strings.Cut(env, "="); found { envVars[key] = value } } return envVars, nil } // OpenPath 使用系统默认程序打开文件或目录 func (a *App) OpenPath(path string) error { return a.filesystem.OpenPath(path) } // ========== Zip 文件操作接口 ========== // ListZipContents 列出 zip 文件内容 func (a *App) ListZipContents(zipPath string) ([]map[string]interface{}, error) { return a.filesystem.ListZipContents(zipPath) } // ExtractFileFromZip 从 zip 文件中提取单个文件内容 func (a *App) ExtractFileFromZip(zipPath, filePath string) (string, error) { return a.filesystem.ExtractFileFromZip(zipPath, filePath) } // ExtractFileFromZipToTemp 从 zip 文件中提取单个文件到临时目录 func (a *App) ExtractFileFromZipToTemp(zipPath, filePath string) (string, error) { return a.filesystem.ExtractFileFromZipToTemp(zipPath, filePath) } // GetZipFileInfo 获取 zip 文件中特定文件的信息 func (a *App) GetZipFileInfo(zipPath, filePath string) (map[string]interface{}, error) { return a.filesystem.GetZipFileInfo(zipPath, filePath) } // ResolveShortcut 解析快捷方式文件,返回目标路径信息 func (a *App) ResolveShortcut(lnkPath string) (map[string]interface{}, error) { targetPath, err := a.filesystem.ResolveShortcut(lnkPath) if err != nil { return map[string]interface{}{ "success": false, "message": err.Error(), }, err } fileInfo, err := a.filesystem.GetFileInfo(targetPath) if err != nil { return map[string]interface{}{ "success": true, "targetPath": targetPath, "targetExists": false, "targetAccessible": false, }, nil } return map[string]interface{}{ "success": true, "targetPath": targetPath, "targetExists": true, "targetAccessible": true, "targetInfo": fileInfo, }, nil } // getWindowsSpecialFolder 从注册表读取 Windows 特殊文件夹的真实路径 func getWindowsSpecialFolder(guid string, fallbackName string) string { key, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders`, registry.READ) if err != nil { return "" } defer key.Close() val, _, err := key.GetStringValue(guid) if err != nil || val == "" { return "" } path := os.ExpandEnv(val) if _, err := os.Stat(path); err != nil { return "" } return path } // GetCommonPaths 获取常用系统路径 func (a *App) GetCommonPaths() (map[string]string, error) { homeDir, err := os.UserHomeDir() if err != nil { return nil, err } paths := map[string]string{ "home": homeDir, } folderGUIDs := map[string]string{ "desktop": "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}", "documents": "{D20B4C7F-5EA7-424C-B25E-039F6F1FCC8A}", "downloads": "{374DE290-123F-4565-9164-39C4925E467B}", } for name, guid := range folderGUIDs { if p := getWindowsSpecialFolder(guid, name); p != "" { paths[name] = p } else { paths[name] = filepath.Join(homeDir, strings.ToUpper(name[:1])+name[1:]) } } if stdruntime.GOOS == "windows" { for _, drive := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ" { path := string(drive) + ":\\" if _, err := os.Stat(path); err == nil { key := fmt.Sprintf("root_%c", drive) paths[key] = path } } } return paths, nil } // Reload 重新加载窗口(用于菜单项) func (a *App) Reload() { if a.mainWindow != nil { a.mainWindow.Reload() } } // ClearCache 清理本地缓存(用于菜单项) func (a *App) ClearCache() { if a.mainWindow != nil { a.mainWindow.EmitEvent("clear-cache") } } // ========== 窗口控制方法 ========== // WindowMinimize 最小化窗口 func (a *App) WindowMinimize() { if a.mainWindow != nil { a.mainWindow.Minimise() } } // WindowMaximize 最大化/还原窗口 func (a *App) WindowMaximize() { if a.mainWindow == nil { return } if a.mainWindow.IsMaximised() { a.mainWindow.UnMaximise() } else { a.mainWindow.Maximise() } } // WindowClose 关闭窗口 func (a *App) WindowClose() { application.Get().Quit() } // WindowIsMaximized 检查窗口是否最大化 func (a *App) WindowIsMaximized() bool { if a.mainWindow != nil { return a.mainWindow.IsMaximised() } return false } // WindowToggleAlwaysOnTop 切换窗口置顶 func (a *App) WindowToggleAlwaysOnTop() bool { if a.mainWindow == nil { return false } a.isAlwaysOnTop = !a.isAlwaysOnTop a.mainWindow.SetAlwaysOnTop(a.isAlwaysOnTop) return a.isAlwaysOnTop } // SetWindowTitleBarColor 设置原生标题栏颜色 + 主题模式(0x00BBGGRR 格式) func (a *App) SetWindowTitleBarColor(color uint32, isDark bool) { if a.mainWindow == nil || stdruntime.GOOS != "windows" { return } hwnd := uintptr(a.mainWindow.NativeWindow()) if hwnd == 0 { return } w32.SetTheme(hwnd, isDark) w32.SetTitleBarColour(hwnd, color) } // ========== 版本更新管理接口 ========== // requireUpdateAPI 检查 updateAPI 是否已初始化 func (a *App) requireUpdateAPI() (*api.UpdateAPI, error) { a.mu.Lock() defer a.mu.Unlock() if a.updateAPI == nil { return nil, fmt.Errorf("更新功能正在初始化中") } return a.updateAPI, nil } // CheckUpdate 检查更新 func (a *App) CheckUpdate() (map[string]interface{}, error) { api, err := a.requireUpdateAPI() if err != nil { return nil, err } return api.CheckUpdate() } // GetCurrentVersion 获取当前版本号 func (a *App) GetCurrentVersion() (map[string]interface{}, error) { api, err := a.requireUpdateAPI() if err != nil { return nil, err } return api.GetCurrentVersion() } // GetUpdateConfig 获取更新配置 func (a *App) GetUpdateConfig() (map[string]interface{}, error) { api, err := a.requireUpdateAPI() if err != nil { return nil, err } return api.GetUpdateConfig() } // SetUpdateConfig 设置更新配置 func (a *App) SetUpdateConfig(autoCheckEnabled bool, checkIntervalMinutes int, checkURL string) (map[string]interface{}, error) { api, err := a.requireUpdateAPI() if err != nil { return nil, err } return api.SetUpdateConfig(autoCheckEnabled, checkIntervalMinutes, checkURL) } // DownloadUpdate 下载更新包 func (a *App) DownloadUpdate(downloadURL string) (map[string]interface{}, error) { api, err := a.requireUpdateAPI() if err != nil { return nil, err } return api.DownloadUpdate(downloadURL) } // InstallUpdate 安装更新包 func (a *App) InstallUpdate(installerPath string, autoRestart bool) (map[string]interface{}, error) { api, err := a.requireUpdateAPI() if err != nil { return nil, err } return api.InstallUpdate(installerPath, autoRestart) } // InstallUpdateWithHash 安装更新包(带哈希验证) func (a *App) InstallUpdateWithHash(installerPath string, autoRestart bool, expectedHash string, hashType string) (map[string]interface{}, error) { api, err := a.requireUpdateAPI() if err != nil { return nil, err } return api.InstallUpdateWithHash(installerPath, autoRestart, expectedHash, hashType) } // VerifyUpdateFile 验证更新文件哈希值 func (a *App) VerifyUpdateFile(filePath string, expectedHash string, hashType string) (map[string]interface{}, error) { api, err := a.requireUpdateAPI() if err != nil { return nil, err } return api.VerifyUpdateFile(filePath, expectedHash, hashType) } // startAutoUpdateCheck 启动自动更新检查 func (a *App) startAutoUpdateCheck() { if a.updateAPI == nil { return } config, err := a.updateAPI.GetUpdateConfig() if err != nil { return } success, ok := config["success"].(bool) if !ok || !success { return } configData, ok := config["data"].(map[string]interface{}) if !ok { return } autoCheckEnabled, ok := configData["auto_check_enabled"].(bool) if !ok || !autoCheckEnabled { return } interval, ok := configData["check_interval_minutes"].(int) if !ok || interval <= 0 { interval = 5 } go a.checkUpdate() a.updateTicker = time.NewTicker(time.Duration(interval) * time.Minute) go func() { for range a.updateTicker.C { a.checkUpdate() } }() } // checkUpdate 执行更新检查 func (a *App) checkUpdate() { defer func() { if r := recover(); r != nil { fmt.Printf("[自动检查更新] 发生错误: %v\n", r) } }() a.mu.Lock() api := a.updateAPI a.mu.Unlock() if api == nil { return } result, err := api.CheckUpdate() if err != nil { return } success, ok := result["success"].(bool) if !ok || !success { return } data, ok := result["data"].(map[string]interface{}) if !ok { return } hasUpdate, ok := data["has_update"].(bool) if ok && hasUpdate && a.mainWindow != nil { a.mainWindow.EmitEvent("update-available", data) } } // ========== 审计日志接口 ========== // GetAuditLogs 获取审计日志 func (a *App) GetAuditLogs(limit int) ([]map[string]interface{}, error) { return a.filesystem.GetAuditLogs(limit) } // ========== 文件服务器接口 ========== // GetFileServerURL 获取本地文件服务器的URL func (a *App) GetFileServerURL() string { return fmt.Sprintf("http://%s", filesystem.GetLocalFileServerAddr()) } // DetectFileTypeByContent 通过文件内容检测文件类型 func (a *App) DetectFileTypeByContent(path string) (map[string]interface{}, error) { return filesystem.DetectFileTypeByContentSimple(path) } // ========== 回收站接口 ========== // GetRecycleBinEntries 获取回收站条目 func (a *App) GetRecycleBinEntries() ([]map[string]interface{}, error) { return a.filesystem.GetRecycleBinEntries() } // RestoreFromRecycleBin 从回收站恢复文件 func (a *App) RestoreFromRecycleBin(recyclePath string) error { return a.filesystem.RestoreFromRecycleBin(recyclePath) } // DeletePermanently 永久删除回收站中的文件 func (a *App) DeletePermanently(recyclePath string) error { return a.filesystem.DeletePermanently(recyclePath) } // EmptyRecycleBin 清空回收站 func (a *App) EmptyRecycleBin() error { return a.filesystem.EmptyRecycleBin() } // ========== 应用配置接口 ========== // GetAppConfig 获取应用配置 func (a *App) GetAppConfig() (map[string]interface{}, error) { if a.configAPI == nil { return nil, fmt.Errorf("配置服务正在初始化中") } return a.configAPI.GetAppConfig() } // SaveAppConfigRequest 保存应用配置请求 type SaveAppConfigRequest struct { Tabs []api.AppTabDefinition `json:"tabs"` VisibleTabs []string `json:"visibleTabs"` DefaultTab string `json:"defaultTab"` } // SaveAppConfig 保存应用配置 func (a *App) SaveAppConfig(req SaveAppConfigRequest) (map[string]interface{}, error) { if a.configAPI == nil { return nil, fmt.Errorf("配置服务正在初始化中") } oldConfig, _ := a.configAPI.GetAppConfig() var oldVisibleTabs []string if success, ok := oldConfig["success"].(bool); ok && success { if data, ok := oldConfig["data"].(map[string]interface{}); ok { if vtInterface, ok := data["visibleTabs"].([]interface{}); ok { oldVisibleTabs = common.InterfaceSliceToStringSlice(vtInterface) } } } apiReq := api.SaveAppConfigRequest{ Tabs: req.Tabs, VisibleTabs: req.VisibleTabs, DefaultTab: req.DefaultTab, } result, err := a.configAPI.SaveAppConfig(apiReq) if err != nil { return result, err } if success, ok := result["success"].(bool); ok && success { a.handleNewlyEnabledModules(oldVisibleTabs, req.VisibleTabs) } return result, nil } // handleNewlyEnabledModules 处理新启用的模块 func (a *App) handleNewlyEnabledModules(oldTabs, newTabs []string) { newlyEnabled := common.Difference(newTabs, oldTabs) if len(newlyEnabled) == 0 { return } fmt.Printf("[模块] 检测到新启用的模块: %v\n", newlyEnabled) for _, tab := range newlyEnabled { switch tab { case common.TabFileSystem: a.initFilesystemModule() case common.TabDevice: fmt.Println("[模块] 设备测试模块已启用") } } } // initFilesystemModule 延迟初始化文件系统模块 func (a *App) initFilesystemModule() { if a.filesystem != nil { fmt.Println("[模块] 文件系统模块已初始化,跳过") return } fmt.Println("[模块] 延迟初始化文件系统模块...") fsConfig := filesystem.DefaultConfig() var err error a.filesystem, err = filesystem.NewFileSystemService(fsConfig) if err != nil { fmt.Printf("[模块] 文件系统模块初始化失败: %v\n", err) return } go a.startFileServer() fmt.Println("[模块] 文件系统模块初始化完成") } // ExportPDF 导出PDF文件 func (a *App) ExportPDF(content string, title string, fileName string, fontSize int, pageWidth int, pageHeight int) (map[string]interface{}, error) { if a.pdfAPI == nil { return map[string]interface{}{ "success": false, "message": "PDF导出功能未初始化", }, fmt.Errorf("PDF导出功能未初始化") } req := api.PdfExportRequest{ Content: content, Title: title, FileName: fileName, FontSize: fontSize, PageWidth: pageWidth, PageHeight: pageHeight, } result, err := a.pdfAPI.ExportMarkdownToPDF(req) if err != nil { return map[string]interface{}{ "success": false, "message": err.Error(), }, err } return map[string]interface{}{ "success": result.Success, "message": result.Message, "path": result.Path, "size": result.Size, }, nil } // SelectPDFSaveDirectory 选择PDF保存目录 func (a *App) SelectPDFSaveDirectory() (string, error) { if a.pdfAPI == nil { return "", fmt.Errorf("PDF导出功能未初始化") } return a.pdfAPI.SelectDirectory() } // ========== SFTP 接口 ========== func (a *App) ensureSftpService() *sftp.Service { a.mu.Lock() defer a.mu.Unlock() if a.sftpService == nil { a.sftpService = sftp.NewService() } return a.sftpService } // SftpConnectRequest SFTP 连接请求 type SftpConnectRequest struct { Host string `json:"host"` Port int `json:"port"` Username string `json:"username"` Password string `json:"password"` KeyPath string `json:"key_path"` KeyPassphrase string `json:"key_passphrase"` } // SftpConnect 建立 SFTP 连接,返回连接标识符 connID func (a *App) SftpConnect(req SftpConnectRequest) (string, error) { config := &sftp.Config{ Host: req.Host, Port: req.Port, Username: req.Username, Password: req.Password, KeyPath: req.KeyPath, KeyPassphrase: req.KeyPassphrase, } if config.Port == 0 { config.Port = 22 } if config.Timeout == 0 { config.Timeout = 15 * time.Second } svc := a.ensureSftpService() _, err := svc.GetManager().Connect(config) if err != nil { return "", sftp.ToUserMessage(err) } connID := sftp.ConnID(config.Host, config.Port) return connID, nil } // SftpDisconnect 断开 SFTP 连接 func (a *App) SftpDisconnect(connID string) error { parts := strings.SplitN(connID, ":", 2) if len(parts) < 2 { return fmt.Errorf("无效的连接标识符") } host := parts[0] port, err := strconv.Atoi(parts[1]) if err != nil { return fmt.Errorf("无效的端口号") } sftp.GetManager().Disconnect(host, port) return nil } // SftpListDir SFTP 列出目录 func (a *App) SftpListDir(connID string, dirPath string) ([]map[string]interface{}, error) { return a.ensureSftpService().ListDir(connID, dirPath) } // SftpReadFile SFTP 读取文件内容 func (a *App) SftpReadFile(connID string, filePath string) (string, error) { return a.ensureSftpService().ReadFile(connID, filePath) } // SftpWriteFileRequest SFTP 写入请求 type SftpWriteFileRequest struct { SessionID string `json:"session_id"` Path string `json:"path"` Content string `json:"content"` } // SftpWriteFile SFTP 写入文件 func (a *App) SftpWriteFile(req SftpWriteFileRequest) error { return a.ensureSftpService().WriteFile(req.SessionID, req.Path, req.Content) } // SftpWriteBase64File SFTP 写入 base64 编码的二进制文件(粘贴图片等) func (a *App) SftpWriteBase64File(sessionID, filePath, base64Content string) error { return a.ensureSftpService().WriteBase64File(sessionID, filePath, base64Content) } // SftpGetFileInfo SFTP 获取文件信息 func (a *App) SftpGetFileInfo(connID string, filePath string) (map[string]interface{}, error) { return a.ensureSftpService().GetFileInfo(connID, filePath) } // SftpCreateDir SFTP 创建目录 func (a *App) SftpCreateDir(connID string, dirPath string) (*filesystem.FileOperationResult, error) { return a.ensureSftpService().CreateDir(connID, dirPath) } // SftpCreateFile SFTP 创建文件 func (a *App) SftpCreateFile(connID string, filePath string) (*filesystem.FileOperationResult, error) { return a.ensureSftpService().CreateFile(connID, filePath) } // SftpDeletePath SFTP 删除文件或目录 func (a *App) SftpDeletePath(connID string, filePath string) (*filesystem.FileOperationResult, error) { return a.ensureSftpService().DeletePath(connID, filePath) } // SftpRenamePathRequest SFTP 重命名请求 type SftpRenamePathRequest struct { SessionID string `json:"session_id"` OldPath string `json:"old_path"` NewPath string `json:"new_path"` } // SftpRenamePath SFTP 重命名文件或目录 func (a *App) SftpRenamePath(req SftpRenamePathRequest) (*filesystem.FileOperationResult, error) { return a.ensureSftpService().RenamePath(req.SessionID, req.OldPath, req.NewPath) } // SftpDownloadToTemp 下载远程文件到本地临时目录(用于预览) func (a *App) SftpDownloadToTemp(connID string, remotePath string) (string, error) { return a.ensureSftpService().DownloadToTemp(connID, remotePath) } // SftpGetCommonPaths 获取 SFTP 远程主机常用路径 func (a *App) SftpGetCommonPaths(connID string) (map[string]string, error) { return a.ensureSftpService().GetCommonPaths(connID) } // SftpGetSystemInfo 获取 SFTP 远程主机系统信息(CPU/内存/磁盘) func (a *App) SftpGetSystemInfo(connID string) (map[string]interface{}, error) { return a.ensureSftpService().GetSystemInfo(connID) } // --- 连接配置 CRUD (SQLite 持久化) --- type SaveProfileRequest struct { ID *uint `json:"id"` Name string `json:"name"` Host string `json:"host"` Port int `json:"port"` Username string `json:"username"` Password string `json:"password"` KeyPath string `json:"key_path"` Type string `json:"type"` Token string `json:"token"` LastConnected *int64 `json:"last_connected"` } var profileSvc *service.ProfileService func (a *App) ensureProfileSvc() *service.ProfileService { if profileSvc == nil { profileSvc = service.NewProfileService() } return profileSvc } func (a *App) LoadConnectionProfiles() ([]map[string]interface{}, error) { list, err := a.ensureProfileSvc().ListProfiles() if err != nil { return nil, err } result := make([]map[string]interface{}, len(list)) for i, p := range list { result[i] = map[string]interface{}{ "id": float64(p.ID), "name": p.Name, "host": p.Host, "port": p.Port, "username": p.Username, "password": p.Password, "keyPath": p.KeyPath, "type": p.Type, "token": p.Token, "lastConnected": p.LastConnected, "sortOrder": float64(p.SortOrder), } } return result, nil } func (a *App) SaveConnectionProfile(req SaveProfileRequest) (map[string]interface{}, error) { p := &models.ConnectionProfile{ Name: req.Name, Host: req.Host, Port: req.Port, Username: req.Username, Password: req.Password, KeyPath: req.KeyPath, Type: req.Type, Token: req.Token, } if req.LastConnected != nil { t := time.Unix(*req.LastConnected, 0) p.LastConnected = &t } if req.ID != nil { p.ID = *req.ID } if err := a.ensureProfileSvc().SaveProfile(p); err != nil { return nil, err } return map[string]interface{}{"id": float64(p.ID), "success": true}, nil } func (a *App) DeleteConnectionProfile(id uint) error { return a.ensureProfileSvc().DeleteProfile(id) } func (a *App) GetLocalSystemInfo() (map[string]interface{}, error) { info := make(map[string]interface{}) cpuInfo, err := system.GetCPUInfo() if err == nil && cpuInfo != nil { if v, ok := cpuInfo["usage"].(string); ok { info["cpu_usage"] = v } } memInfo, err := system.GetMemoryInfo() if err == nil && memInfo != nil { if v, ok := memInfo["usage"].(string); ok { info["mem_usage"] = v } } diskInfos, err := system.GetDiskInfo() if err == nil && len(diskInfos) > 0 { if v, ok := diskInfos[0]["usage"].(string); ok { info["disk_usage"] = v } } return info, nil }