using System.Security.Cryptography;
using Dpz.Core.Infrastructure;
using Dpz.Core.Infrastructure.PublicStruct;
using Dpz.Core.Public.ViewModel.V4;
using Dpz.Core.Shard.Service;
using FluentFTP;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp;
namespace Dpz.Core.Service.ObjectStorage.Services.Impl;
public class ObjectStorageService(
UpyunOperator upyunOperator,
HttpClient httpClient,
ILogger<ObjectStorageService> logger,
IFtpLogger ftpLogger,
ParallelChunkBreakpointUpload<UpyunOperator> parallelChunkBreakpointUpload,
UpyunUpload upyunUpload)
: IObjectStorageOperation
{
private const string FtpHost = "v0.ftp.upyun.com";
public string Bucket => upyunOperator.Bucket ?? throw new BusinessException("Bucket is null");
public async Task<UploadResult> UploadAsync(Stream stream, ICollection<string> path, string filename)
{
return await UploadAsync(path, filename, () => new StreamContent(stream));
}
private async Task<UploadResult> UploadAsync(
IEnumerable<string> path,
string filename,
Func<HttpContent> setContent,
string? contentMd5 = null)
{
if (string.IsNullOrEmpty(filename))
throw new ArgumentNullException(nameof(filename));
var pathList = path.ToList();
pathList.Add(filename);
return await UploadAsync(pathList, setContent, contentMd5);
}
private async Task<UploadResult> UploadAsync(
ICollection<string> pathToFile,
Func<HttpContent> setContent,
string? contentMd5 = null)
{
return await upyunUpload.UploadAsync(
pathToFile,
setContent,
upyunOperator,
contentMd5);
}
public async Task<FileAddress?> UploadFileAsync(CloudFile file)
{
return await parallelChunkBreakpointUpload.UploadFileAsync(file, UploadAsync);
}
public async Task<UploadResult> UploadAsync(byte[] bytes, ICollection<string> path, string filename)
{
return await UploadAsync(path, filename, () => new ByteArrayContent(bytes));
}
private string CalculateMd5(byte[] bytes)
{
using var md5 = MD5.Create();
var hash = md5.ComputeHash(bytes);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
private async Task<string> GetImageFormatAsync(byte[] bytes)
{
try
{
using var stream = new MemoryStream(bytes);
using var image = await Image.LoadAsync(stream);
return image.Metadata.DecodedImageFormat?.FileExtensions.FirstOrDefault() ?? "jpg";
}
catch (Exception e)
{
logger.LogError(e, "get image format fail");
return "jpg";
}
}
public async Task<VmPictureRecord> UploadPictureAsync(byte[] bytes, PictureInformation information)
{
if (bytes == null)
throw new ArgumentNullException(nameof(bytes));
if (information == null)
throw new ArgumentNullException(nameof(information));
var pictureRecord = new VmPictureRecord
{
Creator = information.Uploader,
UploadTime = DateTime.Now,
Tags = information.Tags.ToList(),
Description = information.Description,
Category = information.Category
};
var filename = $"{information.Name ?? Guid.NewGuid().ToString("N")}.{await GetImageFormatAsync(bytes)}";
var uploadResult = await UploadAsync(new[] { "images", "album", DateTime.Now.ToString("yyyy-MM-dd") }, filename,
() => new ByteArrayContent(bytes));
pictureRecord.AccessUrl = uploadResult.AccessUrl;
pictureRecord.Length = bytes.Length;
pictureRecord.ObjectStorageUploadTime = DateTime.Now;
pictureRecord.Md5 = CalculateMd5(bytes);
pictureRecord.Width = uploadResult.Width;
pictureRecord.Height = uploadResult.Height;
return pictureRecord;
}
public async Task<Stream> DownloadAsync(string pathToFile)
{
var request = new HttpRequestMessage(HttpMethod.Get, $"/{Bucket}/{pathToFile}")
{
Version = new Version(2, 0)
};
await request.SignatureAsync(upyunOperator);
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
logger.LogError("download fail,status code:{StatusCode}", response.StatusCode);
throw new BusinessException($"download fail,response status code:{response.StatusCode}");
}
var stream = await response.Content.ReadAsStreamAsync();
return stream;
}
public async Task SaveAsAsync(string pathToFile, string path)
{
if (string.IsNullOrEmpty(pathToFile))
throw new ArgumentNullException(nameof(pathToFile));
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(nameof(path));
var stream = await DownloadAsync(pathToFile);
await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
await stream.CopyToAsync(fileStream);
await fileStream.FlushAsync();
}
public async Task DeleteAsync(string pathToFile)
{
if (string.IsNullOrEmpty(pathToFile))
throw new ArgumentNullException(nameof(pathToFile));
if (pathToFile.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
pathToFile =
pathToFile.Replace(
upyunOperator.Host ??
throw new BusinessException("configuration upyun operator host value is null"), "");
}
else
{
pathToFile = !pathToFile.StartsWith('/') ? $"/{pathToFile}" : pathToFile;
}
var request = new HttpRequestMessage(HttpMethod.Delete, $"/{Bucket}{pathToFile}")
{
Version = new Version(2, 0)
};
await request.SignatureAsync(upyunOperator);
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
logger.LogError("delete fail,status code:{StatusCode}", response.StatusCode);
}
}
public async Task<IList<FolderResult>> GetFolderListAsync(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
try
{
await using var client = new AsyncFtpClient(
FtpHost,
$"{upyunOperator.Operator}/{upyunOperator.Bucket}",
upyunOperator.Password,
logger: ftpLogger);
await client.Connect();
//await client.AutoConnectAsync();
var list = new List<FolderResult>();
foreach (var item in await client.GetListing(path))
{
var result = new FolderResult
{
LastUpdateTime = item.Modified,
Name = item.Name
};
if (item.Type == FtpObjectType.Directory)
{
result.FileType = FileType.Directory;
}
else
{
result.FileType = FileType.File;
result.Size = await client.GetFileSize(item.FullName);
}
list.Add(result);
}
await client.Disconnect();
return list;
}
catch (Exception ex)
{
logger.LogError(ex, "get folder list fail");
return new List<FolderResult>();
}
}
public async Task<FileInformation> GetFileInformationAsync(string pathToFile)
{
if (string.IsNullOrEmpty(pathToFile))
throw new ArgumentNullException(nameof(pathToFile));
pathToFile = !pathToFile.StartsWith('/') ? $"/{pathToFile}" : pathToFile;
var request = new HttpRequestMessage(HttpMethod.Head, $"/{Bucket}{pathToFile}")
{
Version = new Version(2, 0)
};
await request.SignatureAsync(upyunOperator);
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
logger.LogError("delete fail,status code:{StatusCode}", response.StatusCode);
throw new BusinessException($"delete fail,response status code:{response.StatusCode}");
}
// response.Headers.Contains("x-upyun-frames")
// ? response.Headers.GetValues("x-upyun-file-size").FirstOrDefault() ?? "0"
// : "0";
response.Headers.TryGetValues("x-upyun-file-size", out var size);
long.TryParse(size?.FirstOrDefault() ?? "0", out var length);
response.Headers.TryGetValues("x-upyun-file-date", out var date);
long.TryParse(date?.FirstOrDefault() ?? "0", out var timespan);
var dateTime = timespan.ToDateTime();
response.Headers.TryGetValues("Content-Md5", out var md5);
var information = new FileInformation
{
Length = length,
Md5 = md5?.FirstOrDefault() ?? "",
UploadTime = dateTime
};
return information;
}
}