- 新增 ConfigAPI 和 ConfigService 实现配置管理 - 新增 SettingsPanel 和 UpdateNotification 组件 - 文件系统模块化重构,提升代码质量 - 提取公共函数,优化代码结构 - 版本号更新至 0.2.0
845 lines
23 KiB
Go
845 lines
23 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
stdruntime "runtime"
|
||
"time"
|
||
|
||
"u-desk/internal/api"
|
||
"u-desk/internal/common"
|
||
"u-desk/internal/database"
|
||
"u-desk/internal/filesystem"
|
||
"u-desk/internal/storage"
|
||
"u-desk/internal/system"
|
||
|
||
"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
|
||
configAPI *api.ConfigAPI
|
||
fileServer *http.Server
|
||
filesystem *filesystem.FileSystemService
|
||
}
|
||
|
||
// 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))
|
||
}
|
||
_ = sqliteDB // 全局 DB 已由 InitFast() 设置
|
||
|
||
// 2. 初始化配置服务(必需,用于读取模块启用状态)
|
||
configService, err := api.NewConfigAPI()
|
||
if err != nil {
|
||
panic(fmt.Sprintf("配置服务初始化失败: %v", err))
|
||
}
|
||
a.configAPI = configService
|
||
|
||
// 3. 读取配置,获取可见的 Tabs
|
||
visibleTabs := a.getVisibleTabs()
|
||
fmt.Printf("[启动] 可用的模块: %v\n", visibleTabs)
|
||
|
||
// 4. 根据配置初始化模块(条件初始化)
|
||
if err := a.initModulesByConfig(visibleTabs); err != nil {
|
||
panic(fmt.Sprintf("模块初始化失败: %v", err))
|
||
}
|
||
|
||
// 5. 异步初始化: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)
|
||
a.startAutoUpdateCheck()
|
||
}
|
||
}()
|
||
|
||
}
|
||
|
||
// 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
|
||
data, ok := config["data"].(map[string]interface{})
|
||
if !ok {
|
||
return common.DefaultVisibleTabs
|
||
}
|
||
|
||
// 提取 visibleTabs
|
||
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.TabDatabase) {
|
||
fmt.Println("[启动] 初始化数据库模块...")
|
||
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
|
||
}
|
||
|
||
fmt.Println("[启动] 数据库模块初始化完成")
|
||
} else {
|
||
fmt.Println("[启动] 跳过数据库模块(未启用)")
|
||
}
|
||
|
||
// 检查是否启用文件系统模块
|
||
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() {
|
||
// 启动独立的本地文件服务器(使用 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.filesystem != nil {
|
||
fmt.Println("[文件系统服务] 正在关闭...")
|
||
if err := a.filesystem.Close(ctx); err != nil {
|
||
fmt.Printf("[文件系统服务] 关闭失败: %v\n", err)
|
||
}
|
||
}
|
||
|
||
// 停止文件服务器
|
||
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 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)
|
||
}
|
||
|
||
// ListDir 列出目录
|
||
func (a *App) ListDir(path string) ([]map[string]interface{}, error) {
|
||
return a.filesystem.ListDir(path)
|
||
}
|
||
|
||
// CreateDir 创建目录
|
||
func (a *App) CreateDir(path string) error {
|
||
return a.filesystem.CreateDir(path)
|
||
}
|
||
|
||
// CreateFile 创建文件
|
||
func (a *App) CreateFile(path string) error {
|
||
return a.filesystem.CreateFile(path)
|
||
}
|
||
|
||
// DeletePath 删除文件或目录
|
||
func (a *App) DeletePath(path string) 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) 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
|
||
}
|
||
|
||
// 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,
|
||
"desktop": filepath.Join(homeDir, "Desktop"),
|
||
"documents": filepath.Join(homeDir, "Documents"),
|
||
"downloads": filepath.Join(homeDir, "Downloads"),
|
||
}
|
||
|
||
// Windows: 动态添加所有盘符
|
||
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
|
||
}
|
||
|
||
// ========== 数据库连接管理接口 ==========
|
||
|
||
// 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)
|
||
}
|
||
|
||
// startAutoUpdateCheck 启动自动更新检查
|
||
func (a *App) startAutoUpdateCheck() {
|
||
if a.updateAPI == nil {
|
||
return
|
||
}
|
||
|
||
config, err := a.updateAPI.GetUpdateConfig()
|
||
if err != nil || !config["success"].(bool) {
|
||
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()
|
||
|
||
// 启动定时器
|
||
ticker := time.NewTicker(time.Duration(interval) * time.Minute)
|
||
go func() {
|
||
for range ticker.C {
|
||
a.checkUpdate()
|
||
}
|
||
}()
|
||
}
|
||
|
||
// checkUpdate 执行更新检查
|
||
func (a *App) checkUpdate() {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
fmt.Printf("[自动检查更新] 发生错误: %v\n", r)
|
||
}
|
||
}()
|
||
|
||
if a.updateAPI == nil {
|
||
return
|
||
}
|
||
|
||
result, err := a.updateAPI.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.ctx != nil {
|
||
runtime.EventsEmit(a.ctx, "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 "http://localhost:18765"
|
||
}
|
||
|
||
// ========== 回收站接口 ==========
|
||
|
||
// 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.TabDatabase:
|
||
a.initDatabaseModule()
|
||
case common.TabFileSystem:
|
||
a.initFilesystemModule()
|
||
case common.TabDevice:
|
||
fmt.Println("[模块] 设备测试模块已启用")
|
||
}
|
||
}
|
||
}
|
||
|
||
// initDatabaseModule 延迟初始化数据库模块
|
||
func (a *App) initDatabaseModule() {
|
||
if a.connectionAPI != nil {
|
||
fmt.Println("[模块] 数据库模块已初始化,跳过")
|
||
return
|
||
}
|
||
|
||
fmt.Println("[模块] 延迟初始化数据库模块...")
|
||
var err error
|
||
|
||
// 初始化 ConnectionAPI
|
||
if a.connectionAPI, err = api.NewConnectionAPI(); err != nil {
|
||
fmt.Printf("[模块] 数据库模块初始化失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 初始化 SqlAPI
|
||
if a.sqlAPI, err = api.NewSqlAPI(); err != nil {
|
||
fmt.Printf("[模块] SqlAPI 初始化失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 初始化 TabAPI
|
||
if a.tabAPI, err = api.NewTabAPI(); err != nil {
|
||
fmt.Printf("[模块] TabAPI 初始化失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
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("[模块] 文件系统模块初始化完成")
|
||
}
|