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; }
}
文件定位
TencentMediaQueueCallbackRecord.cs 位于 Dpz.Core.Public.Entity 项目(项目级全局 using 文件为 Usings.cs),属于"公共实体层"。该文件一个文件里定义了两个 MongoDB 文档模型:
TencentMediaQueueCallbackRecord—— 腾讯云媒体处理(MediaProcess)队列回调的落库记录;TencentMediaQueueJobRecord—— 回调中单个媒体处理任务的摘要(内嵌子文档)。
TencentMediaQueueCallbackRecord:回调落库记录
继承与接口
- 继承 BasicInformationEntity,因此自动获得
CreateTime、LastUpdateTime(均为DateTime,在基类上标注BsonDateTimeOptions(Kind = DateTimeKind.Local)),再往上继承 BaseEntity(本次未展开读取其成员)。 - 实现 IIndexedEntity<T>:该接口要求以
static abstract方式实现GetIndexDefinitions(),即实体自己声明集合需要哪些 MongoDB 索引。 - 标注
[BsonIgnoreExtraElements]:反序列化时忽略文档中多余的字段,避免线上文档比实体字段多时抛异常(典型于结构演进的集合)。
字段语义
| 字段 | 类型 | 说明 |
|---|---|---|
PayloadHash | required string | 回调正文的 SHA-256 哈希(十六进制小写),是幂等去重的唯一键 |
RawPayload | required string | 回调原始正文,原样保留 |
ContentType | string? | 请求头 Content-Type,供后续追溯格式协商 |
Format | required string | 由解析器识别出的回调格式(如 JSON / XML) |
ReceivedAt | DateTime | 回调接收时间,标注 [BsonDateTimeOptions(Kind = DateTimeKind.Local)] |
JobsDetail | List<TencentMediaQueueJobRecord> | 任务摘要列表,默认空集合 [] |
NonExistJobIds | List<string> | 回调中"任务不存在"的 JobId 列表,默认空集合 [] |
required 表示这三个属性必须在对象初始化器中被赋值(编译期强制),保障落库记录不会丢失幂等键、正文和格式信息。集合属性用 [](C# 12 集合表达式)初始化,避免出现 null,这与 Handler 中直接访问 record.JobsDetail.Count 的写法相吻合。
索引定义 GetIndexDefinitions()
返回 IReadOnlyList<EntityIndexDefinition>(结构定义见 EntityIndexDefinition.cs):
TencentMediaQueueCallbackRecord_PayloadHash_Unique:对PayloadHash升序,Unique = true。这是幂等性的数据库级兜底——即使并发重复回调绕过应用层判断,第二次插入也会因唯一索引冲突而失败。- 对
CreateTime降序:未显式命名,按EntityIndexDefinition.GetResolvedName()的规则由字段名 + 方向后缀自动生成名称,服务于"按接收/创建时间倒序查询最新回调"的场景。 - 对
"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 落地索引;本文件自身不含业务逻辑。