Работа на новой архитектуре
This commit is contained in:
@@ -45,65 +45,69 @@ public class S3FileStorageService : IFileStorageService
|
||||
{
|
||||
await EnsureBucketExistsAsync();
|
||||
|
||||
// 1. Вычисляем SHA-256 для Content-Addressable Storage (CAS) - дедупликация
|
||||
string fileHash;
|
||||
using (var sha256 = SHA256.Create())
|
||||
using (var msHash = new MemoryStream())
|
||||
{
|
||||
var startPos = fileStream.Position;
|
||||
await fileStream.CopyToAsync(msHash);
|
||||
var hashBytes = sha256.ComputeHash(msHash.ToArray());
|
||||
fileHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||
|
||||
// Возвращаем указатель потока в начало для последующего чтения
|
||||
fileStream.Position = startPos;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(fileName);
|
||||
var fileId = $"{fileHash}{ext}";
|
||||
|
||||
// 2. Шифруем файл "на лету" во временный файл
|
||||
// Для больших файлов мы используем временный файл, чтобы не перегружать оперативную память (RAM)
|
||||
var tempFilePath = Path.GetTempFileName();
|
||||
var tempUnencryptedPath = Path.GetTempFileName();
|
||||
var tempEncryptedPath = Path.GetTempFileName();
|
||||
byte[] ivParams;
|
||||
|
||||
try
|
||||
{
|
||||
using (var tempFs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write))
|
||||
using (var cryptoStream = _encryptionService.CreateEncryptionStream(tempFs, out ivParams))
|
||||
// 1. Сохраняем входной поток (который может быть не seekable из-за HTTP/Kestrel)
|
||||
// во временный файл
|
||||
using (var unencryptedFs = new FileStream(tempUnencryptedPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
await fileStream.CopyToAsync(cryptoStream);
|
||||
await fileStream.CopyToAsync(unencryptedFs);
|
||||
}
|
||||
|
||||
// 3. Загружаем зашифрованный файл в MinIO
|
||||
using var fileToUpload = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read);
|
||||
var metadata = new System.Collections.Generic.Dictionary<string, string>
|
||||
// 2. Вычисляем SHA-256 для дедупликации, читая из локального временного файла
|
||||
string fileHash;
|
||||
using (var unencryptedFs = new FileStream(tempUnencryptedPath, FileMode.Open, FileAccess.Read))
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
{ "ContentType", contentType },
|
||||
{ "OriginalFileName", fileName },
|
||||
{ "IV", Convert.ToBase64String(ivParams) },
|
||||
{ "KeyVersion", "1" },
|
||||
{ "EncryptionAlgorithm", "AES-256-CBC" } // Как реализовано в потоке AES CBC
|
||||
};
|
||||
var hashBytes = await sha256.ComputeHashAsync(unencryptedFs);
|
||||
fileHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
var putObjectArgs = new PutObjectArgs()
|
||||
.WithBucket(_bucketName)
|
||||
.WithObject(fileId)
|
||||
.WithStreamData(fileToUpload)
|
||||
.WithObjectSize(fileToUpload.Length)
|
||||
.WithContentType("application/octet-stream")
|
||||
.WithHeaders(metadata);
|
||||
var ext = Path.GetExtension(fileName);
|
||||
if (string.IsNullOrEmpty(ext)) ext = ""; // For files without extension
|
||||
var fileId = $"{fileHash}{ext}";
|
||||
|
||||
await _minioClient.PutObjectAsync(putObjectArgs).ConfigureAwait(false);
|
||||
// 3. Шифруем файл "на лету" во второй временный файл
|
||||
using (var unencryptedFs = new FileStream(tempUnencryptedPath, FileMode.Open, FileAccess.Read))
|
||||
using (var encryptedFs = new FileStream(tempEncryptedPath, FileMode.Create, FileAccess.Write))
|
||||
using (var cryptoStream = _encryptionService.CreateEncryptionStream(encryptedFs, out ivParams))
|
||||
{
|
||||
await unencryptedFs.CopyToAsync(cryptoStream);
|
||||
}
|
||||
|
||||
// 4. Загружаем зашифрованный файл в MinIO
|
||||
using (var fileToUpload = new FileStream(tempEncryptedPath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
var metadata = new System.Collections.Generic.Dictionary<string, string>
|
||||
{
|
||||
{ "ContentType", contentType ?? "application/octet-stream" },
|
||||
{ "OriginalFileName", Uri.EscapeDataString(fileName ?? "unknown") },
|
||||
{ "IV", Convert.ToBase64String(ivParams) },
|
||||
{ "KeyVersion", "1" },
|
||||
{ "EncryptionAlgorithm", "AES-256-CBC" }
|
||||
};
|
||||
|
||||
var putObjectArgs = new PutObjectArgs()
|
||||
.WithBucket(_bucketName)
|
||||
.WithObject(fileId)
|
||||
.WithStreamData(fileToUpload)
|
||||
.WithObjectSize(fileToUpload.Length)
|
||||
.WithContentType("application/octet-stream")
|
||||
.WithHeaders(metadata);
|
||||
|
||||
await _minioClient.PutObjectAsync(putObjectArgs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return fileId;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(tempFilePath))
|
||||
{
|
||||
File.Delete(tempFilePath);
|
||||
}
|
||||
if (File.Exists(tempUnencryptedPath)) File.Delete(tempUnencryptedPath);
|
||||
if (File.Exists(tempEncryptedPath)) File.Delete(tempEncryptedPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,19 +122,36 @@ public class S3FileStorageService : IFileStorageService
|
||||
var statArgs = new StatObjectArgs().WithBucket(_bucketName).WithObject(fileId);
|
||||
var stat = await _minioClient.StatObjectAsync(statArgs).ConfigureAwait(false);
|
||||
|
||||
if (stat.MetaData.ContainsKey("Contenttype"))
|
||||
var metaData = new Dictionary<string, string>(stat.MetaData, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Function to reliably get metadata by key with or without x-amz-meta-
|
||||
string? GetMeta(string key)
|
||||
{
|
||||
contentType = stat.MetaData["Contenttype"];
|
||||
if (metaData.TryGetValue(key, out var val)) return val;
|
||||
if (metaData.TryGetValue($"X-Amz-Meta-{key}", out var val2)) return val2;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stat.MetaData.ContainsKey("Originalfilename"))
|
||||
if (GetMeta("Contenttype") is string ct)
|
||||
{
|
||||
fileName = stat.MetaData["Originalfilename"];
|
||||
contentType = ct;
|
||||
}
|
||||
|
||||
if (stat.MetaData.ContainsKey("Iv"))
|
||||
if (GetMeta("Originalfilename") is string ofn)
|
||||
{
|
||||
ivBase64 = stat.MetaData["Iv"];
|
||||
try
|
||||
{
|
||||
fileName = Uri.UnescapeDataString(ofn);
|
||||
}
|
||||
catch
|
||||
{
|
||||
fileName = ofn; // fallback to unescaped if it was somehow valid
|
||||
}
|
||||
}
|
||||
|
||||
if (GetMeta("Iv") is string ivStr)
|
||||
{
|
||||
ivBase64 = ivStr;
|
||||
}
|
||||
|
||||
// Загружаем во временный файл (так как CryptoStream требует правильного чтения/записи)
|
||||
|
||||
Reference in New Issue
Block a user