using Dpz.Core.Entity.Base.PublicStruct;
using Dpz.Core.Infrastructure;
using Dpz.Core.Shard.Service;
using FluentFTP;
using Microsoft.Extensions.Logging;
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,
CancellationToken cancellationToken = default
)
{
return await UploadAsync(
path,
filename,
() => new StreamContent(stream),
cancellationToken: cancellationToken
);
}
private async Task<UploadResult> UploadAsync(
IEnumerable<string> path,
string filename,
Func<HttpContent> setContent,
string? contentMd5 = null,
CancellationToken cancellationToken = default
)
{
if (string.IsNullOrEmpty(filename))
{
throw new ArgumentNullException(nameof(filename));
}
var pathList = path.ToList();
pathList.Add(filename);
return await UploadAsync(pathList, setContent, contentMd5, cancellationToken);
}
private async Task<UploadResult> UploadAsync(
ICollection<string> pathToFile,
Func<HttpContent> setContent,
string? contentMd5 = null,
CancellationToken cancellationToken = default
)
{
return await upyunUpload.UploadAsync(
pathToFile,
setContent,
upyunOperator,
contentMd5,
cancellationToken
);
}
public async Task<FileAddress?> UploadFileAsync(
CloudFile file,
CancellationToken cancellationToken = default
)
{
return await parallelChunkBreakpointUpload.UploadFileAsync(
file,
UploadAsync,
cancellationToken: cancellationToken
);
}
public async Task<UploadResult> UploadAsync(
byte[] bytes,
ICollection<string> path,
string filename,
CancellationToken cancellationToken = default
)
{
return await UploadAsync(
path,
filename,
() => new ByteArrayContent(bytes),
cancellationToken: cancellationToken
);
}
public async Task<Stream> DownloadAsync(
string pathToFile,
CancellationToken cancellationToken = default
)
{
var request = new HttpRequestMessage(HttpMethod.Get, $"/{Bucket}/{pathToFile}")
{
Version = new Version(2, 0),
};
await request.SignatureAsync(upyunOperator, cancellationToken: cancellationToken);
var response = await httpClient.SendAsync(request, cancellationToken);
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(cancellationToken);
return stream;
}
public async Task SaveAsAsync(
string pathToFile,
string path,
CancellationToken cancellationToken = default
)
{
if (string.IsNullOrEmpty(pathToFile))
{
throw new ArgumentNullException(nameof(pathToFile));
}
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
}
var stream = await DownloadAsync(pathToFile, cancellationToken);
await using var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
await stream.CopyToAsync(fileStream, cancellationToken);
await fileStream.FlushAsync(cancellationToken);
}
public async Task DeleteAsync(string? pathToFile, CancellationToken cancellationToken = default)
{
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, cancellationToken: cancellationToken);
var response = await httpClient.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
logger.LogError("delete fail,status code:{StatusCode}", response.StatusCode);
}
}
public async Task<IList<FolderResult>> GetFolderListAsync(
string path,
CancellationToken cancellationToken = default
)
{
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(cancellationToken);
//await client.AutoConnectAsync();
var list = new List<FolderResult>();
foreach (var item in await client.GetListing(path, cancellationToken))
{
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, token: cancellationToken);
}
list.Add(result);
}
await client.Disconnect(cancellationToken);
return list;
}
catch (Exception ex)
{
logger.LogError(ex, "get folder list fail");
return new List<FolderResult>();
}
}
public async Task<FileInformation> GetFileInformationAsync(
string pathToFile,
CancellationToken cancellationToken = default
)
{
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, cancellationToken: cancellationToken);
var response = await httpClient.SendAsync(request, cancellationToken);
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;
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个对象存储服务的实现类,主要用于与又拍云(UPYUN)存储服务进行交互。让我详细解释一下这个代码:
类概述
ObjectStorageService 实现了 IObjectStorageOperation 接口,提供了完整的对象存储操作功能。
构造函数依赖注入
通过主构造函数注入了以下依赖:
UpyunOperator: 又拍云操作器,包含认证信息HttpClient: HTTP客户端,用于API调用ILogger: 日志记录器IFtpLogger: FTP日志记录器ParallelChunkBreakpointUpload: 并行分块断点上传服务UpyunUpload: 又拍云上传服务
主要功能方法
1. 上传功能
UploadAsync(Stream): 上传流数据UploadAsync(byte[]): 上传字节数组UploadFileAsync(CloudFile): 上传云文件,支持断点续传- 多个重载的私有
UploadAsync方法处理不同的上传场景
2. 下载功能
DownloadAsync(string): 下载文件并返回流SaveAsAsync(string, string): 下载文件并保存到本地路径
3. 删除功能
DeleteAsync(string): 删除指定路径的文件- 支持处理完整URL和相对路径两种格式
4. 文件管理功能
GetFolderListAsync(string): 获取目录列表,使用FTP协议GetFileInformationAsync(string): 获取文件信息(大小、MD5、上传时间等)
技术特点
1. HTTP/2 支持
Version = new Version(2, 0)
所有HTTP请求都使用HTTP/2协议。
2. 签名认证
await request.SignatureAsync(upyunOperator, cancellationToken: cancellationToken);
每个请求都会进行签名认证。
3. 异步编程
所有方法都采用异步模式,支持 CancellationToken。
4. 错误处理
- 参数验证(空值检查)
- HTTP状态码检查
- 异常日志记录
- 业务异常抛出
5. FTP协议支持
在获取文件夹列表时使用FTP协议,这是又拍云提供的一种访问方式。
6. 灵活的路径处理
支持多种路径格式,自动处理URL和相对路径的转换。
使用场景
这个服务适用于需要与又拍云存储进行交互的应用程序,提供了完整的文件上传、下载、删除和管理功能,特别适合需要大文件上传(支持断点续传)的场景。
评论加载中...