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: 用于创建日志记录器
构造函数还会:
- 验证输入参数的有效性
- 创建数据库访问对象
RepositoryBase - 设置还原目录路径(格式:
restore/{数据库名}/{时间戳}) - 创建还原目录
核心方法
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 文件
- 支持密码保护的压缩文件
- 将文件解压到指定目录
关键特性
- 错误处理: 构造函数中有完整的参数验证
- 并行处理: 使用
Parallel.ForEachAsync并行还原多个集合 - 日志记录: 详细的操作日志,包括信息、调试和错误日志
- 文件管理: 自动创建带时间戳的还原目录
- 数据完整性: 还原前会清空目标集合,确保数据一致性
使用场景
这个类适用于:
- MongoDB数据库的灾难恢复
- 数据迁移
- 测试环境的数据重置
- 定期数据还原操作
依赖项
Dpz.Core.MongodbAccess: 自定义的MongoDB访问库ICSharpCode.SharpZipLib: ZIP文件处理MongoDB.Bson: MongoDB BSON数据处理Microsoft.Extensions.Logging: 日志记录
这个实现提供了一个完整、健壮的MongoDB备份还原解决方案。
评论加载中...