596 lines
17 KiB
Go
596 lines
17 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"u-desk/internal/api"
|
||
"u-desk/internal/database"
|
||
"u-desk/internal/filesystem"
|
||
"u-desk/internal/storage"
|
||
"u-desk/internal/system"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||
)
|
||
|
||
// App 应用结构体
|
||
type App struct {
|
||
ctx context.Context
|
||
db *database.DB
|
||
connectionAPI *api.ConnectionAPI
|
||
sqlAPI *api.SqlAPI
|
||
tabAPI *api.TabAPI
|
||
updateAPI *api.UpdateAPI
|
||
fileServer *http.Server
|
||
}
|
||
|
||
// NewApp 创建新的应用实例
|
||
func NewApp() *App {
|
||
return &App{}
|
||
}
|
||
|
||
// Startup 应用启动时调用
|
||
func (a *App) Startup(ctx context.Context) {
|
||
a.ctx = ctx
|
||
|
||
// 1. 核心初始化:SQLite(必须同步,很快)
|
||
sqliteDB, err := storage.InitFast()
|
||
if err != nil {
|
||
panic(fmt.Sprintf("SQLite 初始化失败,应用无法启动: %v", err))
|
||
}
|
||
|
||
// 2. 快速初始化核心 API(都是毫秒级操作,不影响启动速度)
|
||
if err := a.initCoreAPIs(); err != nil {
|
||
panic(fmt.Sprintf("核心 API 初始化失败: %v", err))
|
||
}
|
||
|
||
// 3. 异步初始化:文件服务器(不等待)
|
||
go a.startFileServer()
|
||
|
||
// 4. 异步初始化:UpdateAPI(涉及网络请求,完全异步)
|
||
go func() {
|
||
if updateAPI, err := api.NewUpdateAPI("https://img.1216.top/u-desk/last-version.json"); err == nil {
|
||
a.updateAPI = updateAPI
|
||
a.updateAPI.SetContext(ctx)
|
||
}
|
||
}()
|
||
|
||
_ = sqliteDB // 标记已使用
|
||
}
|
||
|
||
// startFileServer 启动文件服务器
|
||
func (a *App) startFileServer() {
|
||
// 启动独立的本地文件服务器(使用 filesystem 包中的实现)
|
||
if _, err := filesystem.StartLocalFileServer(); err != nil {
|
||
fmt.Printf("[文件服务器] 启动失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 创建一个占位服务器用于保持引用(实际服务器由 StartLocalFileServer 管理)
|
||
a.fileServer = &http.Server{
|
||
Addr: "localhost:18765",
|
||
}
|
||
|
||
fmt.Println("[文件服务器] 启动在 http://localhost:18765")
|
||
}
|
||
|
||
// Shutdown 应用关闭时调用
|
||
func (a *App) Shutdown(ctx context.Context) {
|
||
// 停止文件服务器
|
||
if a.fileServer != nil {
|
||
fmt.Println("[文件服务器] 正在关闭...")
|
||
a.fileServer.Shutdown(ctx)
|
||
}
|
||
}
|
||
|
||
// QueryUsers 查询用户列表
|
||
func (a *App) QueryUsers(keyword string, status int, role int, organid int, page int, pageSize int, sortField string, sortOrder string) (map[string]interface{}, error) {
|
||
db, err := a.getDB()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return db.QueryUsers(keyword, status, role, organid, page, pageSize, sortField, sortOrder)
|
||
}
|
||
|
||
// getDB 获取数据库连接(延迟加载,按需初始化)
|
||
func (a *App) getDB() (*database.DB, error) {
|
||
if a.db != nil {
|
||
return a.db, nil
|
||
}
|
||
|
||
// 首次调用时才连接数据库
|
||
db, err := database.Init()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("数据库连接失败: %v", err)
|
||
}
|
||
|
||
a.db = db
|
||
return db, nil
|
||
}
|
||
|
||
// Greet 测试方法
|
||
func (a *App) Greet(name string) string {
|
||
return "Hello " + name + ", It's show time!"
|
||
}
|
||
|
||
// 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 filesystem.ReadFile(path)
|
||
}
|
||
|
||
// WriteFileRequest 写入文件请求结构体
|
||
type WriteFileRequest struct {
|
||
Path string `json:"path"`
|
||
Content string `json:"content"`
|
||
}
|
||
|
||
// WriteFile 写入文件
|
||
func (a *App) WriteFile(req WriteFileRequest) error {
|
||
return filesystem.WriteFile(req.Path, req.Content)
|
||
}
|
||
|
||
// ListDir 列出目录
|
||
func (a *App) ListDir(path string) ([]map[string]interface{}, error) {
|
||
return filesystem.ListDir(path)
|
||
}
|
||
|
||
// CreateDir 创建目录
|
||
func (a *App) CreateDir(path string) error {
|
||
return filesystem.CreateDir(path)
|
||
}
|
||
|
||
// CreateFile 创建文件
|
||
func (a *App) CreateFile(path string) error {
|
||
return filesystem.CreateFile(path)
|
||
}
|
||
|
||
// DeletePath 删除文件或目录
|
||
func (a *App) DeletePath(path string) error {
|
||
return filesystem.DeletePath(path)
|
||
}
|
||
|
||
// GetFileInfo 获取文件信息
|
||
func (a *App) GetFileInfo(path string) (map[string]interface{}, error) {
|
||
return 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 filesystem.OpenPath(path)
|
||
}
|
||
|
||
// ========== Zip 文件操作接口 ==========
|
||
|
||
// ListZipContents 列出 zip 文件内容
|
||
func (a *App) ListZipContents(zipPath string) ([]map[string]interface{}, error) {
|
||
return filesystem.ListZipContents(zipPath)
|
||
}
|
||
|
||
// ExtractFileFromZip 从 zip 文件中提取单个文件内容
|
||
func (a *App) ExtractFileFromZip(zipPath, filePath string) (string, error) {
|
||
return filesystem.ExtractFileFromZip(zipPath, filePath)
|
||
}
|
||
|
||
// ExtractFileFromZipToTemp 从 zip 文件中提取单个文件到临时目录
|
||
// 返回临时文件的完整路径,适用于图片等二进制文件
|
||
func (a *App) ExtractFileFromZipToTemp(zipPath, filePath string) (string, error) {
|
||
return filesystem.ExtractFileFromZipToTemp(zipPath, filePath)
|
||
}
|
||
|
||
// GetZipFileInfo 获取 zip 文件中特定文件的信息
|
||
func (a *App) GetZipFileInfo(zipPath, filePath string) (map[string]interface{}, error) {
|
||
return filesystem.GetZipFileInfo(zipPath, filePath)
|
||
}
|
||
|
||
// GetCommonPaths 获取常用系统路径
|
||
func (a *App) GetCommonPaths() (map[string]string, error) {
|
||
homeDir, err := os.UserHomeDir()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 获取所有可用驱动器(Windows)
|
||
drives := getSystemDrives()
|
||
|
||
paths := map[string]string{
|
||
"home": homeDir,
|
||
"desktop": filepath.Join(homeDir, "Desktop"),
|
||
"documents": filepath.Join(homeDir, "Documents"),
|
||
"downloads": filepath.Join(homeDir, "Downloads"),
|
||
}
|
||
|
||
// 动态添加所有盘符
|
||
for _, drive := range drives {
|
||
key := fmt.Sprintf("root_%s", drive[:1])
|
||
paths[key] = drive
|
||
}
|
||
|
||
return paths, nil
|
||
}
|
||
|
||
// getSystemDrives 获取系统所有可用驱动器
|
||
func getSystemDrives() []string {
|
||
var drives []string
|
||
|
||
// Windows: 检查 A-Z 所有盘符
|
||
for _, drive := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ" {
|
||
path := string(drive) + ":\\"
|
||
if _, err := os.Stat(path); err == nil {
|
||
drives = append(drives, path)
|
||
}
|
||
}
|
||
|
||
return drives
|
||
}
|
||
|
||
// ========== 数据库连接管理接口 ==========
|
||
|
||
// initCoreAPIs 初始化核心 API(快速操作,毫秒级完成)
|
||
func (a *App) initCoreAPIs() error {
|
||
var err error
|
||
|
||
// 初始化 ConnectionAPI
|
||
if a.connectionAPI, err = api.NewConnectionAPI(); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 初始化 SqlAPI
|
||
if a.sqlAPI, err = api.NewSqlAPI(); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 初始化 TabAPI
|
||
if a.tabAPI, err = api.NewTabAPI(); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// SaveDbConnection 保存数据库连接配置
|
||
func (a *App) SaveDbConnection(req api.SaveConnectionRequest) error {
|
||
return a.connectionAPI.SaveDbConnection(req)
|
||
}
|
||
|
||
// ListDbConnections 获取连接列表
|
||
func (a *App) ListDbConnections() ([]map[string]interface{}, error) {
|
||
return a.connectionAPI.ListDbConnections()
|
||
}
|
||
|
||
// DeleteDbConnection 删除连接配置
|
||
func (a *App) DeleteDbConnection(id uint) error {
|
||
return a.connectionAPI.DeleteDbConnection(id)
|
||
}
|
||
|
||
// TestDbConnection 测试连接(通过已保存的连接ID)
|
||
func (a *App) TestDbConnection(id uint) error {
|
||
return a.connectionAPI.TestDbConnection(id)
|
||
}
|
||
|
||
// TestDbConnectionWithParams 测试数据库连接(直接传入参数,不保存数据)
|
||
func (a *App) TestDbConnectionWithParams(req api.TestConnectionRequest) error {
|
||
return a.connectionAPI.TestDbConnectionWithParams(req)
|
||
}
|
||
|
||
// ExecuteSQL 执行 SQL 语句
|
||
// 注意:SQL 语句应该已经包含分页信息(LIMIT 和 OFFSET),由客户端添加
|
||
func (a *App) ExecuteSQL(connectionId uint, sqlStr string, database string) (map[string]interface{}, error) {
|
||
return a.sqlAPI.ExecuteSQL(connectionId, sqlStr, database)
|
||
}
|
||
|
||
// GetDatabases 获取数据库列表
|
||
func (a *App) GetDatabases(connectionId uint) ([]string, error) {
|
||
return a.sqlAPI.GetDatabases(connectionId)
|
||
}
|
||
|
||
// GetTables 获取表列表
|
||
func (a *App) GetTables(connectionId uint, database string) ([]string, error) {
|
||
return a.sqlAPI.GetTables(connectionId, database)
|
||
}
|
||
|
||
// GetTableStructure 获取表结构
|
||
func (a *App) GetTableStructure(connectionId uint, database, tableName string) (map[string]interface{}, error) {
|
||
return a.sqlAPI.GetTableStructure(connectionId, database, tableName)
|
||
}
|
||
|
||
// GetIndexes 获取索引列表
|
||
func (a *App) GetIndexes(connectionId uint, database, tableName string) ([]map[string]interface{}, error) {
|
||
return a.sqlAPI.GetIndexes(connectionId, database, tableName)
|
||
}
|
||
|
||
// PreviewTableStructure 预览表结构变更
|
||
func (a *App) PreviewTableStructure(connectionId uint, database, tableName string, structure map[string]interface{}) ([]string, error) {
|
||
return a.sqlAPI.PreviewTableStructure(connectionId, database, tableName, structure)
|
||
}
|
||
|
||
// UpdateTableStructure 更新表结构
|
||
func (a *App) UpdateTableStructure(connectionId uint, database, tableName string, structure map[string]interface{}) ([]string, error) {
|
||
return a.sqlAPI.UpdateTableStructure(connectionId, database, tableName, structure)
|
||
}
|
||
|
||
// SaveResult 手动保存执行结果
|
||
func (a *App) SaveResult(connectionId uint, database, sql string, resultType string, data interface{}, columns []string, rowsAffected int, executionTime int64) (map[string]interface{}, error) {
|
||
return a.sqlAPI.SaveResult(connectionId, database, sql, resultType, data, columns, rowsAffected, executionTime)
|
||
}
|
||
|
||
// GetResultHistory 获取结果历史
|
||
func (a *App) GetResultHistory(connectionId *uint, keyword string, limit, offset int) (map[string]interface{}, error) {
|
||
return a.sqlAPI.GetResultHistory(connectionId, keyword, limit, offset)
|
||
}
|
||
|
||
// GetResultHistoryByID 根据ID获取结果历史
|
||
func (a *App) GetResultHistoryByID(id uint) (map[string]interface{}, error) {
|
||
return a.sqlAPI.GetResultHistoryByID(id)
|
||
}
|
||
|
||
// DeleteResultHistory 删除结果历史
|
||
func (a *App) DeleteResultHistory(id uint) error {
|
||
return a.sqlAPI.DeleteResultHistory(id)
|
||
}
|
||
|
||
// Reload 重新加载窗口(用于菜单项)
|
||
func (a *App) Reload() {
|
||
if a.ctx != nil {
|
||
runtime.WindowReload(a.ctx)
|
||
}
|
||
}
|
||
|
||
// ClearCache 清理本地缓存(用于菜单项)
|
||
func (a *App) ClearCache() {
|
||
if a.ctx != nil {
|
||
// 发送事件到前端,让前端清理 localStorage
|
||
runtime.EventsEmit(a.ctx, "clear-cache")
|
||
}
|
||
}
|
||
|
||
// ========== 窗口控制方法 ==========
|
||
|
||
// WindowMinimize 最小化窗口
|
||
func (a *App) WindowMinimize() {
|
||
if a.ctx != nil {
|
||
runtime.WindowMinimise(a.ctx)
|
||
}
|
||
}
|
||
|
||
// WindowMaximize 最大化/还原窗口
|
||
func (a *App) WindowMaximize() {
|
||
if a.ctx != nil {
|
||
if runtime.WindowIsMaximised(a.ctx) {
|
||
runtime.WindowUnmaximise(a.ctx)
|
||
} else {
|
||
runtime.WindowMaximise(a.ctx)
|
||
}
|
||
}
|
||
}
|
||
|
||
// WindowClose 关闭窗口
|
||
func (a *App) WindowClose() {
|
||
if a.ctx != nil {
|
||
runtime.Quit(a.ctx)
|
||
}
|
||
}
|
||
|
||
// WindowIsMaximized 检查窗口是否最大化
|
||
func (a *App) WindowIsMaximized() bool {
|
||
if a.ctx != nil {
|
||
return runtime.WindowIsMaximised(a.ctx)
|
||
}
|
||
return false
|
||
}
|
||
|
||
// ========== SQL 标签页管理接口 ==========
|
||
|
||
// SaveSqlTabs 保存 SQL 标签页列表
|
||
func (a *App) SaveSqlTabs(tabs []map[string]interface{}) error {
|
||
return a.tabAPI.SaveSqlTabs(tabs)
|
||
}
|
||
|
||
// ListSqlTabs 获取 SQL 标签页列表
|
||
func (a *App) ListSqlTabs() ([]map[string]interface{}, error) {
|
||
return a.tabAPI.ListSqlTabs()
|
||
}
|
||
|
||
// ========== 版本更新管理接口 ==========
|
||
|
||
// CheckUpdate 检查更新(UpdateAPI 可能尚未初始化完成)
|
||
func (a *App) CheckUpdate() (map[string]interface{}, error) {
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI.CheckUpdate()
|
||
}
|
||
|
||
// GetCurrentVersion 获取当前版本号
|
||
func (a *App) GetCurrentVersion() (map[string]interface{}, error) {
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI.GetCurrentVersion()
|
||
}
|
||
|
||
// GetUpdateConfig 获取更新配置
|
||
func (a *App) GetUpdateConfig() (map[string]interface{}, error) {
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI.GetUpdateConfig()
|
||
}
|
||
|
||
// SetUpdateConfig 设置更新配置
|
||
func (a *App) SetUpdateConfig(autoCheckEnabled bool, checkIntervalMinutes int, checkURL string) (map[string]interface{}, error) {
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI.SetUpdateConfig(autoCheckEnabled, checkIntervalMinutes, checkURL)
|
||
}
|
||
|
||
// DownloadUpdate 下载更新包
|
||
func (a *App) DownloadUpdate(downloadURL string) (map[string]interface{}, error) {
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI.DownloadUpdate(downloadURL)
|
||
}
|
||
|
||
// InstallUpdate 安装更新包
|
||
func (a *App) InstallUpdate(installerPath string, autoRestart bool) (map[string]interface{}, error) {
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI.InstallUpdate(installerPath, autoRestart)
|
||
}
|
||
|
||
// InstallUpdateWithHash 安装更新包(带哈希验证)
|
||
func (a *App) InstallUpdateWithHash(installerPath string, autoRestart bool, expectedHash string, hashType string) (map[string]interface{}, error) {
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI.InstallUpdateWithHash(installerPath, autoRestart, expectedHash, hashType)
|
||
}
|
||
|
||
// VerifyUpdateFile 验证更新文件哈希值
|
||
func (a *App) VerifyUpdateFile(filePath string, expectedHash string, hashType string) (map[string]interface{}, error) {
|
||
if a.updateAPI == nil {
|
||
return nil, fmt.Errorf("更新功能正在初始化中")
|
||
}
|
||
return a.updateAPI.VerifyUpdateFile(filePath, expectedHash, hashType)
|
||
}
|
||
|
||
// ========== 应用生命周期管理 ==========
|
||
|
||
// shutdown 应用关闭时调用,清理资源
|
||
func (a *App) shutdown(ctx context.Context) {
|
||
// 关闭审计日志
|
||
filesystem.CloseAudit()
|
||
|
||
// 停止文件服务器
|
||
if a.fileServer != nil {
|
||
_ = a.fileServer.Shutdown(ctx)
|
||
}
|
||
}
|
||
|
||
// ========== 审计日志接口 ==========
|
||
|
||
// GetAuditLogs 获取审计日志
|
||
func (a *App) GetAuditLogs(limit int) ([]map[string]interface{}, error) {
|
||
userDataDir := getUserDataDir()
|
||
logDir := filepath.Join(userDataDir, "logs")
|
||
|
||
entries, err := filesystem.GetRecentLogs(logDir, limit)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 转换为map格式
|
||
result := make([]map[string]interface{}, len(entries))
|
||
for i, entry := range entries {
|
||
result[i] = map[string]interface{}{
|
||
"timestamp": entry.Timestamp.Format("2006-01-02 15:04:05"),
|
||
"operation": entry.Operation,
|
||
"path": entry.Path,
|
||
"size": entry.Size,
|
||
"is_directory": entry.IsDirectory,
|
||
"success": entry.Success,
|
||
"error": entry.Error,
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// ========== 文件服务器接口 ==========
|
||
|
||
// GetFileServerURL 获取本地文件服务器的URL
|
||
func (a *App) GetFileServerURL() string {
|
||
return "http://localhost:18765"
|
||
}
|
||
|
||
// ========== 回收站接口 ==========
|
||
|
||
// GetRecycleBinEntries 获取回收站条目
|
||
func (a *App) GetRecycleBinEntries() ([]map[string]interface{}, error) {
|
||
bin := filesystem.GetRecycleBin()
|
||
if bin == nil {
|
||
return []map[string]interface{}{}, nil
|
||
}
|
||
|
||
entries := bin.ListEntries()
|
||
result := make([]map[string]interface{}, len(entries))
|
||
|
||
for i, entry := range entries {
|
||
result[i] = map[string]interface{}{
|
||
"original_path": entry.OriginalPath,
|
||
"deleted_path": entry.DeletedPath,
|
||
"deleted_time": entry.DeletedTime.Format("2006-01-02 15:04:05"),
|
||
"size": entry.Size,
|
||
"is_directory": entry.IsDirectory,
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// RestoreFromRecycleBin 从回收站恢复文件
|
||
func (a *App) RestoreFromRecycleBin(recyclePath string) error {
|
||
bin := filesystem.GetRecycleBin()
|
||
if bin == nil {
|
||
return fmt.Errorf("回收站未初始化")
|
||
}
|
||
|
||
return bin.RestoreFromRecycleBin(recyclePath)
|
||
}
|
||
|
||
// DeletePermanently 永久删除回收站中的文件
|
||
func (a *App) DeletePermanently(recyclePath string) error {
|
||
bin := filesystem.GetRecycleBin()
|
||
if bin == nil {
|
||
return fmt.Errorf("回收站未初始化")
|
||
}
|
||
|
||
return bin.DeletePermanently(recyclePath)
|
||
}
|
||
|
||
// EmptyRecycleBin 清空回收站
|
||
func (a *App) EmptyRecycleBin() error {
|
||
bin := filesystem.GetRecycleBin()
|
||
if bin == nil {
|
||
return fmt.Errorf("回收站未初始化")
|
||
}
|
||
|
||
return bin.Empty()
|
||
}
|