using Dpz.Core.Entity.Base;
using Dpz.Core.Entity.Base.Indexes;

namespace Dpz.Core.Public.Entity;

/// <summary>
/// 腾讯云媒体处理队列回调记录
/// </summary>
[BsonIgnoreExtraElements]
public class TencentMediaQueueCallbackRecord
    : BasicInformationEntity,
        IIndexedEntity<TencentMediaQueueCallbackRecord>
{
    /// <summary>
    /// 回调正文的 SHA-256 哈希,用于幂等去重
    /// </summary>
    public required string PayloadHash { get; set; }

    /// <summary>
    /// 回调原始正文
    /// </summary>
    public required string RawPayload { get; set; }

    /// <summary>
    /// 回调请求的 Content-Type
    /// </summary>
    public string? ContentType { get; set; }

    /// <summary>
    /// 解析出的回调格式
    /// </summary>
    public required string Format { get; set; }

    /// <summary>
    /// 回调接收时间
    /// </summary>
    [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
    public DateTime ReceivedAt { get; set; }

    /// <summary>
    /// 回调中的媒体处理任务摘要
    /// </summary>
    public List<TencentMediaQueueJobRecord> JobsDetail { get; set; } = [];

    /// <summary>
    /// 回调中不存在的任务 ID
    /// </summary>
    public List<string> NonExistJobIds { get; set; } = [];

    /// <summary>
    /// 获取腾讯云媒体处理队列回调记录的索引定义
    /// </summary>
    public static IReadOnlyList<EntityIndexDefinition> GetIndexDefinitions() =>
        [
            new()
            {
                Name = "TencentMediaQueueCallbackRecord_PayloadHash_Unique",
                Fields =
                [
                    EntityIndexField.Ascending<TencentMediaQueueCallbackRecord>(x => x.PayloadHash),
                ],
                Unique = true,
            },
            new()
            {
                Fields =
                [
                    EntityIndexField.Descending<TencentMediaQueueCallbackRecord>(x => x.CreateTime),
                ],
            },
            new() { Fields = [EntityIndexField.Ascending("JobsDetail.JobId")] },
        ];
}

/// <summary>
/// 腾讯云媒体处理任务回调摘要
/// </summary>
[BsonIgnoreExtraElements]
public class TencentMediaQueueJobRecord
{
    /// <summary>
    /// 媒体处理任务 ID
    /// </summary>
    public string? JobId { get; set; }

    /// <summary>
    /// 任务状态
    /// </summary>
    public string? State { get; set; }

    /// <summary>
    /// 任务错误码
    /// </summary>
    public string? Code { get; set; }

    /// <summary>
    /// 任务错误信息
    /// </summary>
    public string? Message { get; set; }

    /// <summary>
    /// 任务标签
    /// </summary>
    public string? Tag { get; set; }

    /// <summary>
    /// 任务所属队列 ID
    /// </summary>
    public string? QueueId { get; set; }

    /// <summary>
    /// 任务创建时间
    /// </summary>
    public string? CreationTime { get; set; }

    /// <summary>
    /// 任务开始时间
    /// </summary>
    public string? StartTime { get; set; }

    /// <summary>
    /// 任务结束时间
    /// </summary>
    public string? EndTime { get; set; }

    /// <summary>
    /// 输入对象
    /// </summary>
    public string? InputObject { get; set; }

    /// <summary>
    /// 输出对象
    /// </summary>
    public string? OutputObject { get; set; }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

文件定位

TencentMediaQueueCallbackRecord.cs 位于 Dpz.Core.Public.Entity 项目(项目级全局 using 文件为 Usings.cs),属于"公共实体层"。该文件一个文件里定义了两个 MongoDB 文档模型:

  1. TencentMediaQueueCallbackRecord —— 腾讯云媒体处理(MediaProcess)队列回调的落库记录;
  2. TencentMediaQueueJobRecord —— 回调中单个媒体处理任务的摘要(内嵌子文档)。

TencentMediaQueueCallbackRecord:回调落库记录

继承与接口

  • 继承 BasicInformationEntity,因此自动获得 CreateTime、LastUpdateTime(均为 DateTime,在基类上标注 BsonDateTimeOptions(Kind = DateTimeKind.Local)),再往上继承 BaseEntity(本次未展开读取其成员)。
  • 实现 IIndexedEntity<T>:该接口要求以 static abstract 方式实现 GetIndexDefinitions(),即实体自己声明集合需要哪些 MongoDB 索引。
  • 标注 [BsonIgnoreExtraElements]:反序列化时忽略文档中多余的字段,避免线上文档比实体字段多时抛异常(典型于结构演进的集合)。

字段语义

字段类型说明
PayloadHashrequired string回调正文的 SHA-256 哈希(十六进制小写),是幂等去重的唯一键
RawPayloadrequired string回调原始正文,原样保留
ContentTypestring?请求头 Content-Type,供后续追溯格式协商
Formatrequired string由解析器识别出的回调格式(如 JSON / XML)
ReceivedAtDateTime回调接收时间,标注 [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
JobsDetailList<TencentMediaQueueJobRecord>任务摘要列表,默认空集合 []
NonExistJobIdsList<string>回调中"任务不存在"的 JobId 列表,默认空集合 []

required 表示这三个属性必须在对象初始化器中被赋值(编译期强制),保障落库记录不会丢失幂等键、正文和格式信息。集合属性用 [](C# 12 集合表达式)初始化,避免出现 null,这与 Handler 中直接访问 record.JobsDetail.Count 的写法相吻合。

索引定义 GetIndexDefinitions()

返回 IReadOnlyList<EntityIndexDefinition>(结构定义见 EntityIndexDefinition.cs):

  1. TencentMediaQueueCallbackRecord_PayloadHash_Unique:对 PayloadHash 升序,Unique = true。这是幂等性的数据库级兜底——即使并发重复回调绕过应用层判断,第二次插入也会因唯一索引冲突而失败。
  2. 对 CreateTime 降序:未显式命名,按 EntityIndexDefinition.GetResolvedName() 的规则由字段名 + 方向后缀自动生成名称,服务于"按接收/创建时间倒序查询最新回调"的场景。
  3. 对 "JobsDetail.JobId" 升序:使用字符串形式的点号路径(子文档/数组元素字段),用于按媒体处理任务 ID 反查回调记录。

索引定义不是在本文件里被"执行"的:由 MongoIndexInitializer.cs 通过反射判断实体类型是否可赋给 IIndexedEntity<T>,进而取出定义并在集合上创建/比对索引(每个实体类型在应用生命周期内只处理一次)。


TencentMediaQueueJobRecord:任务摘要子文档

一个纯数据载体(POCO),同样标注 [BsonIgnoreExtraElements]。所有属性都是可空 string,包括 JobId、State、Code、Message、Tag、QueueId、CreationTime、StartTime、EndTime、InputObject、OutputObject。

值得注意的是:时间字段(CreationTime / StartTime / EndTime)刻意保持为 string 而非 DateTime——这与腾讯云回调原文的格式保真有关(本文件未给出格式化/解析逻辑,具体约定需以解析器为准,不做臆断)。该类型作为 JobsDetail 的元素嵌入回调文档,因此"按 JobsDetail.JobId 建索引"才成立。


与相关文件的协作关系

  • 写入方:TencentMediaProcessCallbackHandler.cs(MediatR Command Handler)。它读取请求体 → 计算 SHA-256(Convert.ToHexString(...).ToLowerInvariant())→ new TencentMediaQueueCallbackRecord { ... } 填充本文件定义的所有 required 字段以及 CreateTime/LastUpdateTime(由 Handler 手动赋值,基类未自动填充)→ 调用泛型仓储 IRepository<TencentMediaQueueCallbackRecord>.InsertAsync。
  • 幂等配合:Handler 捕获 MongoWriteException 且 WriteError.Category == ServerErrorCategory.DuplicateKey 时,返回 ResponseResult.Ok("回调已处理")——这正是本文件 PayloadHash 唯一索引存在的意义:应用层哈希去重 + 数据库唯一索引兜底,两者协作实现"重复回调不重复入库、且对调用方幂等成功"。
  • 解析方:TencentMediaProcessCallbackParser.cs 定义内部负载 TencentMediaProcessCallbackPayload(string Format, List<TencentMediaQueueJobRecord> JobsDetail, List<string> NonExistJobIds),直接复用本文件的 TencentMediaQueueJobRecord 作为任务摘要类型,并负责 JSON/XML 双格式识别与结构化。
  • 测试:TencentMediaProcessCallbackHandlerTest.cs 使用 Mock<IRepository<TencentMediaQueueCallbackRecord>> 验证插入内容与幂等分支。
flowchart TD
    A["腾讯云媒体处理队列回调 HTTP 请求"] --> B[TencentMediaProcessCallbackHandler]
    B --> C["ReadPayloadAsync:读取正文<br/>计算 SHA-256 → PayloadHash"]
    C -->|空正文| E1["ResponseResult.Fail 400"]
    C --> D[TencentMediaProcessCallbackParser.Parse<br/>识别 JSON / XML]
    D -->|FormatException| E1
    D --> F["TencentMediaProcessCallbackPayload<br/>Format / JobsDetail / NonExistJobIds"]
    F --> G["构造 TencentMediaQueueCallbackRecord<br/>填充 required 字段 + CreateTime/LastUpdateTime"]
    G --> H["IRepository.InsertAsync"]
    H --> I{"MongoDB 写入"}
    I -->|成功| J["ResponseResult.Ok<br/>记录已保存"]
    I -->|"DuplicateKey<br/>(PayloadHash 唯一索引)"| K["按幂等处理<br/>ResponseResult.Ok 回调已处理"]
    L["MongoIndexInitializer<br/>反射读取 GetIndexDefinitions()"] -.->|"创建/校验索引"| I

小结

该文件是"腾讯云媒体处理回调"功能在数据层的契约定义:用两个 BsonIgnoreExtraElements 文档类型描述回调原文 + 解析结果 + 任务摘要的持久化结构,用 required 与集合初始值约束数据完备性,并通过 IIndexedEntity 声明三个索引(PayloadHash 唯一索引是幂等去重的关键)。其运行时行为由 TencentMediaProcessCallbackHandler.cs 写入、由 TencentMediaProcessCallbackParser.cs 产出数据、由 MongoIndexInitializer.cs 落地索引;本文件自身不含业务逻辑。

评论加载中...