using Dpz.Core.Entity.Base.Image;
using Dpz.Core.Infrastructure;
using Microsoft.Extensions.Logging;
namespace Dpz.Core.Service.ObjectStorage;
public class UpyunUpload(HttpClient httpClient, ILogger<UpyunUpload> logger)
{
internal async Task<IFileMetadata> UploadAsync<TOperator>(
ICollection<string> pathToFile,
Func<HttpContent> setContent,
TOperator operatorConfiguration,
long size = 0,
string? hash = null,
string? contentMd5 = null,
CancellationToken cancellationToken = default
)
where TOperator : UpyunOperator
{
if (pathToFile.Count == 0)
{
throw new ArgumentException(
"argument pathToFile does not any contain item.",
nameof(pathToFile)
);
}
if (string.IsNullOrEmpty(operatorConfiguration.Bucket))
{
throw new ArgumentException(
"argument operatorConfiguration does not have bucket.",
nameof(operatorConfiguration)
);
}
var uploadAddress = string.Join("/", pathToFile.Select(Uri.EscapeDataString));
var request = new HttpRequestMessage(
HttpMethod.Put,
$"/{operatorConfiguration.Bucket}/{uploadAddress}"
)
{
Version = new Version(2, 0),
};
await request.SignatureAsync(
operatorConfiguration,
contentMd5,
cancellationToken: cancellationToken
);
request.Content = setContent();
using var response = await httpClient.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
logger.LogError("upload fail,status code:{StatusCode}", response.StatusCode);
throw new BusinessException($"upload fail,response status code:{response.StatusCode}");
}
#region upload result
var accessUrl = GetAccessUrl(uploadAddress, operatorConfiguration);
var fileType = response.Headers.GetResponseHeaderValue("x-upyun-file-type", "");
var frames = response.Headers.GetResponseHeaderValue("x-upyun-frames", "0");
_ = int.TryParse(frames, out var framesInt);
var width = response.Headers.GetResponseHeaderValue("x-upyun-width", "0");
_ = int.TryParse(width, out var widthInt);
var height = response.Headers.GetResponseHeaderValue("x-upyun-height", "0");
_ = int.TryParse(height, out var heightInt);
if (response.Content.Headers.ContentMD5 != null)
{
var md5Value = BitConverter
.ToString(response.Content.Headers.ContentMD5)
.Replace("-", "")
.ToLowerInvariant();
logger.LogInformation(
"upload file:{AccessUrl},MD5 value:{MD5Value}",
accessUrl,
md5Value
);
hash = md5Value;
}
#endregion
var format = GetImageFormat(fileType);
if (
widthInt > 0
|| heightInt > 0
|| framesInt > 0
|| format.Kind != ImageFormatKind.Unknown
)
{
return new ImageMetadata
{
Url = accessUrl,
Width = widthInt,
Height = heightInt,
Frames = framesInt,
Hash = hash,
Size = size,
Format = format,
};
}
return new FileMetadata(accessUrl, size, hash);
}
private static ImageFormat GetImageFormat(string? fileType)
{
return fileType?.ToLowerInvariant() switch
{
"jpg" or "jpeg" or "image/jpeg" => ImageFormat.Jpeg,
"png" or "image/png" => ImageFormat.Png,
"gif" or "image/gif" => ImageFormat.Gif,
"bmp" or "image/bmp" => ImageFormat.Bmp,
"webp" or "image/webp" => ImageFormat.Webp,
"tif" or "tiff" or "image/tiff" => ImageFormat.Tiff,
"heic" or "image/heic" => ImageFormat.Heic,
"avif" or "image/avif" => ImageFormat.Avif,
"ico" or "image/x-icon" or "image/vnd.microsoft.icon" => ImageFormat.Ico,
"svg" or "image/svg+xml" => ImageFormat.Svg,
_ => ImageFormat.Unknown,
};
}
private static string GetAccessUrl<TOperator>(string pathToFile, TOperator upyunOperator)
where TOperator : UpyunOperator
{
return (
upyunOperator.Host?.LastOrDefault() == '/'
? upyunOperator.Host
: upyunOperator.Host + "/"
) + pathToFile;
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个又拍云(Upyun)对象存储服务的文件上传类,使用 C# 编写。
类结构
public class UpyunUpload(HttpClient httpClient, ILogger<UpyunUpload> logger)
使用了 C# 12 的主构造函数(Primary Constructor)语法,注入了 HttpClient 和 ILogger 依赖。
核心方法:UploadAsync
功能
异步上传文件到又拍云存储,并返回文件元数据。
参数说明
- pathToFile: 文件路径集合(多级目录)
- setContent: 设置 HTTP 请求内容的委托函数
- operatorConfiguration: 又拍云操作员配置(包含 Bucket、认证信息等)
- size: 文件大小
- hash: 文件哈希值
- contentMd5: 内容 MD5 值
- cancellationToken: 取消令牌
核心流程
参数验证
- 检查路径集合是否为空
- 检查 Bucket 配置是否存在
构建上传请求
var uploadAddress = string.Join("/", pathToFile.Select(Uri.EscapeDataString));- 将路径数组组合成完整路径,并进行 URL 编码
- 使用 HTTP PUT 方法
- 使用 HTTP/2.0 协议
签名认证
await request.SignatureAsync(operatorConfiguration, contentMd5, ...);- 对请求进行签名(扩展方法,代码未显示)
发送请求并处理响应
- 检查状态码,失败则记录日志并抛出业务异常
解析响应头 从又拍云返回的响应头中提取:
x-upyun-file-type: 文件类型x-upyun-frames: 帧数(适用于动图)x-upyun-width: 图片宽度x-upyun-height: 图片高度Content-MD5: MD5 校验值
返回元数据
- 如果是图片(有宽高或帧数信息),返回
ImageMetadata - 否则返回普通
FileMetadata
- 如果是图片(有宽高或帧数信息),返回
辅助方法
GetImageFormat
根据文件类型字符串识别图片格式,支持:
- JPEG/JPG
- PNG
- GIF
- BMP
- WebP
- TIFF
- HEIC
- AVIF
- ICO
- SVG
使用 C# 的 switch 表达式和模式匹配,简洁高效。
GetAccessUrl
生成文件的访问 URL:
Host + "/" + pathToFile
自动处理 Host 末尾的斜杠问题。
设计亮点
- 泛型约束:
where TOperator : UpyunOperator确保类型安全 - 异步模式: 全异步操作,支持高并发
- 日志记录: 关键操作都有日志输出
- 异常处理: 参数验证和业务异常
- 多态返回: 根据文件类型返回不同的元数据对象
这是一个结构良好、类型安全的云存储客户端实现。
AI 正在分析代码…
评论加载中...