网站首页 网站源码
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);
}
}
}
上述代码实现了一个数据库备份管理器 BackupManager
,用于将 MongoDB 数据库中的数据备份到本地文件系统,并将备份数据压缩成 ZIP 文件。以下是代码的主要功能和结构的详细解释:
Parallel.ForEachAsync
并行备份每个集合。SaveDataFileAsync
方法将数据保存为 BSON 文件。PipeWriter
进行异步写入,以提高性能。ILogger
记录备份过程中的信息、警告和错误,便于后续的调试和监控。ICSharpCode.SharpZipLib
库来处理 ZIP 文件的创建和压缩。整体而言,BackupManager
类提供了一个完整的解决方案,用于从 MongoDB 数据库中备份数据,支持过滤条件、异步操作、数据加密和压缩,适合需要定期备份数据库的场景。