Files
ssq-desk/internal/service/backup_service.go
2026-01-14 14:17:38 +08:00

288 lines
6.9 KiB
Go

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
}