重构:文件系统模块化架构,优化应用启动流程
This commit is contained in:
313
app.go
313
app.go
@@ -8,6 +8,7 @@ import (
|
||||
"go-desk/internal/filesystem"
|
||||
"go-desk/internal/storage"
|
||||
"go-desk/internal/system"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -23,6 +24,7 @@ type App struct {
|
||||
sqlAPI *api.SqlAPI
|
||||
tabAPI *api.TabAPI
|
||||
updateAPI *api.UpdateAPI
|
||||
fileServer *http.Server
|
||||
}
|
||||
|
||||
// NewApp 创建新的应用实例
|
||||
@@ -34,37 +36,79 @@ func NewApp() *App {
|
||||
func (a *App) Startup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
|
||||
// 初始化 SQLite 本地存储(核心依赖,必须成功)
|
||||
// 如果失败,应用无法正常工作,应该 panic
|
||||
_, err := storage.Init()
|
||||
// 1. 核心初始化:SQLite(必须同步,很快)
|
||||
sqliteDB, err := storage.InitFast()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("SQLite 初始化失败,应用无法启动: %v", err))
|
||||
}
|
||||
|
||||
// 初始化数据库连接(可选,用于测试功能)
|
||||
// 失败不影响核心功能,只记录日志
|
||||
appDB, err := database.Init()
|
||||
if err != nil {
|
||||
println("数据库连接失败(可选功能):", err.Error())
|
||||
} else {
|
||||
a.db = appDB
|
||||
// 2. 快速初始化核心 API(都是毫秒级操作,不影响启动速度)
|
||||
if err := a.initCoreAPIs(); err != nil {
|
||||
panic(fmt.Sprintf("核心 API 初始化失败: %v", err))
|
||||
}
|
||||
|
||||
// 初始化 API 层(依赖 storage)
|
||||
// 如果失败,应用无法正常工作,应该 panic
|
||||
if err := a.initAPIs(); 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/go-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
|
||||
}
|
||||
|
||||
// 设置 updateAPI 的上下文
|
||||
if a.updateAPI != nil {
|
||||
a.updateAPI.SetContext(ctx)
|
||||
// 创建一个占位服务器用于保持引用(实际服务器由 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) {
|
||||
return a.db.QueryUsers(keyword, status, role, organid, page, pageSize, sortField, sortOrder)
|
||||
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 测试方法
|
||||
@@ -97,9 +141,15 @@ 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(path, content string) error {
|
||||
return filesystem.WriteFile(path, content)
|
||||
func (a *App) WriteFile(req WriteFileRequest) error {
|
||||
return filesystem.WriteFile(req.Path, req.Content)
|
||||
}
|
||||
|
||||
// ListDir 列出目录
|
||||
@@ -112,6 +162,11 @@ 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)
|
||||
@@ -133,6 +188,34 @@ func (a *App) GetEnvVars() (map[string]string, error) {
|
||||
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()
|
||||
@@ -151,10 +234,9 @@ func (a *App) GetCommonPaths() (map[string]string, error) {
|
||||
}
|
||||
|
||||
// 动态添加所有盘符
|
||||
for i, drive := range drives {
|
||||
for _, drive := range drives {
|
||||
key := fmt.Sprintf("root_%s", drive[:1])
|
||||
paths[key] = drive
|
||||
_ = i // 避免未使用变量警告
|
||||
}
|
||||
|
||||
return paths, nil
|
||||
@@ -177,23 +259,26 @@ func getSystemDrives() []string {
|
||||
|
||||
// ========== 数据库连接管理接口 ==========
|
||||
|
||||
// initAPIs 初始化所有API(在startup中调用)
|
||||
func (a *App) initAPIs() error {
|
||||
// initCoreAPIs 初始化核心 API(快速操作,毫秒级完成)
|
||||
func (a *App) initCoreAPIs() error {
|
||||
var err error
|
||||
a.connectionAPI, err = api.NewConnectionAPI()
|
||||
if err != nil {
|
||||
|
||||
// 初始化 ConnectionAPI
|
||||
if a.connectionAPI, err = api.NewConnectionAPI(); err != nil {
|
||||
return err
|
||||
}
|
||||
a.sqlAPI, err = api.NewSqlAPI()
|
||||
if err != nil {
|
||||
|
||||
// 初始化 SqlAPI
|
||||
if a.sqlAPI, err = api.NewSqlAPI(); err != nil {
|
||||
return err
|
||||
}
|
||||
a.tabAPI, err = api.NewTabAPI()
|
||||
if err != nil {
|
||||
|
||||
// 初始化 TabAPI
|
||||
if a.tabAPI, err = api.NewTabAPI(); err != nil {
|
||||
return err
|
||||
}
|
||||
a.updateAPI, err = api.NewUpdateAPI("https://img.1216.top/go-desk/last-version.json")
|
||||
return err
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveDbConnection 保存数据库连接配置
|
||||
@@ -292,6 +377,41 @@ func (a *App) ClearCache() {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 窗口控制方法 ==========
|
||||
|
||||
// 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 标签页列表
|
||||
@@ -306,43 +426,170 @@ func (a *App) ListSqlTabs() ([]map[string]interface{}, error) {
|
||||
|
||||
// ========== 版本更新管理接口 ==========
|
||||
|
||||
// CheckUpdate 检查更新
|
||||
// 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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user