package store import ( "fmt" "os" "path/filepath" "sort" "strings" "time" ) // SetBackupDir 设置备份目录(来自配置文件)。 func (db *DB) SetBackupDir(dir string) { db.BackupPath = dir } // BackupDir 返回备份目录。 func (db *DB) BackupDir() string { if db.BackupPath != "" { return db.BackupPath } return filepath.Join(filepath.Dir(db.Path), "backup") } // BackupDaily 当天首次调用时生成一份备份;已有今日备份则跳过。 // 空库(还没有客户)不生成备份,避免第一天的备份是空壳。 func (db *DB) BackupDaily(keep int) (string, error) { dir := db.BackupDir() if err := os.MkdirAll(dir, 0o755); err != nil { return "", err } now := time.Now() prefix := "daili-" + now.Format("20060102") + "-" entries, err := os.ReadDir(dir) if err != nil { return "", err } for _, e := range entries { if strings.HasPrefix(e.Name(), prefix) { return "", nil } } if n, err := db.CustomerCount(); err != nil || n == 0 { return "", err } target := filepath.Join(dir, fmt.Sprintf("daili-%s.db", now.Format("20060102-150405"))) return db.BackupTo(target, keep) } // BackupNow 立即生成一份备份。 func (db *DB) BackupNow(keep int) (string, error) { dir := db.BackupDir() if err := os.MkdirAll(dir, 0o755); err != nil { return "", err } target := filepath.Join(dir, fmt.Sprintf("daili-%s.db", time.Now().Format("20060102-150405"))) return db.BackupTo(target, keep) } // BackupTo 用 VACUUM INTO 生成一致性备份,并按保留份数清理。 func (db *DB) BackupTo(target string, keep int) (string, error) { abs, err := filepath.Abs(target) if err != nil { return "", err } if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil { return "", fmt.Errorf("创建备份目录失败: %w", err) } if _, err := os.Stat(abs); err == nil { return abs, fmt.Errorf("备份文件已存在: %s", filepath.Base(abs)) } escaped := strings.ReplaceAll(strings.ReplaceAll(abs, "\\", "/"), "'", "''") if _, err := db.sql.Exec("VACUUM INTO '" + escaped + "'"); err != nil { return "", err } if keep > 0 { _ = db.pruneBackups(keep) } return abs, nil } // ListBackups 列出备份(新→旧)。 func (db *DB) ListBackups() ([]string, error) { dir := db.BackupDir() entries, err := os.ReadDir(dir) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, err } var out []string for _, e := range entries { if !e.IsDir() && strings.HasSuffix(e.Name(), ".db") { out = append(out, e.Name()) } } sort.Sort(sort.Reverse(sort.StringSlice(out))) return out, nil } func (db *DB) pruneBackups(keep int) error { list, err := db.ListBackups() if err != nil { return err } if len(list) <= keep { return nil } dir := db.BackupDir() for _, name := range list[keep:] { _ = os.Remove(filepath.Join(dir, name)) } return nil }