using Dpz.Core.MongodbAccess;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;

namespace Dpz.Core.Backup;

/// <summary>
/// 数据还原
/// </summary>
public class RestoreManager
{
    private readonly string _zipPassword;
    private readonly IRepositoryBase _repositoryBase;
    private readonly string _backupFilePath;
    private readonly string _restorePath;
    private readonly ILogger<RestoreManager> _logger;
    private readonly DirectoryInfo _restoreDirectory;

    /// <summary>
    /// 数据还原构造函数
    /// </summary>
    /// <param name="connectionString">要还原数据库的连接字符串</param>
    /// <param name="zipPassword">备份文件的解压密码</param>
    /// <param name="backupFilePath">备份文件的路径</param>
    /// <param name="loggerFactory">logger factory</param>
    /// <exception cref="Exception"></exception>
    public RestoreManager(
        string? connectionString,
        string zipPassword,
        string backupFilePath,
        ILoggerFactory loggerFactory
    )
    {
        if (string.IsNullOrEmpty(connectionString))
        {
            throw new Exception("连接字符串不能为空");
        }

        if (string.IsNullOrEmpty(zipPassword))
        {
            throw new Exception("解压缩密码不能为空");
        }

        if (string.IsNullOrEmpty(backupFilePath) || !File.Exists(backupFilePath))
        {
            throw new Exception("备份文件不存在");
        }

        _zipPassword = zipPassword;
        _repositoryBase = new RepositoryBase(connectionString);
        _logger = loggerFactory.CreateLogger<RestoreManager>();
        var database = _repositoryBase.Database.DatabaseNamespace.DatabaseName;

        _logger.LogInformation("当前还原数据库:{Database}", database);

        var folderName = DateTime.Now.ToString("yyyyMMdd_HHmmss");
        var restorePath = Path.Combine("restore", database, folderName);

        var directoryInfo = new DirectoryInfo(backupFilePath);
        _backupFilePath = directoryInfo.FullName;
        _logger.LogInformation("当前还原文件:{BackupFilePath}", backupFilePath);
        _restoreDirectory = new DirectoryInfo(restorePath);
        if (!_restoreDirectory.Exists)
        {
            _restoreDirectory.Create();
            _logger.LogInformation("创建还原目录:{BackupPath}", restorePath);
        }

        _restorePath = _restoreDirectory.FullName;
    }


    /// <summary>
    /// 还原
    /// </summary>
    public async Task RestoreAsync()
    {
        ExtractZipFile(_backupFilePath, _restorePath, _zipPassword);

        var files = _restoreDirectory.GetFiles("*.bson");
        // await RestoreCollectionAsync(files[0]);
        // return;

        await Parallel.ForEachAsync(files, async (file, _) => { await RestoreCollectionAsync(file); });

        _logger.LogInformation("数据库还原完毕");
    }

    private async Task RestoreCollectionAsync(FileInfo file)
    {
        var data = await ReadBackupDataAsync(file);
        if (data.Count == 0)
        {
            return;
        }

        var collectionName = Path.GetFileNameWithoutExtension(file.FullName);
        await _repositoryBase.DeleteAllAsync(collectionName);
        await _repositoryBase.InsertAsync(collectionName, data);
    }

    private async Task<List<BsonDocument>> ReadBackupDataAsync(FileInfo fileInfo)
    {
        if (!fileInfo.Exists)
        {
            return [];
        }

        var data = new List<BsonDocument>();
        _logger.LogInformation("读取备份数据文件:{FilePath}", fileInfo.FullName);

        try
        {
            await using var fileStream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
            using var reader = new BsonBinaryReader(fileStream);
            while (!reader.IsAtEndOfFile())
            {
                var bsonType = reader.ReadBsonType();
                if (bsonType == BsonType.EndOfDocument)
                {
                    break;
                }

                var document = ReadBsonDocument(reader);
                data.Add(document);
            }
        }
        catch (Exception e)
        {
            _logger.LogError(e, "读取备份数据文件失败:{FilePath}", fileInfo.FullName);
            return data;
        }

        _logger.LogDebug("读取备份数据文件完成:{@Data}", data);

        return data;
    }

    private static BsonDocument ReadBsonDocument(BsonBinaryReader reader)
    {
        var context = BsonDeserializationContext.CreateRoot(reader);
        var bsonDocumentSerializer = BsonSerializer.LookupSerializer<BsonDocument>();
        return bsonDocumentSerializer.Deserialize(context);
    }

    /// <summary>
    /// 解压缩zip
    /// </summary>
    /// <param name="archivePath">压缩文件路径</param>
    /// <param name="outFolder"></param>
    /// <param name="password"></param>
    private static void ExtractZipFile(string archivePath, string outFolder, string password)
    {
        using var fsInput = File.OpenRead(archivePath);
        using var zf = new ZipFile(fsInput);
        if (!string.IsNullOrEmpty(password))
        {
            zf.Password = password;
        }

        foreach (ZipEntry zipEntry in zf)
        {
            var entryFileName = zipEntry.Name;

            var fullZipToPath = Path.Combine(outFolder, entryFileName);
            var directoryName = Path.GetDirectoryName(fullZipToPath);
            if (directoryName is { Length: > 0 })
            {
                Directory.CreateDirectory(directoryName);
            }

            var buffer = new byte[4096];

            using var zipStream = zf.GetInputStream(zipEntry);
            using Stream fsOutput = File.Create(fullZipToPath);
            StreamUtils.Copy(zipStream, fsOutput, buffer);
        }
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个用于MongoDB数据库备份文件还原的管理类。让我详细解释一下这个代码的功能和结构:

类的主要功能

RestoreManager 类负责将压缩的MongoDB备份文件解压并还原到指定的数据库中。

构造函数

public RestoreManager(
    string? connectionString,
    string zipPassword,
    string backupFilePath,
    ILoggerFactory loggerFactory
)

构造函数接收以下参数:

  • connectionString: 目标数据库的连接字符串
  • zipPassword: 备份文件的解压密码
  • backupFilePath: 备份文件的完整路径
  • loggerFactory: 用于创建日志记录器

构造函数还会:

  1. 验证输入参数的有效性
  2. 创建数据库访问对象 RepositoryBase
  3. 设置还原目录路径(格式:restore/{数据库名}/{时间戳}
  4. 创建还原目录

核心方法

1. RestoreAsync() - 主要还原方法

public async Task RestoreAsync()
  • 解压备份文件到还原目录
  • 获取所有 .bson 文件
  • 并行处理每个文件,还原对应的集合
  • 记录还原完成日志

2. RestoreCollectionAsync(FileInfo file) - 集合还原

  • 读取单个 .bson 文件的数据
  • 根据文件名确定集合名称
  • 删除目标集合中的所有现有数据
  • 插入备份数据到集合中

3. ReadBackupDataAsync(FileInfo fileInfo) - 读取备份数据

  • 打开 .bson 文件
  • 使用 BsonBinaryReader 逐个读取 BSON 文档
  • 将读取的文档反序列化为 BsonDocument 对象
  • 返回文档列表

4. ReadBsonDocument(BsonBinaryReader reader) - BSON文档读取

  • 使用 MongoDB 的序列化器将二进制数据反序列化为 BsonDocument

5. ExtractZipFile() - ZIP文件解压

  • 使用 SharpZipLib 库解压加密的 ZIP 文件
  • 支持密码保护的压缩文件
  • 将文件解压到指定目录

关键特性

  1. 错误处理: 构造函数中有完整的参数验证
  2. 并行处理: 使用 Parallel.ForEachAsync 并行还原多个集合
  3. 日志记录: 详细的操作日志,包括信息、调试和错误日志
  4. 文件管理: 自动创建带时间戳的还原目录
  5. 数据完整性: 还原前会清空目标集合,确保数据一致性

使用场景

这个类适用于:

  • MongoDB数据库的灾难恢复
  • 数据迁移
  • 测试环境的数据重置
  • 定期数据还原操作

依赖项

  • Dpz.Core.MongodbAccess: 自定义的MongoDB访问库
  • ICSharpCode.SharpZipLib: ZIP文件处理
  • MongoDB.Bson: MongoDB BSON数据处理
  • Microsoft.Extensions.Logging: 日志记录

这个实现提供了一个完整、健壮的MongoDB备份还原解决方案。

评论加载中...