网站首页 网站源码
website
站点相关全部源代码,隐藏了一些关于服务器的信息
using System.Buffers;
using System.Web;
using Dpz.Core.Public.Entity;
using Dpz.Core.WebApi.Models.EventRequest;
using MediatR;
using Microsoft.Extensions.Primitives;

#nullable enable

namespace Dpz.Core.WebApi.EventHandles;

/// <summary>
/// 处理又拍云回调通知
/// </summary>
public class UpyunNotificationEventHandle(
    ILogger<UpyunNotificationEventHandle> logger,
    UpyunOperator upyunOperator,
    IConfiguration configuration,
    IObjectStorageOperation objectStorageService
) : IRequestHandler<UpyunNotificationRequest, IActionResult>
{
    /// <summary>
    /// handle upyun notification
    /// </summary>
    /// <param name="request"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<IActionResult> Handle(
        UpyunNotificationRequest request,
        CancellationToken cancellationToken
    )
    {
        if (!await CheckSignature(request.Request))
        {
            return new UnauthorizedResult();
        }

        var body = await ReadBodyAsync(request.Request);

        logger.LogInformation(
            "receive upyun notification: {Body},authorization:{Authorization},content-type:{ContentType}",
            body,
            request.Request.Headers.Authorization,
            request.Request.Headers.ContentType
        );
        var parameters = HttpUtility.ParseQueryString(body);
        var bucketName = parameters.Get("bucket_name");
        if (string.IsNullOrEmpty(bucketName) || bucketName != upyunOperator.Bucket)
        {
            return new UnauthorizedResult();
        }

        var statusCode = parameters.Get("status_code");
        if (string.IsNullOrEmpty(statusCode) || statusCode != "200")
        {
            return new BadRequestResult();
        }
        var sourceUrl = parameters.Get("media_uris[0]");
        if (!string.IsNullOrEmpty(sourceUrl))
        {
            await objectStorageService.DeleteAsync(sourceUrl);
        }

        var meta = GetVideoMetaInformation(parameters);

        // todo save meta

        return new NoContentResult();
    }

    private async Task<bool> CheckSignature(HttpRequest request)
    {
        var authorization = request.Headers.Authorization;
        var requestDate = request.Headers.Date;
        var contentMd5 = request.Headers.ContentMD5;
        if (
            StringValues.IsNullOrEmpty(authorization)
            || StringValues.IsNullOrEmpty(requestDate)
            || StringValues.IsNullOrEmpty(contentMd5)
        )
        {
            return false;
        }

        var notifyUrl = configuration["NotifyUrl"] ?? throw new InvalidConfigurationException();
        var uri = new Uri(notifyUrl);

        // Method、URI、Date、Content-MD5 值,用英文符号 & 拼接为字符串
        var signatureBody = $"POST&{uri.OriginalString}&{requestDate}&{contentMd5}";
        var signatureKeyBytes = Encoding.UTF8.GetBytes(
            upyunOperator.Password ?? throw new InvalidConfigurationException()
        );

        using var hmac = new HMACSHA1(signatureKeyBytes);
        var signatureBodyBytes = Encoding.UTF8.GetBytes(signatureBody);
        await using var stream = ApplicationTools.MemoryStreamManager.GetStream(signatureBodyBytes);
        var signatureBytes = await hmac.ComputeHashAsync(stream);
        var signature = Convert.ToBase64String(signatureBytes);

        return authorization == $"UPYUN {upyunOperator.Operator}:{signature}";
    }

    private static async Task<string> ReadBodyAsync(HttpRequest request)
    {
        var body = new StringBuilder();
        var reader = request.BodyReader;
        while (true)
        {
            var readResult = await reader.ReadAsync();
            var buffer = readResult.Buffer;
            if (readResult.IsCompleted && buffer.Length > 0)
            {
                AppendString(body, in buffer);
            }

            reader.AdvanceTo(buffer.Start, buffer.End);

            if (readResult.IsCompleted)
            {
                break;
            }
        }

        return body.ToString();
    }

    private static void AppendString(
        StringBuilder result,
        in ReadOnlySequence<byte> readOnlySequence
    )
    {
        var span = readOnlySequence.IsSingleSegment
            ? readOnlySequence.First.Span
            : readOnlySequence.ToArray().AsSpan();
        result.Append(Encoding.UTF8.GetString(span));
    }

    private readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true };

    private Notification? GetVideoMetaInformation(NameValueCollection parameters)
    {
        var info = parameters.Get("info");
        Notification? notification = null;
        if (string.IsNullOrEmpty(info))
        {
            return notification;
        }

        var bytes = DpzAppBuilderExtensions.Base64StringToBytes(info);
        if (bytes.Length <= 0)
        {
            return notification;
        }

        var json = Encoding.UTF8.GetString(bytes);

        try
        {
            notification = JsonSerializer.Deserialize<Notification>(json, _options);
        }
        catch (Exception e)
        {
            logger.LogError(e, "parse notification info failed: {Json}", json);
            return notification;
        }

        logger.LogInformation("notification: {@Notification}", notification);
        return notification;
    }
}

// https://www.yuque.com/u160746/fk96qt/flqrx8#u8uiM

/*
 *
avopt=%2Fht%2F5%2Fs%2F1080p%2816%3A9%29&bucket_name=images-dpangzi&description=OK&info=eyJzdHJlYW1zIjpbeyJiaXRfZGVwdGgiOjgsImNvZGVjIjoiaDI2NCIsImNvZGVjX2Rlc2MiOiJILjI2NCAvIEFWQyAvIE1QRUctNCBBVkMgLyBNUEVHLTQgcGFydCAxMCIsImNvbG9yX3NwYWNlIjoiYnQ3MDkiLCJpbmRleCI6MCwibWV0YWRhdGEiOnsidmFyaWFudF9iaXRyYXRlIjoiMCJ9LCJwaXhfZm10IjoieXV2NDIwcCIsInNhbXBsZV9hc3BlY3RfcmF0aW8iOiIxOjEiLCJ0eXBlIjoidmlkZW8iLCJ2aWRlb19mcHMiOjI1LCJ2aWRlb19oZWlnaHQiOjEwODAsInZpZGVvX3dpZHRoIjoxOTIwfSx7ImF1ZGlvX2NoYW5uZWxzIjoyLCJhdWRpb19zYW1wbGVyYXRlIjo0ODAwMCwiY29kZWMiOiJhYWMiLCJjb2RlY19kZXNjIjoiQUFDIChBZHZhbmNlZCBBdWRpbyBDb2RpbmcpIiwiaW5kZXgiOjEsIm1ldGFkYXRhIjp7InZhcmlhbnRfYml0cmF0ZSI6IjAifSwidHlwZSI6ImF1ZGlvIn1dLCJmb3JtYXQiOnsiZHVyYXRpb24iOjEwNS44OCwiZnVsbG5hbWUiOiJBcHBsZSBIVFRQIExpdmUgU3RyZWFtaW5nIiwiYml0cmF0ZSI6MTE4LCJmaWxlc2l6ZSI6MTU0OCwiZm9ybWF0IjoiaGxzIn19&media_uris%5B0%5D=%2FSourceVideo%2F%E3%80%8A%E6%88%91%E5%9C%A8%E9%82%A3%E4%B8%80%E8%A7%92%E8%90%BD%E6%82%A3%E8%BF%87%E4%BC%A4%E9%A3%8E%E3%80%8B+%28%E7%A5%9E%E9%BE%99%E5%A3%AB%E5%8A%9B%E6%9E%B6%29.mp4&path%5B0%5D=%2FVideo%2F%E6%88%91%E5%9C%A8%E9%82%A3%E4%B8%80%E8%A7%92%E8%90%BD%E6%82%A3%E8%BF%87%E4%BC%A4%E9%A3%8E%2F1080p.m3u8&signature=15bfe38ab83185c23ca863fcb1fdcd3d&status_code=200&task_id=2cb5818b43820daeafadb9a271717a5e&timestamp=1733469346&ts=%2FVideo%2F%E6%88%91%E5%9C%A8%E9%82%A3%E4%B8%80%E8%A7%92%E8%90%BD%E6%82%A3%E8%BF%87%E4%BC%A4%E9%A3%8E%2Fts&uploads%5B0%5D=%2FVideo%2F%E6%88%91%E5%9C%A8%E9%82%A3%E4%B8%80%E8%A7%92%E8%90%BD%E6%82%A3%E8%BF%87%E4%BC%A4%E9%A3%8E%2F1080p.m3u8
 *
 */

/*
 *
{
  "streams": [
    {
      "bit_depth": 8,
      "codec": "h264",
      "codec_desc": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
      "color_space": "bt709",
      "index": 0,
      "metadata": {
        "variant_bitrate": "0"
      },
      "pix_fmt": "yuv420p",
      "sample_aspect_ratio": "1:1",
      "type": "video",
      "video_fps": 25,
      "video_height": 1080,
      "video_width": 1920
    },
    {
      "audio_channels": 2,
      "audio_samplerate": 48000,
      "codec": "aac",
      "codec_desc": "AAC (Advanced Audio Coding)",
      "index": 1,
      "metadata": {
        "variant_bitrate": "0"
      },
      "type": "audio"
    }
  ],
  "format": {
    "duration": 105.88,
    "fullname": "Apple HTTP Live Streaming",
    "bitrate": 118,
    "filesize": 1548,
    "format": "hls"
  }
}
 *
 */
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

上述代码是一个 ASP.NET Core Web API 的事件处理程序,专门用于处理又拍云(Upyun)的回调通知。以下是代码的主要功能和结构的详细解释:

1. 类和构造函数

  • UpyunNotificationEventHandle: 这是一个处理又拍云通知的事件处理程序,继承自 IRequestHandler<UpyunNotificationRequest, IActionResult>,表示它处理 UpyunNotificationRequest 类型的请求并返回 IActionResult 类型的结果。
  • 构造函数: 接受多个依赖项,包括日志记录器、又拍云操作类、配置和对象存储操作服务。

2. 处理请求

  • Handle 方法: 这是处理请求的主要方法。它执行以下步骤:
    • 签名验证: 调用 CheckSignature 方法验证请求的签名。如果签名不正确,返回 UnauthorizedResult
    • 读取请求体: 使用 ReadBodyAsync 方法读取请求的主体内容。
    • 日志记录: 记录接收到的通知内容、授权信息和内容类型。
    • 参数解析: 使用 HttpUtility.ParseQueryString 解析请求体中的参数。
    • 桶名验证: 检查 bucket_name 参数是否与配置中的桶名匹配。如果不匹配,返回 UnauthorizedResult
    • 状态码验证: 检查 status_code 是否为 "200"。如果不是,返回 BadRequestResult
    • 删除源 URL: 如果 media_uris[0] 存在,调用 objectStorageService.DeleteAsync 删除该 URL。
    • 获取视频元信息: 调用 GetVideoMetaInformation 方法获取视频的元信息,并在 TODO 注释中提到需要保存这些元信息。

3. 签名验证

  • CheckSignature 方法: 验证请求的签名是否有效。它从请求头中提取 AuthorizationDateContent-MD5,并根据这些信息生成一个签名,与请求中的签名进行比较。

4. 读取请求体

  • ReadBodyAsync 方法: 异步读取请求的主体内容,使用 StringBuilder 来构建最终的字符串。

5. 解析视频元信息

  • GetVideoMetaInformation 方法: 从参数中提取 info 字段,并将其解码为 JSON 格式,最后反序列化为 Notification 对象。如果解析失败,会记录错误信息。

6. 其他

  • AppendString 方法: 辅助方法,用于将 ReadOnlySequence<byte> 转换为字符串并追加到 StringBuilder 中。
  • JsonSerializerOptions: 用于 JSON 反序列化时的配置,设置为属性名不区分大小写。

总结

整体上,这段代码实现了一个完整的回调处理流程,确保了请求的合法性,处理了视频上传的通知,并提供了日志记录和错误处理机制。它是一个典型的 Web API 事件处理程序,适用于处理外部服务的回调通知。

loading