refactor: 枚举移入Models目录,命名空间更新为Rainbow.Entity.Models
大石头 authored at 2026-07-02 12:54:58
7.23 KiB
RainbowBridge
using System;
using System.IO;
using System.Threading.Tasks;
using NewLife;
using NewLife.Log;
using Rainbow.Entity;

namespace Rainbow.Services;

/// <summary>配置文件备份器。备份/回滚/清理</summary>
public class ConfigBackup
{
    private readonly String _backupRoot;

    /// <summary>默认备份的配置文件列表</summary>
    public static readonly String[] DefaultConfigFiles = [
        "/etc/dnsmasq.conf",
        "/etc/dnsmasq.d/",
        "/etc/iptables/",
        "/etc/ppp/",
        "/etc/network/interfaces",
        "/etc/netplan/"
    ];

    /// <summary>创建备份器</summary>
    /// <param name="backupRoot">备份根目录,默认 Data/Backup</param>
    public ConfigBackup(String? backupRoot = null)
    {
        _backupRoot = (backupRoot ?? "Data/Backup").GetFullPath();
    }

    /// <summary>备份单个文件到备份目录,创建 ConfigSnapshot 记录</summary>
    /// <param name="filePath">源文件路径</param>
    /// <returns>备份文件路径,文件不存在时返回 null</returns>
    public async Task<String?> BackupAsync(String filePath)
    {
        if (filePath.IsNullOrEmpty()) throw new ArgumentNullException(nameof(filePath));

        var sourceFile = filePath.GetFullPath();
        if (!File.Exists(sourceFile))
        {
            XTrace.WriteLine("ConfigBackup: 源文件不存在 {0}", sourceFile);
            return null;
        }

        // 备份目录:BackupRoot/yyyyMMdd/
        var dateDir = Path.Combine(_backupRoot, DateTime.Now.ToString("yyyyMMdd"));
        if (!Directory.Exists(dateDir)) Directory.CreateDirectory(dateDir);

        // 备份文件名:原文件名_yyyyMMddHHmmss.bak
        var fileName = Path.GetFileName(sourceFile);
        var backupName = $"{fileName}_{DateTime.Now:yyyyMMddHHmmss}.bak";
        var backupPath = Path.Combine(dateDir, backupName);

        // 异步复制文件
        using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
        using (var destStream = new FileStream(backupPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
        {
            await sourceStream.CopyToAsync(destStream);
        }

        var fileInfo = new FileInfo(sourceFile);

        // 创建 ConfigSnapshot 记录
        var snapshot = new ConfigSnapshot
        {
            FileName = fileName,
            BackupPath = backupPath,
            SourcePath = sourceFile,
            FileSize = fileInfo.Length,
            Enable = true
        };
        snapshot.Insert();

        XTrace.WriteLine("ConfigBackup: 备份完成 {0} → {1}", sourceFile, backupPath);

        return backupPath;
    }

    /// <summary>从备份恢复配置文件</summary>
    /// <param name="backupPath">备份文件路径</param>
    public async Task RollbackAsync(String backupPath)
    {
        if (backupPath.IsNullOrEmpty()) throw new ArgumentNullException(nameof(backupPath));

        var backupFile = backupPath.GetFullPath();
        if (!File.Exists(backupFile))
            throw new FileNotFoundException("备份文件不存在", backupFile);

        // 查找对应的 ConfigSnapshot 记录
        var snapshot = ConfigSnapshot.Find(ConfigSnapshot._.BackupPath == backupFile);
        if (snapshot == null)
            throw new InvalidOperationException($"未找到备份记录: {backupFile}");

        var sourcePath = snapshot.SourcePath;
        if (sourcePath.IsNullOrEmpty())
            throw new InvalidOperationException("备份记录中源文件路径为空");

        // 确保源目录存在
        var sourceDir = Path.GetDirectoryName(sourcePath);
        if (!sourceDir.IsNullOrEmpty() && !Directory.Exists(sourceDir))
            Directory.CreateDirectory(sourceDir);

        // 复制备份到源位置
        using (var sourceStream = new FileStream(backupFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
        using (var destStream = new FileStream(sourcePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
        {
            await sourceStream.CopyToAsync(destStream);
        }

        XTrace.WriteLine("ConfigBackup: 回滚完成 {0} → {1}", backupFile, sourcePath);
    }

    /// <summary>清理 N 天前的旧备份(同时删除文件和数据库记录)</summary>
    /// <param name="days">保留天数,默认 7</param>
    public void CleanOldBackups(Int32 days = 7)
    {
        var cutoff = DateTime.Now.AddDays(-days);
        var oldSnapshots = ConfigSnapshot.FindAll(
            ConfigSnapshot._.CreateTime < cutoff & ConfigSnapshot._.Enable == true);

        var deleted = 0;
        foreach (var snapshot in oldSnapshots)
        {
            try
            {
                // 删除备份文件
                var backupPath = snapshot.BackupPath;
                if (!backupPath.IsNullOrEmpty() && File.Exists(backupPath))
                    File.Delete(backupPath);

                // 删除数据库记录
                snapshot.Delete();
                deleted++;
            }
            catch (Exception ex)
            {
                XTrace.WriteException(ex);
            }
        }

        // 清理空的日期目录
        if (Directory.Exists(_backupRoot))
        {
            foreach (var dir in Directory.GetDirectories(_backupRoot))
            {
                try
                {
                    if (Directory.GetFileSystemEntries(dir).Length == 0)
                        Directory.Delete(dir);
                }
                catch
                {
                    // 忽略目录删除失败
                }
            }
        }

        if (deleted > 0)
            XTrace.WriteLine("ConfigBackup: 清理完成,删除 {0} 条旧备份", deleted);
    }

    /// <summary>备份所有默认配置文件</summary>
    /// <returns>成功备份的文件数</returns>
    public async Task<Int32> BackupAllAsync()
    {
        var count = 0;
        foreach (var pattern in DefaultConfigFiles)
        {
            try
            {
                if (Directory.Exists(pattern))
                {
                    foreach (var file in Directory.GetFiles(pattern, "*", SearchOption.AllDirectories))
                    {
                        if (await BackupAsync(file) != null)
                            count++;
                    }
                }
                else if (File.Exists(pattern))
                {
                    if (await BackupAsync(pattern) != null)
                        count++;
                }
            }
            catch (Exception ex)
            {
                XTrace.WriteException(ex);
            }
        }
        return count;
    }

    /// <summary>导出指定日期范围内的备份文件路径列表</summary>
    /// <param name="days">最近 N 天</param>
    /// <returns>备份文件路径列表</returns>
    public IEnumerable<String> GetBackupFiles(Int32 days = 7)
    {
        var cutoff = DateTime.Now.AddDays(-days);
        var snapshots = ConfigSnapshot.FindAll(
            ConfigSnapshot._.CreateTime >= cutoff & ConfigSnapshot._.Enable == true);

        foreach (var snapshot in snapshots)
        {
            if (!snapshot.BackupPath.IsNullOrEmpty() && File.Exists(snapshot.BackupPath))
                yield return snapshot.BackupPath;
        }
    }
}