package service import ( "archive/zip" "encoding/json" "fmt" "os" "path/filepath" "ssq-desk/internal/database" "time" ) // BackupService 数据备份服务 type BackupService struct{} // NewBackupService 创建备份服务 func NewBackupService() *BackupService { return &BackupService{} } // BackupResult 备份结果 type BackupResult struct { BackupPath string `json:"backup_path"` FileName string `json:"file_name"` FileSize int64 `json:"file_size"` CreatedAt string `json:"created_at"` } // Backup 备份 SQLite 数据库 func (s *BackupService) Backup() (*BackupResult, error) { // 获取 SQLite 数据库路径 appDataDir, err := os.UserConfigDir() if err != nil { return nil, fmt.Errorf("获取用户配置目录失败: %v", err) } dbPath := filepath.Join(appDataDir, "ssq-desk", "data", "ssq.db") // 检查数据库文件是否存在 if _, err := os.Stat(dbPath); os.IsNotExist(err) { return nil, fmt.Errorf("数据库文件不存在: %s", dbPath) } // 创建备份目录 backupDir := filepath.Join(appDataDir, "ssq-desk", "backups") if err := os.MkdirAll(backupDir, 0755); err != nil { return nil, fmt.Errorf("创建备份目录失败: %v", err) } // 生成备份文件名(带时间戳) timestamp := time.Now().Format("20060102-150405") backupFileName := fmt.Sprintf("ssq-backup-%s.zip", timestamp) backupPath := filepath.Join(backupDir, backupFileName) // 创建 ZIP 文件 zipFile, err := os.Create(backupPath) if err != nil { return nil, fmt.Errorf("创建备份文件失败: %v", err) } defer zipFile.Close() zipWriter := zip.NewWriter(zipFile) defer zipWriter.Close() // 添加数据库文件到 ZIP dbFile, err := os.Open(dbPath) if err != nil { return nil, fmt.Errorf("打开数据库文件失败: %v", err) } defer dbFile.Close() dbInfo, err := dbFile.Stat() if err != nil { return nil, fmt.Errorf("获取数据库文件信息失败: %v", err) } dbHeader, err := zip.FileInfoHeader(dbInfo) if err != nil { return nil, fmt.Errorf("创建 ZIP 文件头失败: %v", err) } dbHeader.Name = "ssq.db" dbHeader.Method = zip.Deflate dbWriter, err := zipWriter.CreateHeader(dbHeader) if err != nil { return nil, fmt.Errorf("创建 ZIP 写入器失败: %v", err) } if _, err := dbWriter.Write([]byte{}); err != nil { return nil, fmt.Errorf("写入 ZIP 文件失败: %v", err) } // 重新写入数据库内容 if _, err := dbFile.Seek(0, 0); err != nil { return nil, fmt.Errorf("重置文件指针失败: %v", err) } buffer := make([]byte, 1024*1024) // 1MB buffer for { n, err := dbFile.Read(buffer) if n > 0 { if _, err := dbWriter.Write(buffer[:n]); err != nil { return nil, fmt.Errorf("写入数据库内容失败: %v", err) } } if err != nil { break } } // 添加元数据文件 metaData := map[string]interface{}{ "backup_time": time.Now().Format("2006-01-02 15:04:05"), "version": "1.0", } metaWriter, err := zipWriter.Create("metadata.json") if err != nil { return nil, fmt.Errorf("创建元数据文件失败: %v", err) } metaJSON, err := json.Marshal(metaData) if err != nil { return nil, fmt.Errorf("序列化元数据失败: %v", err) } if _, err := metaWriter.Write(metaJSON); err != nil { return nil, fmt.Errorf("写入元数据失败: %v", err) } // 获取备份文件大小 fileInfo, err := zipFile.Stat() if err != nil { return nil, fmt.Errorf("获取备份文件信息失败: %v", err) } return &BackupResult{ BackupPath: backupPath, FileName: backupFileName, FileSize: fileInfo.Size(), CreatedAt: time.Now().Format("2006-01-02 15:04:05"), }, nil } // Restore 恢复数据 func (s *BackupService) Restore(backupPath string) error { // 检查备份文件是否存在 if _, err := os.Stat(backupPath); os.IsNotExist(err) { return fmt.Errorf("备份文件不存在: %s", backupPath) } // 打开 ZIP 文件 zipReader, err := zip.OpenReader(backupPath) if err != nil { return fmt.Errorf("打开备份文件失败: %v", err) } defer zipReader.Close() // 获取 SQLite 数据库路径 appDataDir, err := os.UserConfigDir() if err != nil { return fmt.Errorf("获取用户配置目录失败: %v", err) } dataDir := filepath.Join(appDataDir, "ssq-desk", "data") if err := os.MkdirAll(dataDir, 0755); err != nil { return fmt.Errorf("创建数据目录失败: %v", err) } dbPath := filepath.Join(dataDir, "ssq.db") // 备份当前数据库(如果存在) if _, err := os.Stat(dbPath); err == nil { backupName := fmt.Sprintf("ssq.db.bak.%s", time.Now().Format("20060102-150405")) backupPath := filepath.Join(dataDir, backupName) if err := copyFile(dbPath, backupPath); err != nil { return fmt.Errorf("备份当前数据库失败: %v", err) } } // 查找数据库文件 var dbFile *zip.File for _, file := range zipReader.File { if file.Name == "ssq.db" { dbFile = file break } } if dbFile == nil { return fmt.Errorf("备份文件中未找到数据库文件") } // 解压数据库文件 rc, err := dbFile.Open() if err != nil { return fmt.Errorf("打开数据库文件失败: %v", err) } defer rc.Close() // 创建新的数据库文件 newDBFile, err := os.Create(dbPath) if err != nil { return fmt.Errorf("创建数据库文件失败: %v", err) } defer newDBFile.Close() // 复制数据 buffer := make([]byte, 1024*1024) // 1MB buffer for { n, err := rc.Read(buffer) if n > 0 { if _, err := newDBFile.Write(buffer[:n]); err != nil { return fmt.Errorf("写入数据库文件失败: %v", err) } } if err != nil { break } } // 重新初始化数据库连接 _, err = database.InitSQLite() if err != nil { return fmt.Errorf("重新初始化数据库失败: %v", err) } return nil } // ListBackups 列出所有备份文件 func (s *BackupService) ListBackups() ([]BackupResult, error) { appDataDir, err := os.UserConfigDir() if err != nil { return nil, fmt.Errorf("获取用户配置目录失败: %v", err) } backupDir := filepath.Join(appDataDir, "ssq-desk", "backups") // 检查备份目录是否存在 if _, err := os.Stat(backupDir); os.IsNotExist(err) { return []BackupResult{}, nil } files, err := os.ReadDir(backupDir) if err != nil { return nil, fmt.Errorf("读取备份目录失败: %v", err) } var backups []BackupResult for _, file := range files { if filepath.Ext(file.Name()) == ".zip" { filePath := filepath.Join(backupDir, file.Name()) fileInfo, err := file.Info() if err != nil { continue } backups = append(backups, BackupResult{ BackupPath: filePath, FileName: file.Name(), FileSize: fileInfo.Size(), CreatedAt: fileInfo.ModTime().Format("2006-01-02 15:04:05"), }) } } return backups, nil } // copyFile 复制文件 func copyFile(src, dst string) error { sourceFile, err := os.Open(src) if err != nil { return err } defer sourceFile.Close() destFile, err := os.Create(dst) if err != nil { return err } defer destFile.Close() _, err = destFile.ReadFrom(sourceFile) return err }