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: 取消令牌

核心流程

  1. 参数验证

    • 检查路径集合是否为空
    • 检查 Bucket 配置是否存在
  2. 构建上传请求

    var uploadAddress = string.Join("/", pathToFile.Select(Uri.EscapeDataString));
    
    • 将路径数组组合成完整路径,并进行 URL 编码
    • 使用 HTTP PUT 方法
    • 使用 HTTP/2.0 协议
  3. 签名认证

    await request.SignatureAsync(operatorConfiguration, contentMd5, ...);
    
    • 对请求进行签名(扩展方法,代码未显示)
  4. 发送请求并处理响应

    • 检查状态码,失败则记录日志并抛出业务异常
  5. 解析响应头 从又拍云返回的响应头中提取:

    • x-upyun-file-type: 文件类型
    • x-upyun-frames: 帧数(适用于动图)
    • x-upyun-width: 图片宽度
    • x-upyun-height: 图片高度
    • Content-MD5: MD5 校验值
  6. 返回元数据

    • 如果是图片(有宽高或帧数信息),返回 ImageMetadata
    • 否则返回普通 FileMetadata

辅助方法

GetImageFormat

根据文件类型字符串识别图片格式,支持:

  • JPEG/JPG
  • PNG
  • GIF
  • BMP
  • WebP
  • TIFF
  • HEIC
  • AVIF
  • ICO
  • SVG

使用 C# 的 switch 表达式和模式匹配,简洁高效。

GetAccessUrl

生成文件的访问 URL:

Host + "/" + pathToFile

自动处理 Host 末尾的斜杠问题。

设计亮点

  1. 泛型约束: where TOperator : UpyunOperator 确保类型安全
  2. 异步模式: 全异步操作,支持高并发
  3. 日志记录: 关键操作都有日志输出
  4. 异常处理: 参数验证和业务异常
  5. 多态返回: 根据文件类型返回不同的元数据对象

这是一个结构良好、类型安全的云存储客户端实现。

评论加载中...