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数据库备份文件还原的C#类。让我详细解释一下这个代码的功能和结构:

类的主要功能

RestoreManager 类的主要目的是从ZIP压缩的备份文件中还原MongoDB数据库数据。

构造函数分析

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

构造函数接收以下参数:

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

构造函数中进行了严格的参数验证,确保连接字符串不为空、密码不为空、备份文件存在。

核心方法解析

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

public async Task RestoreAsync()
  • 首先解压ZIP文件到临时目录
  • 获取所有 .bson 文件
  • 使用 Parallel.ForEachAsync 并行处理每个集合文件,提高还原效率

2. RestoreCollectionAsync() - 单个集合还原

private async Task RestoreCollectionAsync(FileInfo file)
  • 读取BSON文件中的数据
  • 根据文件名确定集合名称
  • 删除目标集合中的所有现有数据
  • 插入备份的数据

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

private async Task<List<BsonDocument>> ReadBackupDataAsync(FileInfo fileInfo)
  • 打开BSON文件流
  • 使用 BsonBinaryReader 逐个读取BSON文档
  • 将文档反序列化为 BsonDocument 对象

4. ExtractZipFile() - ZIP解压缩

private static void ExtractZipFile(string archivePath, string outFolder, string password)
  • 使用SharpZipLib库解压缩ZIP文件
  • 支持密码保护的ZIP文件
  • 创建必要的目录结构

技术特点

  1. 异步操作: 使用async/await模式提高性能
  2. 并行处理: 使用Parallel.ForEachAsync并行还原多个集合
  3. 错误处理: 包含适当的异常处理和日志记录
  4. 资源管理: 正确使用using语句管理文件流和其他资源
  5. BSON格式: 直接处理MongoDB的原生BSON格式,保证数据完整性

使用场景

这个类适用于:

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

依赖项

  • Dpz.Core.MongodbAccess: MongoDB访问层
  • ICSharpCode.SharpZipLib: ZIP文件处理
  • Microsoft.Extensions.Logging: 日志记录
  • MongoDB.Bson: MongoDB BSON文档处理

这个实现提供了一个完整的、生产就绪的MongoDB备份还原解决方案。

评论加载中...