网站首页 网站源码
website
站点相关全部源代码,隐藏了一些关于服务器的信息
using System.IO.Pipelines;
using System.Security.Cryptography;
using System.Text;
using Dpz.Core.MongodbAccess;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;

namespace Dpz.Core.Backup;

/// <summary>
/// 数据备份
/// </summary>
public class BackupManager
{
    private readonly string _backupKey;
    private readonly IRepositoryAny _repositoryAny;
    private readonly string _backupPath;
    private readonly ILogger<BackupManager> _logger;
    private readonly string _today;
    private readonly string _database;

    public BackupManager(string? connectionString, string backupKey, ILoggerFactory loggerFactory)
    {
        _backupKey = backupKey;
        _repositoryAny = new RepositoryAny(connectionString);
        _logger = loggerFactory.CreateLogger<BackupManager>();
        _database = _repositoryAny.Database.DatabaseNamespace.DatabaseName;

        _logger.LogInformation("当前备份数据库:{Database}", _database);

        _today = DateTime.Now.ToString("yyyyMMdd");
        var backupPath = Path.Combine("backup", _database, _today);

        _logger.LogInformation("当前备份路径:{BackupPath}", backupPath);

        var directoryInfo = new DirectoryInfo(backupPath);
        if (!directoryInfo.Exists)
        {
            directoryInfo.Create();
            _logger.LogInformation("创建备份目录:{BackupPath}", backupPath);
        }

        _backupPath = directoryInfo.FullName;
    }

    /// <summary>
    /// 备份数据库
    /// </summary>
    /// <param name="filter">
    /// 需要过滤的集合
    /// Key :   集合名称   如:Config
    /// Value : 过滤条件   如: { CreateTime: { $gt: ISODate('2024-01-01') } }
    /// </param>
    /// <returns></returns>
    public async Task<BackupResult> BackupAsync(Dictionary<string, string>? filter = null)
    {
        var list = await _repositoryAny.GetAllCollectionAsync();
        _logger.LogInformation("开始备份数据库,获取集合:{@Collections}", list);
        await Parallel.ForEachAsync(
            list,
            async (collectionName, _) =>
            {
                await BackupCollectionAsync(filter, collectionName);
            }
        );

        var zipPath = Path.Combine(_backupPath, "..", $"{_database}_{_today}.zip");
        _logger.LogInformation("数据获取完毕,准备压缩,压缩文件路径:{ZipPath}", zipPath);

        var password = Sha256Encrypt(_backupKey + _database + _today);

        CreateZip(zipPath, password, _backupPath);
        _logger.LogInformation("压缩完成,压缩文件路径:{ZipPath}", zipPath);

        return new BackupResult(
            password,
            zipPath,
            _repositoryAny.Database.DatabaseNamespace.DatabaseName
        );
    }

    private async Task BackupCollectionAsync(
        Dictionary<string, string>? filter,
        string collectionName
    )
    {
        try
        {
            FilterDefinition<BsonDocument>? collectionFilter = null;
            if (
                filter != null
                && filter.TryGetValue(collectionName, out var value)
                && BsonDocument.TryParse(value, out var filterValue)
            )
            {
                _logger.LogInformation(
                    "backup {Database}, collection {Collection} filter:{@Filter}",
                    _database,
                    collectionName,
                    filterValue
                );
                collectionFilter = new BsonDocumentFilterDefinition<BsonDocument>(filterValue);
            }

            var data = await _repositoryAny.GetCollectionDataAsync(
                collectionName,
                collectionFilter
            );
            _logger.LogTrace("backup data:{@Data}", data);
            await SaveDataFileAsync(data, collectionName);
            _logger.LogInformation("备份集合:{Collection}完成", collectionName);
        }
        catch (Exception e)
        {
            _logger.LogError(e, "备份集合:{Collection}时出错", collectionName);
        }
    }

    private async Task SaveDataFileAsync(List<BsonDocument> data, string collectionName)
    {
        var filePath = Path.Combine(_backupPath, collectionName + ".bson");

        await using var fileStream = new FileStream(
            filePath,
            FileMode.Create,
            FileAccess.Write,
            FileShare.None,
            bufferSize: 1 << 12,
            useAsync: true
        );

        var pipeWriter = PipeWriter.Create(
            fileStream,
            new StreamPipeWriterOptions(leaveOpen: true)
        );
        try
        {
            foreach (var document in data)
            {
                var bsonBytes = document.ToBson();
                var memory = pipeWriter.GetMemory(bsonBytes.Length);
                bsonBytes.CopyTo(memory);
                pipeWriter.Advance(bsonBytes.Length);
            }
        }
        finally
        {
            await pipeWriter.FlushAsync();
            await pipeWriter.CompleteAsync();
        }
    }

    private static string Sha256Encrypt(string input)
    {
        var buffer = Encoding.UTF8.GetBytes(input);
        var sha256 = SHA256.Create();
        var hashBytes = sha256.ComputeHash(buffer);
        var key = new StringBuilder();
        for (var i = 0; i < hashBytes.Length; i++)
        {
            key.Append($"{hashBytes[i]:X2}");
            // if (i % 4 == 3 && i != hashBytes.Length - 1)
            //     key.Append(' ');
        }
        return key.ToString();
    }

    /// <summary>
    /// 创建一个zip文件
    /// </summary>
    /// <param name="outPathname">zip文件路径</param>
    /// <param name="password">密码</param>
    /// <param name="folderName">要压缩的文件夹路径</param>
    private static void CreateZip(string outPathname, string password, string folderName)
    {
        using var fsOut = File.Create(outPathname);
        using var zipStream = new ZipOutputStream(fsOut);
        //0-9, 9 是最高压缩级别
        zipStream.SetLevel(9);

        // 密码
        zipStream.Password = password;

        var folderOffset = folderName.Length + (folderName.EndsWith(Path.PathSeparator) ? 0 : 1);

        CompressFolder(folderName, zipStream, folderOffset);
    }

    /// <summary>
    /// 递归压缩文件夹
    /// </summary>
    /// <param name="path"></param>
    /// <param name="zipStream"></param>
    /// <param name="folderOffset"></param>
    private static void CompressFolder(string path, ZipOutputStream zipStream, int folderOffset)
    {
        var files = Directory.GetFiles(path);

        foreach (var filename in files)
        {
            var fi = new FileInfo(filename);

            // 根据文件夹名称命名zip文件名
            var entryName = filename[folderOffset..];

            // 修正不同系统造成的不同目录的斜杠问题
            entryName = ZipEntry.CleanName(entryName);

            var newEntry = new ZipEntry(entryName)
            {
                // Zip 存储2秒的颗粒度
                DateTime = fi.LastWriteTime,
                // 指定 AESKeySize 会使用 AES 加密。
                // 允许的值为 0(关闭)、128 或 256。
                // 如果使用 AES,则需要 ZipOutputStream 上的密码
                AESKeySize = 256,
                /*
                 * 为了让WinXP和Server2003中内置的解压程序、WinZip 8、Java和其他旧代码能够解压缩这个zip文件,你需要执行以下操作之一:
                 *      指定 UseZip64.Off 或设置文件大小。
                 *      如果文件大小超过4GB,或者不需要WinXP内置的兼容性,那么不需要上述操作,但是生成的zip文件将是Zip64格式,而并非所有工具都能理解这种格式。
                 */
                // zipStream.UseZip64 = UseZip64.Off;
                Size = fi.Length
            };

            zipStream.PutNextEntry(newEntry);

            // Zip 缓冲区
            var buffer = new byte[4096];
            using (var fsInput = File.OpenRead(filename))
            {
                StreamUtils.Copy(fsInput, zipStream, buffer);
            }

            zipStream.CloseEntry();
        }

        // 递归
        var folders = Directory.GetDirectories(path);
        foreach (var folder in folders)
        {
            CompressFolder(folder, zipStream, folderOffset);
        }
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

上述代码实现了一个数据库备份管理器 BackupManager,用于将 MongoDB 数据库中的数据备份到本地文件系统,并将备份数据压缩成 ZIP 文件。以下是代码的主要功能和结构的详细解释:

1. 类和构造函数

  • BackupManager 类:负责管理数据库的备份过程。
  • 构造函数:接受 MongoDB 的连接字符串、备份密钥和日志工厂。构造函数中初始化了数据库连接、日志记录器、备份路径等,并创建备份目录(如果不存在)。

2. 备份数据库

  • BackupAsync 方法:这是备份的主要入口方法。它接受一个可选的过滤条件字典,表示需要备份的集合及其对应的过滤条件。
    • 首先获取所有集合的名称。
    • 使用 Parallel.ForEachAsync 并行备份每个集合。
    • 备份完成后,生成一个 ZIP 文件,文件名包含数据库名称和当前日期,并使用 SHA-256 加密生成的密码。

3. 备份集合

  • BackupCollectionAsync 方法:负责备份单个集合。
    • 根据传入的过滤条件构建 MongoDB 查询。
    • 从数据库中获取数据并调用 SaveDataFileAsync 方法将数据保存为 BSON 文件。

4. 保存数据文件

  • SaveDataFileAsync 方法:将集合数据保存为 BSON 格式的文件。
    • 使用 PipeWriter 进行异步写入,以提高性能。

5. 加密和压缩

  • Sha256Encrypt 方法:生成输入字符串的 SHA-256 哈希值,用于生成 ZIP 文件的密码。
  • CreateZip 方法:创建一个 ZIP 文件并设置密码。
  • CompressFolder 方法:递归压缩指定文件夹中的所有文件和子文件夹。

6. 日志记录

  • 使用 ILogger 记录备份过程中的信息、警告和错误,便于后续的调试和监控。

7. 其他细节

  • 代码中使用了 ICSharpCode.SharpZipLib 库来处理 ZIP 文件的创建和压缩。
  • 备份的 BSON 文件和 ZIP 文件的路径是动态生成的,基于当前日期和数据库名称。

总结

整体而言,BackupManager 类提供了一个完整的解决方案,用于从 MongoDB 数据库中备份数据,支持过滤条件、异步操作、数据加密和压缩,适合需要定期备份数据库的场景。

loading