288 lines
6.9 KiB
Go
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
|
|
}
|