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文件
- 创建必要的目录结构
技术特点
- 异步操作: 使用async/await模式提高性能
- 并行处理: 使用
Parallel.ForEachAsync并行还原多个集合 - 错误处理: 包含适当的异常处理和日志记录
- 资源管理: 正确使用using语句管理文件流和其他资源
- BSON格式: 直接处理MongoDB的原生BSON格式,保证数据完整性
使用场景
这个类适用于:
- MongoDB数据库灾难恢复
- 数据迁移
- 定期数据还原
- 测试环境数据重置
依赖项
Dpz.Core.MongodbAccess: MongoDB访问层ICSharpCode.SharpZipLib: ZIP文件处理Microsoft.Extensions.Logging: 日志记录MongoDB.Bson: MongoDB BSON文档处理
这个实现提供了一个完整的、生产就绪的MongoDB备份还原解决方案。
评论加载中...